@khanacademy/math-input 17.0.4 → 17.0.5
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/dist/es/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -2
- package/.eslintrc.js +0 -18
- package/CHANGELOG.md +0 -660
- package/less/main.less +0 -2
- package/less/overrides.less +0 -122
- package/src/components/__tests__/integration.test.tsx +0 -300
- package/src/components/aphrodite-css-transition-group/index.tsx +0 -78
- package/src/components/aphrodite-css-transition-group/transition-child.tsx +0 -192
- package/src/components/aphrodite-css-transition-group/types.ts +0 -20
- package/src/components/aphrodite-css-transition-group/util.ts +0 -97
- package/src/components/input/__tests__/context-tracking.test.ts +0 -176
- package/src/components/input/__tests__/mathquill-helpers.test.ts +0 -105
- package/src/components/input/__tests__/mathquill.test.ts +0 -747
- package/src/components/input/__tests__/test-math-wrapper.ts +0 -29
- package/src/components/input/cursor-contexts.ts +0 -37
- package/src/components/input/cursor-handle.tsx +0 -137
- package/src/components/input/cursor-styles.ts +0 -10
- package/src/components/input/drag-listener.ts +0 -79
- package/src/components/input/math-input.tsx +0 -1036
- package/src/components/input/math-wrapper.ts +0 -189
- package/src/components/input/mathquill-helpers.ts +0 -262
- package/src/components/input/mathquill-instance.ts +0 -106
- package/src/components/input/mathquill-types.ts +0 -32
- package/src/components/input/scroll-into-view.ts +0 -65
- package/src/components/key-handlers/__tests__/handle-jump-out.test.ts +0 -94
- package/src/components/key-handlers/handle-arrow.ts +0 -70
- package/src/components/key-handlers/handle-backspace.ts +0 -277
- package/src/components/key-handlers/handle-exponent.ts +0 -53
- package/src/components/key-handlers/handle-jump-out.ts +0 -107
- package/src/components/key-handlers/key-translator.ts +0 -222
- package/src/components/keypad/__tests__/__snapshots__/keypad.test.tsx.snap +0 -1913
- package/src/components/keypad/__tests__/__snapshots__/mobile-keypad.test.tsx.snap +0 -600
- package/src/components/keypad/__tests__/keypad-button.test.tsx +0 -84
- package/src/components/keypad/__tests__/keypad-v2-mathquill.test.tsx +0 -304
- package/src/components/keypad/__tests__/keypad-v2.cypress.ts +0 -16
- package/src/components/keypad/__tests__/keypad.test.tsx +0 -321
- package/src/components/keypad/__tests__/mobile-keypad.test.tsx +0 -115
- package/src/components/keypad/__tests__/test-data-tabs.ts +0 -21
- package/src/components/keypad/button-assets.tsx +0 -1880
- package/src/components/keypad/index.tsx +0 -2
- package/src/components/keypad/keypad-button.stories.tsx +0 -81
- package/src/components/keypad/keypad-button.tsx +0 -124
- package/src/components/keypad/keypad-mathquill.stories.tsx +0 -109
- package/src/components/keypad/keypad-pages/extras-page.tsx +0 -35
- package/src/components/keypad/keypad-pages/fractions-page.tsx +0 -125
- package/src/components/keypad/keypad-pages/geometry-page.tsx +0 -34
- package/src/components/keypad/keypad-pages/keypad-pages.stories.tsx +0 -37
- package/src/components/keypad/keypad-pages/numbers-page.tsx +0 -94
- package/src/components/keypad/keypad-pages/operators-page.tsx +0 -117
- package/src/components/keypad/keypad.tsx +0 -233
- package/src/components/keypad/mobile-keypad-internals.tsx +0 -240
- package/src/components/keypad/mobile-keypad.tsx +0 -24
- package/src/components/keypad/navigation-button.tsx +0 -127
- package/src/components/keypad/navigation-pad.stories.tsx +0 -26
- package/src/components/keypad/navigation-pad.tsx +0 -67
- package/src/components/keypad/shared-keys.tsx +0 -109
- package/src/components/keypad/utils.ts +0 -34
- package/src/components/keypad-context.tsx +0 -70
- package/src/components/prop-types.ts +0 -16
- package/src/components/tabbar/__tests__/tabbar.test.tsx +0 -105
- package/src/components/tabbar/icons.tsx +0 -122
- package/src/components/tabbar/index.ts +0 -1
- package/src/components/tabbar/item.tsx +0 -146
- package/src/components/tabbar/tabbar.stories.tsx +0 -83
- package/src/components/tabbar/tabbar.tsx +0 -65
- package/src/data/key-configs.ts +0 -770
- package/src/data/keys.ts +0 -123
- package/src/enums.ts +0 -27
- package/src/fake-react-native-web/index.ts +0 -11
- package/src/fake-react-native-web/text.tsx +0 -55
- package/src/fake-react-native-web/view.tsx +0 -91
- package/src/full-keypad.stories.tsx +0 -142
- package/src/full-mobile-input.stories.tsx +0 -115
- package/src/index.ts +0 -52
- package/src/types.ts +0 -70
- package/src/utils.test.ts +0 -33
- package/src/utils.ts +0 -61
- package/src/version.ts +0 -10
- package/tsconfig-build.json +0 -11
- package/tsconfig-build.tsbuildinfo +0 -1
package/less/overrides.less
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
@wonderBlocksBlue: #1865f2;
|
|
2
|
-
@offBlack16: rgba(33, 36, 44, 0.16);
|
|
3
|
-
|
|
4
|
-
@cursorWidth: 2px;
|
|
5
|
-
@cursorMargin: -1px;
|
|
6
|
-
@emptyBlockBorderRadius: 1px;
|
|
7
|
-
@emptyBlockPadding: 4px;
|
|
8
|
-
|
|
9
|
-
// Originally this was 500ms to match MathQuill's own
|
|
10
|
-
// frequency for toggling the application of the .mq-blink class.
|
|
11
|
-
// Due to the inconsistent nature of `setTimer` this caused weird
|
|
12
|
-
// flickering in the animation, so keeping this below MQ's animation.
|
|
13
|
-
@cursorAnimationDuration: 300ms;
|
|
14
|
-
|
|
15
|
-
.keypad-input {
|
|
16
|
-
outline: none !important;
|
|
17
|
-
|
|
18
|
-
.mq-editable-field {
|
|
19
|
-
.mq-root-block {
|
|
20
|
-
overflow-x: auto;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.mq-cursor:not(:only-child),
|
|
24
|
-
.mq-root-block.mq-hasCursor > .mq-cursor:only-child {
|
|
25
|
-
/* HACK(charlie): Magic numbers to properly size and position the vertical
|
|
26
|
-
cursor, which is visible whenever the cursor is not alone in its parent,
|
|
27
|
-
with the exception that it's also visible when the entire input is
|
|
28
|
-
empty. */
|
|
29
|
-
height: 20px !important;
|
|
30
|
-
width: @cursorWidth;
|
|
31
|
-
margin-top: -5px !important;
|
|
32
|
-
vertical-align: middle !important;
|
|
33
|
-
border-radius: 1px !important;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.mq-cursor {
|
|
37
|
-
border-left: @cursorWidth solid @wonderBlocksBlue !important;
|
|
38
|
-
|
|
39
|
-
margin-left: @cursorMargin !important;
|
|
40
|
-
margin-right: @cursorMargin !important;
|
|
41
|
-
|
|
42
|
-
// Fade the cursor out, overriding MathQuill's default behavior.
|
|
43
|
-
opacity: 1 !important;
|
|
44
|
-
transition: opacity @cursorAnimationDuration ease !important;
|
|
45
|
-
visibility: visible !important;
|
|
46
|
-
|
|
47
|
-
&.mq-blink {
|
|
48
|
-
// And fade the cursor back in.
|
|
49
|
-
opacity: 0 !important;
|
|
50
|
-
visibility: visible !important;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.mq-non-leaf .mq-cursor:only-child {
|
|
55
|
-
// Set the cursor to a grey rectangle, rather than a vertical line.
|
|
56
|
-
border: @cursorWidth solid !important;
|
|
57
|
-
border-color: @wonderBlocksBlue !important;
|
|
58
|
-
border-radius: @emptyBlockBorderRadius;
|
|
59
|
-
opacity: 1 !important;
|
|
60
|
-
padding: 0 @emptyBlockPadding 0 @emptyBlockPadding;
|
|
61
|
-
transition: border-color @cursorAnimationDuration ease !important;
|
|
62
|
-
|
|
63
|
-
&.mq-blink {
|
|
64
|
-
// And animate it between blue and gray.
|
|
65
|
-
border-color: @wonderBlocksBlue !important;
|
|
66
|
-
opacity: 1 !important;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.mq-empty {
|
|
72
|
-
background: transparent !important;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Adds empty block styling to elements with .mq-hasCursor but without a
|
|
76
|
-
// cursor element (this happens where the cursor is when the math input
|
|
77
|
-
// loses focus).
|
|
78
|
-
.mq-empty:not(.mq-root-block):after,
|
|
79
|
-
.mq-hasCursor:empty:not(.mq-root-block):after {
|
|
80
|
-
border: @cursorWidth solid @offBlack16;
|
|
81
|
-
border-radius: @emptyBlockBorderRadius;
|
|
82
|
-
// Hides the 'c' content added by MathQuill to measure the width.
|
|
83
|
-
color: transparent;
|
|
84
|
-
display: inline-block;
|
|
85
|
-
margin-left: @cursorMargin;
|
|
86
|
-
margin-right: @cursorMargin;
|
|
87
|
-
padding: 0 @emptyBlockPadding 0 @emptyBlockPadding;
|
|
88
|
-
visibility: visible !important;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.mq-selection .mq-empty:not(.mq-root-block):after {
|
|
92
|
-
border-color: white;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.mq-hasCursor:empty:not(.mq-root-block):after {
|
|
96
|
-
// Place a 'c' inside for sizing the cursor (for the case explained
|
|
97
|
-
// above); normally, MathQuill already does this for .mq-cursor.
|
|
98
|
-
content: "c";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.mq-math-mode .mq-selection,
|
|
102
|
-
.mq-editable-field .mq-selection {
|
|
103
|
-
.mq-non-leaf {
|
|
104
|
-
background: @wonderBlocksBlue !important;
|
|
105
|
-
border-color: white !important;
|
|
106
|
-
color: white !important;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.mq-scaled {
|
|
110
|
-
background: transparent !important;
|
|
111
|
-
border-color: transparent !important;
|
|
112
|
-
color: white !important;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.mq-selection {
|
|
117
|
-
background: @wonderBlocksBlue !important;
|
|
118
|
-
border-color: white !important;
|
|
119
|
-
color: white !important;
|
|
120
|
-
display: inline-block !important;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
import "@testing-library/jest-dom";
|
|
2
|
-
import {
|
|
3
|
-
screen,
|
|
4
|
-
render,
|
|
5
|
-
fireEvent,
|
|
6
|
-
within,
|
|
7
|
-
waitFor,
|
|
8
|
-
} from "@testing-library/react";
|
|
9
|
-
import userEvent from "@testing-library/user-event";
|
|
10
|
-
import MathQuill from "mathquill";
|
|
11
|
-
import React, {useState} from "react";
|
|
12
|
-
|
|
13
|
-
import {KeypadType} from "../../enums";
|
|
14
|
-
import MathInput from "../input/math-input";
|
|
15
|
-
import {MobileKeypad} from "../keypad";
|
|
16
|
-
import {KeypadContext, StatefulKeypadContextProvider} from "../keypad-context";
|
|
17
|
-
|
|
18
|
-
import type {KeypadConfiguration} from "../../types";
|
|
19
|
-
|
|
20
|
-
const MQ = MathQuill.getInterface(3);
|
|
21
|
-
|
|
22
|
-
const defaultConfiguration: KeypadConfiguration = {
|
|
23
|
-
keypadType: KeypadType.FRACTION,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function InputWithContext({keypadConfiguration}) {
|
|
27
|
-
const [value, setValue] = useState<string>("");
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<KeypadContext.Consumer>
|
|
31
|
-
{({keypadElement}) => {
|
|
32
|
-
return (
|
|
33
|
-
<MathInput
|
|
34
|
-
keypadElement={keypadElement as any}
|
|
35
|
-
value={value}
|
|
36
|
-
onChange={(nextValue, cb) => {
|
|
37
|
-
setValue(nextValue);
|
|
38
|
-
cb();
|
|
39
|
-
}}
|
|
40
|
-
onFocus={() => {
|
|
41
|
-
keypadElement?.configure(
|
|
42
|
-
keypadConfiguration,
|
|
43
|
-
() => {
|
|
44
|
-
keypadElement?.activate();
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
}}
|
|
48
|
-
onBlur={() => {
|
|
49
|
-
keypadElement?.dismiss();
|
|
50
|
-
}}
|
|
51
|
-
/>
|
|
52
|
-
);
|
|
53
|
-
}}
|
|
54
|
-
</KeypadContext.Consumer>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function KeypadWithContext() {
|
|
59
|
-
return (
|
|
60
|
-
<KeypadContext.Consumer>
|
|
61
|
-
{({setKeypadElement}) => {
|
|
62
|
-
return (
|
|
63
|
-
<MobileKeypad
|
|
64
|
-
onElementMounted={setKeypadElement}
|
|
65
|
-
onDismiss={() => {}}
|
|
66
|
-
onAnalyticsEvent={async () => {}}
|
|
67
|
-
/>
|
|
68
|
-
);
|
|
69
|
-
}}
|
|
70
|
-
</KeypadContext.Consumer>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function ConnectedMathInput({keypadConfiguration = defaultConfiguration}) {
|
|
75
|
-
return (
|
|
76
|
-
<StatefulKeypadContextProvider>
|
|
77
|
-
<InputWithContext keypadConfiguration={keypadConfiguration} />
|
|
78
|
-
<KeypadWithContext />
|
|
79
|
-
</StatefulKeypadContextProvider>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
describe("math input integration", () => {
|
|
84
|
-
it("renders", () => {
|
|
85
|
-
render(<ConnectedMathInput />);
|
|
86
|
-
|
|
87
|
-
expect(
|
|
88
|
-
screen.getByLabelText(
|
|
89
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
90
|
-
),
|
|
91
|
-
).toBeInTheDocument();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("doesn't show the keypad initially", () => {
|
|
95
|
-
render(<ConnectedMathInput />);
|
|
96
|
-
|
|
97
|
-
expect(
|
|
98
|
-
screen.queryByRole("button", {name: "1"}),
|
|
99
|
-
).not.toBeInTheDocument();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("shows the keypad after input touch-interaction", async () => {
|
|
103
|
-
render(<ConnectedMathInput />);
|
|
104
|
-
|
|
105
|
-
const input = screen.getByLabelText(
|
|
106
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
fireEvent.touchStart(input);
|
|
110
|
-
|
|
111
|
-
await waitFor(() => {
|
|
112
|
-
expect(screen.getByRole("button", {name: "4"})).toBeVisible();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(screen.getByRole("button", {name: "1"})).toBeVisible();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("shows the keypad after input click-interaction", async () => {
|
|
119
|
-
render(<ConnectedMathInput />);
|
|
120
|
-
|
|
121
|
-
const input = screen.getByLabelText(
|
|
122
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
userEvent.click(input);
|
|
126
|
-
|
|
127
|
-
await waitFor(() => {
|
|
128
|
-
expect(screen.getByRole("button", {name: "4"})).toBeVisible();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
expect(screen.getByRole("button", {name: "1"})).toBeVisible();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("updates input when using keypad", async () => {
|
|
135
|
-
render(<ConnectedMathInput />);
|
|
136
|
-
|
|
137
|
-
const input = screen.getByLabelText(
|
|
138
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
fireEvent.touchStart(input);
|
|
142
|
-
|
|
143
|
-
await waitFor(() => {
|
|
144
|
-
expect(screen.getByRole("button", {name: "4"})).toBeVisible();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
userEvent.click(screen.getByRole("button", {name: "1"}));
|
|
148
|
-
|
|
149
|
-
// MathQuill is problematic,
|
|
150
|
-
// this is the only way I know how to test the "input"
|
|
151
|
-
const mathquillInput =
|
|
152
|
-
// eslint-disable-next-line testing-library/no-node-access
|
|
153
|
-
document.getElementsByClassName("mq-root-block")[0] as HTMLElement;
|
|
154
|
-
const span1 = within(mathquillInput).getByText("1");
|
|
155
|
-
|
|
156
|
-
expect(span1).toBeVisible();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("updates input when pressing many numbers", async () => {
|
|
160
|
-
render(<ConnectedMathInput />);
|
|
161
|
-
|
|
162
|
-
const input = screen.getByLabelText(
|
|
163
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
fireEvent.touchStart(input);
|
|
167
|
-
|
|
168
|
-
await waitFor(() => {
|
|
169
|
-
expect(screen.getByRole("button", {name: "4"})).toBeVisible();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const testNumbers = [8, 6, 7, 5, 3, 0, 9];
|
|
173
|
-
testNumbers.forEach((num) => {
|
|
174
|
-
userEvent.click(screen.getByRole("button", {name: `${num}`}));
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// MathQuill is problematic,
|
|
178
|
-
// this is how to get the value of the input directly from MQ
|
|
179
|
-
const mathquillInstance = MQ(
|
|
180
|
-
// eslint-disable-next-line testing-library/no-node-access
|
|
181
|
-
document.getElementsByClassName(
|
|
182
|
-
"mq-editable-field",
|
|
183
|
-
)[0] as HTMLElement,
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
expect(mathquillInstance?.latex()).toBe("8675309");
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("can handle symbols", async () => {
|
|
190
|
-
render(<ConnectedMathInput />);
|
|
191
|
-
|
|
192
|
-
const input = screen.getByLabelText(
|
|
193
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
fireEvent.touchStart(input);
|
|
197
|
-
|
|
198
|
-
await waitFor(() => {
|
|
199
|
-
expect(screen.getByRole("button", {name: "4"})).toBeVisible();
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
userEvent.click(screen.getByRole("button", {name: "4"}));
|
|
203
|
-
userEvent.click(screen.getByRole("button", {name: "2"}));
|
|
204
|
-
userEvent.click(screen.getByRole("button", {name: "Percent"}));
|
|
205
|
-
|
|
206
|
-
// MathQuill is problematic,
|
|
207
|
-
// this is how to get the value of the input directly from MQ
|
|
208
|
-
const mathquillInstance = MQ(
|
|
209
|
-
// eslint-disable-next-line testing-library/no-node-access
|
|
210
|
-
document.getElementsByClassName(
|
|
211
|
-
"mq-editable-field",
|
|
212
|
-
)[0] as HTMLElement,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
expect(mathquillInstance?.latex()).toBe("42\\%");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("handles fractions correctly in expression", async () => {
|
|
219
|
-
const keypadConfiguration = {
|
|
220
|
-
keypadType: KeypadType.EXPRESSION,
|
|
221
|
-
};
|
|
222
|
-
render(
|
|
223
|
-
<ConnectedMathInput keypadConfiguration={keypadConfiguration} />,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
const input = screen.getByLabelText(
|
|
227
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
fireEvent.touchStart(input);
|
|
231
|
-
|
|
232
|
-
await waitFor(() => {
|
|
233
|
-
expect(screen.getByRole("button", {name: "4"})).toBeVisible();
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
userEvent.click(screen.getByRole("button", {name: "1"}));
|
|
237
|
-
userEvent.click(
|
|
238
|
-
screen.getByRole("button", {
|
|
239
|
-
name: "Fraction, excluding the current expression",
|
|
240
|
-
}),
|
|
241
|
-
);
|
|
242
|
-
userEvent.click(screen.getByRole("button", {name: "4"}));
|
|
243
|
-
userEvent.click(
|
|
244
|
-
screen.getByRole("button", {
|
|
245
|
-
name: "Navigate right out of the numerator and into the denominator",
|
|
246
|
-
}),
|
|
247
|
-
);
|
|
248
|
-
userEvent.click(screen.getByRole("button", {name: "2"}));
|
|
249
|
-
|
|
250
|
-
// MathQuill is problematic,
|
|
251
|
-
// this is how to get the value of the input directly from MQ
|
|
252
|
-
const mathquillInstance = MQ(
|
|
253
|
-
// eslint-disable-next-line testing-library/no-node-access
|
|
254
|
-
document.getElementsByClassName(
|
|
255
|
-
"mq-editable-field",
|
|
256
|
-
)[0] as HTMLElement,
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
expect(mathquillInstance?.latex()).toBe("1\\frac{4}{2}");
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it("handles fractions correctly in fraction", async () => {
|
|
263
|
-
render(<ConnectedMathInput />);
|
|
264
|
-
|
|
265
|
-
const input = screen.getByLabelText(
|
|
266
|
-
"Math input box Tap with one or two fingers to open keyboard",
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
fireEvent.touchStart(input);
|
|
270
|
-
|
|
271
|
-
await waitFor(() => {
|
|
272
|
-
expect(screen.getByRole("button", {name: "4"})).toBeVisible();
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
userEvent.click(screen.getByRole("button", {name: "1"}));
|
|
276
|
-
userEvent.click(
|
|
277
|
-
screen.getByRole("button", {
|
|
278
|
-
name: "Fraction, excluding the current expression",
|
|
279
|
-
}),
|
|
280
|
-
);
|
|
281
|
-
userEvent.click(screen.getByRole("button", {name: "4"}));
|
|
282
|
-
userEvent.click(
|
|
283
|
-
screen.getByRole("button", {
|
|
284
|
-
name: "Navigate right out of the numerator and into the denominator",
|
|
285
|
-
}),
|
|
286
|
-
);
|
|
287
|
-
userEvent.click(screen.getByRole("button", {name: "2"}));
|
|
288
|
-
|
|
289
|
-
// MathQuill is problematic,
|
|
290
|
-
// this is how to get the value of the input directly from MQ
|
|
291
|
-
const mathquillInstance = MQ(
|
|
292
|
-
// eslint-disable-next-line testing-library/no-node-access
|
|
293
|
-
document.getElementsByClassName(
|
|
294
|
-
"mq-editable-field",
|
|
295
|
-
)[0] as HTMLElement,
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
expect(mathquillInstance?.latex()).toBe("1\\frac{4}{2}");
|
|
299
|
-
});
|
|
300
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aphrodite doesn't play well with CSSTransition from react-transition-group,
|
|
3
|
-
* which assumes that you have CSS classes and it can combine them arbitrarily.
|
|
4
|
-
*
|
|
5
|
-
* There are also some issue with react-transition-group that make it difficult
|
|
6
|
-
* to work. Even if the CSS classes are defined ahead of time it makes no
|
|
7
|
-
* guarantee that the start style will be applied by the browser before the
|
|
8
|
-
* active style is applied. This can cause the first time a transition runs to
|
|
9
|
-
* fail.
|
|
10
|
-
*
|
|
11
|
-
* AphroditeCSSTransitionGroup provides a wrapper around TransitionGroup to
|
|
12
|
-
* address these issues.
|
|
13
|
-
*
|
|
14
|
-
* There are three types of transitions:
|
|
15
|
-
* - appear: the time the child is added to the render tree
|
|
16
|
-
* - enter: whenever the child is added to the render tree after "appear". If
|
|
17
|
-
* no "appear" transition is specified then the "enter" transition will also
|
|
18
|
-
* be used for the first time the child is added to the render tree.
|
|
19
|
-
* - leave: whenever the child is removed from the render tree
|
|
20
|
-
*
|
|
21
|
-
* Each transition type has two states:
|
|
22
|
-
* - base: e.g. css(enter)
|
|
23
|
-
* - active: e.g. css(enter, enterActive)
|
|
24
|
-
*
|
|
25
|
-
* If "done" styles are not provided, the "active" style will remain on the
|
|
26
|
-
* component after the animation has completed.
|
|
27
|
-
*
|
|
28
|
-
* Usage: TBD
|
|
29
|
-
*
|
|
30
|
-
* Limitations:
|
|
31
|
-
* - This component only supports a single child whereas TransitionGroup supports
|
|
32
|
-
* multiple children.
|
|
33
|
-
* - We ignore inline styles that are provided as part of AnimationStyles.
|
|
34
|
-
*
|
|
35
|
-
* TODOs:
|
|
36
|
-
* - (FEI-3211): Change the API for AphroditeCSSTransitionGroup so that it makes
|
|
37
|
-
* bad states impossible.
|
|
38
|
-
*/
|
|
39
|
-
import * as React from "react";
|
|
40
|
-
import {TransitionGroup} from "react-transition-group";
|
|
41
|
-
|
|
42
|
-
import TransitionChild from "./transition-child";
|
|
43
|
-
|
|
44
|
-
import type {AnimationStyles} from "./types";
|
|
45
|
-
|
|
46
|
-
type Props = {
|
|
47
|
-
// If a function is provided, that function will be called to retrieve the
|
|
48
|
-
// current set of animation styles to be used when animating the children.
|
|
49
|
-
transitionStyle: AnimationStyles | (() => AnimationStyles);
|
|
50
|
-
transitionAppearTimeout?: number;
|
|
51
|
-
transitionEnterTimeout?: number;
|
|
52
|
-
transitionLeaveTimeout?: number;
|
|
53
|
-
children?: React.ReactNode;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
class AphroditeCSSTransitionGroup extends React.Component<Props> {
|
|
57
|
-
render(): React.ReactNode {
|
|
58
|
-
const {children} = this.props;
|
|
59
|
-
return (
|
|
60
|
-
// `component={null}` prevents wrapping each child with a <div>
|
|
61
|
-
// which can muck with certain layouts.
|
|
62
|
-
<TransitionGroup component={null}>
|
|
63
|
-
{React.Children.map(children, (child) => (
|
|
64
|
-
<TransitionChild
|
|
65
|
-
transitionStyles={this.props.transitionStyle}
|
|
66
|
-
appearTimeout={this.props.transitionAppearTimeout}
|
|
67
|
-
enterTimeout={this.props.transitionEnterTimeout}
|
|
68
|
-
leaveTimeout={this.props.transitionLeaveTimeout}
|
|
69
|
-
>
|
|
70
|
-
{child}
|
|
71
|
-
</TransitionChild>
|
|
72
|
-
))}
|
|
73
|
-
</TransitionGroup>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export default AphroditeCSSTransitionGroup;
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import {withActionScheduler} from "@khanacademy/wonder-blocks-timing";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import ReactDOM from "react-dom";
|
|
4
|
-
|
|
5
|
-
import {processStyleType} from "./util";
|
|
6
|
-
|
|
7
|
-
import type {AnimationStyles} from "./types";
|
|
8
|
-
import type {WithActionSchedulerProps} from "@khanacademy/wonder-blocks-timing";
|
|
9
|
-
|
|
10
|
-
type ChildProps = {
|
|
11
|
-
transitionStyles: AnimationStyles | (() => AnimationStyles);
|
|
12
|
-
appearTimeout?: number; // default appearTimeout to be the same as enterTimeout
|
|
13
|
-
enterTimeout?: number;
|
|
14
|
-
leaveTimeout?: number;
|
|
15
|
-
children: React.ReactNode;
|
|
16
|
-
in?: boolean; // provided by TransitionGroup
|
|
17
|
-
} & WithActionSchedulerProps;
|
|
18
|
-
|
|
19
|
-
type ChildState = {
|
|
20
|
-
// Keeps track of whether we should render our children or not.
|
|
21
|
-
status: "mounted" | "unmounted";
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
class TransitionChild extends React.Component<ChildProps, ChildState> {
|
|
25
|
-
// Each 2-tuple in the queue represents two classnames: one to remove and
|
|
26
|
-
// one to add (in that order).
|
|
27
|
-
classNameQueue: Array<[string, string]>;
|
|
28
|
-
// We keep track of all of the current applied classes so that we can remove
|
|
29
|
-
// them before a new transition starts in the case of the current transition
|
|
30
|
-
// being interrupted.
|
|
31
|
-
appliedClassNames: Set<string>;
|
|
32
|
-
_isMounted = false;
|
|
33
|
-
|
|
34
|
-
// The use of getDerivedStateFromProps here is to avoid an extra call to
|
|
35
|
-
// setState if the component re-enters. This can happen if TransitionGroup
|
|
36
|
-
// sets `in` from `false` to `true`.
|
|
37
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
38
|
-
static getDerivedStateFromProps(
|
|
39
|
-
{in: nextIn}: ChildProps,
|
|
40
|
-
prevState: ChildState,
|
|
41
|
-
): Partial<ChildState> | null {
|
|
42
|
-
if (nextIn && prevState.status === "unmounted") {
|
|
43
|
-
return {status: "mounted"};
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
constructor(props: ChildProps) {
|
|
49
|
-
super(props);
|
|
50
|
-
|
|
51
|
-
this._isMounted = false;
|
|
52
|
-
this.classNameQueue = [];
|
|
53
|
-
this.appliedClassNames = new Set();
|
|
54
|
-
|
|
55
|
-
this.state = {
|
|
56
|
-
status: "mounted",
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
componentDidMount() {
|
|
61
|
-
this._isMounted = true;
|
|
62
|
-
|
|
63
|
-
if (typeof this.props.appearTimeout === "number") {
|
|
64
|
-
this.transition("appear", this.props.appearTimeout);
|
|
65
|
-
} else {
|
|
66
|
-
this.transition("enter", this.props.enterTimeout);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
componentDidUpdate(oldProps: ChildProps, oldState: ChildState) {
|
|
71
|
-
if (oldProps.in && !this.props.in) {
|
|
72
|
-
this.transition("leave", this.props.leaveTimeout);
|
|
73
|
-
} else if (!oldProps.in && this.props.in) {
|
|
74
|
-
this.transition("enter", this.props.enterTimeout);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (oldState.status !== "mounted" && this.state.status === "mounted") {
|
|
78
|
-
// Remove the node from the DOM
|
|
79
|
-
// eslint-disable-next-line react/no-did-update-set-state
|
|
80
|
-
this.setState({status: "unmounted"});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// NOTE: This will only get called when the parent TransitionGroup becomes
|
|
85
|
-
// unmounted. This is because that component clones all of its children and
|
|
86
|
-
// keeps them around so that they can be animated when leaving and also so
|
|
87
|
-
// that the can be animated when re-rentering if that occurs.
|
|
88
|
-
componentWillUnmount() {
|
|
89
|
-
this._isMounted = false;
|
|
90
|
-
this.props.schedule.clearAll();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
removeAllClasses(node: Element) {
|
|
94
|
-
for (const className of this.appliedClassNames) {
|
|
95
|
-
this.removeClass(node, className);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
addClass = (elem: Element, className: string): void => {
|
|
100
|
-
if (className) {
|
|
101
|
-
elem.classList.add(className);
|
|
102
|
-
this.appliedClassNames.add(className);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
removeClass = (elem: Element, className: string): void => {
|
|
107
|
-
if (className) {
|
|
108
|
-
elem.classList.remove(className);
|
|
109
|
-
this.appliedClassNames.delete(className);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
transition(
|
|
114
|
-
animationType: "appear" | "enter" | "leave",
|
|
115
|
-
duration?: number | null,
|
|
116
|
-
) {
|
|
117
|
-
const node = ReactDOM.findDOMNode(this);
|
|
118
|
-
|
|
119
|
-
if (!(node instanceof Element)) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Remove any classes from previous transitions.
|
|
124
|
-
this.removeAllClasses(node);
|
|
125
|
-
|
|
126
|
-
// A previous transition may still be in progress so clear its timers.
|
|
127
|
-
this.props.schedule.clearAll();
|
|
128
|
-
|
|
129
|
-
const transitionStyles =
|
|
130
|
-
typeof this.props.transitionStyles === "function"
|
|
131
|
-
? this.props.transitionStyles()
|
|
132
|
-
: this.props.transitionStyles;
|
|
133
|
-
|
|
134
|
-
const {className} = processStyleType(transitionStyles[animationType]);
|
|
135
|
-
const {className: activeClassName} = processStyleType([
|
|
136
|
-
transitionStyles[animationType],
|
|
137
|
-
transitionStyles[animationType + "Active"],
|
|
138
|
-
]);
|
|
139
|
-
|
|
140
|
-
// Put the node in the starting position.
|
|
141
|
-
this.addClass(node, className);
|
|
142
|
-
|
|
143
|
-
// Queue the component to show the "active" style.
|
|
144
|
-
this.queueClass(className, activeClassName);
|
|
145
|
-
|
|
146
|
-
// Unmount the children after the 'leave' transition has completed.
|
|
147
|
-
if (animationType === "leave") {
|
|
148
|
-
this.props.schedule.timeout(() => {
|
|
149
|
-
if (this._isMounted) {
|
|
150
|
-
this.setState({status: "unmounted"});
|
|
151
|
-
}
|
|
152
|
-
}, duration || 0);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
queueClass(removeClassName: string, addClassName: string) {
|
|
157
|
-
this.classNameQueue.push([removeClassName, addClassName]);
|
|
158
|
-
// Queue operation for after the next paint.
|
|
159
|
-
this.props.schedule.timeout(this.flushClassNameQueue, 0);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
flushClassNameQueue = () => {
|
|
163
|
-
if (this._isMounted) {
|
|
164
|
-
const node = ReactDOM.findDOMNode(this);
|
|
165
|
-
if (node instanceof Element) {
|
|
166
|
-
this.classNameQueue.forEach(
|
|
167
|
-
([removeClassName, addClassName]: [any, any]) => {
|
|
168
|
-
// Remove the old class before adding a new class just
|
|
169
|
-
// in case the new class is the same as the old one.
|
|
170
|
-
this.removeClass(node, removeClassName);
|
|
171
|
-
this.addClass(node, addClassName);
|
|
172
|
-
},
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Remove all items in the Array.
|
|
178
|
-
this.classNameQueue.length = 0;
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
render(): React.ReactNode {
|
|
182
|
-
const {status} = this.state;
|
|
183
|
-
|
|
184
|
-
if (status === "unmounted") {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return this.props.children;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export default withActionScheduler(TransitionChild);
|