@khanacademy/math-input 4.1.0 → 4.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/components/input/math-wrapper.d.ts +1 -1
  3. package/dist/components/input/math-wrapper.js.flow +1 -1
  4. package/dist/components/input/mathquill-instance.d.ts +14 -3
  5. package/dist/components/input/mathquill-instance.js.flow +18 -3
  6. package/dist/components/input/mathquill-types.d.ts +28 -6
  7. package/dist/components/input/mathquill-types.js.flow +31 -7
  8. package/dist/components/key-handlers/handle-arrow.d.ts +3 -0
  9. package/dist/components/{input/key-handlers → key-handlers}/handle-arrow.js.flow +2 -2
  10. package/dist/components/{input/key-handlers → key-handlers}/handle-backspace.d.ts +1 -1
  11. package/dist/components/{input/key-handlers → key-handlers}/handle-backspace.js.flow +1 -1
  12. package/dist/components/key-handlers/handle-exponent.d.ts +3 -0
  13. package/dist/components/{input/key-handlers → key-handlers}/handle-exponent.js.flow +2 -2
  14. package/dist/components/{input/key-handlers → key-handlers}/handle-jump-out.d.ts +2 -2
  15. package/dist/components/{input/key-handlers → key-handlers}/handle-jump-out.js.flow +2 -2
  16. package/dist/components/key-handlers/key-translator.d.ts +4 -0
  17. package/dist/components/{key-translator.js.flow → key-handlers/key-translator.js.flow} +3 -3
  18. package/dist/components/keypad/geometry-page/index.d.ts +2 -1
  19. package/dist/components/keypad/geometry-page/index.js.flow +2 -1
  20. package/dist/components/keypad/index.d.ts +2 -1
  21. package/dist/components/keypad/index.js.flow +2 -1
  22. package/dist/components/keypad/keypad-page-items.d.ts +8 -16
  23. package/dist/components/keypad/keypad-page-items.js.flow +11 -16
  24. package/dist/components/keypad/numbers-page/index.d.ts +2 -1
  25. package/dist/components/keypad/numbers-page/index.js.flow +2 -1
  26. package/dist/components/keypad/operators-page/advanced-relations-buttons.d.ts +2 -1
  27. package/dist/components/keypad/operators-page/advanced-relations-buttons.js.flow +2 -1
  28. package/dist/components/keypad/operators-page/basic-relations-buttons.d.ts +2 -1
  29. package/dist/components/keypad/operators-page/basic-relations-buttons.js.flow +2 -1
  30. package/dist/components/keypad/operators-page/index.d.ts +2 -1
  31. package/dist/components/keypad/operators-page/index.js.flow +2 -1
  32. package/dist/components/keypad/operators-page/logarithms-buttons.d.ts +2 -1
  33. package/dist/components/keypad/operators-page/logarithms-buttons.js.flow +2 -1
  34. package/dist/components/keypad/operators-page/pre-algebra-buttons.d.ts +2 -1
  35. package/dist/components/keypad/operators-page/pre-algebra-buttons.js.flow +2 -1
  36. package/dist/es/index.js +395 -366
  37. package/dist/es/index.js.map +1 -1
  38. package/dist/index.d.ts +3 -1
  39. package/dist/index.js +398 -367
  40. package/dist/index.js.flow +6 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/types.d.ts +1 -0
  43. package/dist/types.js.flow +1 -0
  44. package/package.json +1 -1
  45. package/src/components/input/math-input.tsx +10 -14
  46. package/src/components/input/math-wrapper.ts +23 -49
  47. package/src/components/input/mathquill-helpers.ts +11 -11
  48. package/src/components/input/mathquill-instance.ts +57 -2
  49. package/src/components/input/mathquill-types.ts +37 -7
  50. package/src/components/{input/key-handlers → key-handlers}/handle-arrow.ts +6 -6
  51. package/src/components/{input/key-handlers → key-handlers}/handle-backspace.ts +19 -17
  52. package/src/components/{input/key-handlers → key-handlers}/handle-exponent.ts +8 -5
  53. package/src/components/{input/key-handlers → key-handlers}/handle-jump-out.ts +15 -10
  54. package/src/components/{key-translator.ts → key-handlers/key-translator.ts} +43 -28
  55. package/src/components/keypad/__tests__/Button.test.tsx +51 -0
  56. package/src/components/keypad/__tests__/keypad-button.test.tsx +41 -0
  57. package/src/components/keypad/__tests__/keypad-v2-mathquill.test.tsx +231 -0
  58. package/src/components/keypad/__tests__/keypad-v2.cypress.ts +46 -0
  59. package/src/components/keypad/geometry-page/index.tsx +2 -1
  60. package/src/components/keypad/index.tsx +2 -1
  61. package/src/components/keypad/keypad-mathquill.stories.tsx +15 -23
  62. package/src/components/keypad/keypad-page-items.tsx +10 -19
  63. package/src/components/keypad/numbers-page/index.tsx +2 -1
  64. package/src/components/keypad/operators-page/advanced-relations-buttons.tsx +2 -1
  65. package/src/components/keypad/operators-page/basic-relations-buttons.tsx +2 -1
  66. package/src/components/keypad/operators-page/index.tsx +2 -1
  67. package/src/components/keypad/operators-page/logarithms-buttons.tsx +2 -1
  68. package/src/components/keypad/operators-page/pre-algebra-buttons.tsx +2 -1
  69. package/src/index.ts +6 -1
  70. package/src/types.ts +2 -0
  71. package/tsconfig-build.json +1 -2
  72. package/tsconfig-build.tsbuildinfo +1 -1
  73. package/dist/components/input/__tests__/test-math-wrapper.d.ts +0 -8
  74. package/dist/components/input/__tests__/test-math-wrapper.js.flow +0 -14
  75. package/dist/components/input/key-handlers/handle-arrow.d.ts +0 -3
  76. package/dist/components/input/key-handlers/handle-exponent.d.ts +0 -3
  77. package/dist/components/key-translator.d.ts +0 -4
@@ -1,12 +1,15 @@
1
- import Key from "../data/keys";
2
- import {DecimalSeparator} from "../enums";
3
- import {decimalSeparator} from "../utils";
4
-
5
- import MQ from "./input/mathquill-instance";
1
+ import Key from "../../data/keys";
2
+ import {DecimalSeparator} from "../../enums";
3
+ import {decimalSeparator} from "../../utils";
4
+ import {mathQuillInstance} from "../input/mathquill-instance";
6
5
  import {
7
6
  MathFieldInterface,
8
- MathQuillUpdaterCallback,
9
- } from "./input/mathquill-types";
7
+ MathFieldUpdaterCallback,
8
+ } from "../input/mathquill-types";
9
+
10
+ import handleArrow from "./handle-arrow";
11
+ import handleExponent from "./handle-exponent";
12
+ import handleJumpOut from "./handle-jump-out";
10
13
 
11
14
  enum ActionType {
12
15
  WRITE = "write",
@@ -20,7 +23,7 @@ const decimalSymbol = decimalSeparator === DecimalSeparator.COMMA ? "," : ".";
20
23
  function buildGenericCallback(
21
24
  str: string,
22
25
  type: ActionType = ActionType.WRITE,
23
- ): MathQuillUpdaterCallback {
26
+ ): MathFieldUpdaterCallback {
24
27
  return function (mathQuill: MathFieldInterface) {
25
28
  switch (type) {
26
29
  case ActionType.WRITE: {
@@ -39,20 +42,41 @@ function buildGenericCallback(
39
42
  };
40
43
  }
41
44
 
42
- const keyToMathquillMap: Record<Key, MathQuillUpdaterCallback> = {
45
+ function buildNormalFunctionCallback(command: string) {
46
+ return function (mathField: MathFieldInterface) {
47
+ mathField.write(`\\${command}\\left(\\right)`);
48
+ mathField.keystroke("Left");
49
+ };
50
+ }
51
+
52
+ const keyToMathquillMap: Record<Key, MathFieldUpdaterCallback> = {
53
+ EXP: handleExponent,
54
+ EXP_2: handleExponent,
55
+ EXP_3: handleExponent,
56
+
57
+ JUMP_OUT_PARENTHESES: handleJumpOut,
58
+ JUMP_OUT_EXPONENT: handleJumpOut,
59
+ JUMP_OUT_BASE: handleJumpOut,
60
+ JUMP_INTO_NUMERATOR: handleJumpOut,
61
+ JUMP_OUT_NUMERATOR: handleJumpOut,
62
+ JUMP_OUT_DENOMINATOR: handleJumpOut,
63
+
64
+ LEFT: handleArrow,
65
+ RIGHT: handleArrow,
66
+
67
+ LOG: buildNormalFunctionCallback("log"),
68
+ LN: buildNormalFunctionCallback("ln"),
69
+ SIN: buildNormalFunctionCallback("sin"),
70
+ COS: buildNormalFunctionCallback("cos"),
71
+ TAN: buildNormalFunctionCallback("tan"),
72
+
43
73
  CDOT: buildGenericCallback("\\cdot"),
44
- COS: buildGenericCallback("cos"),
45
74
  DECIMAL: buildGenericCallback(decimalSymbol),
46
75
  DIVIDE: buildGenericCallback("\\div"),
47
76
  EQUAL: buildGenericCallback("="),
48
- EXP: buildGenericCallback("^"),
49
- EXP_2: buildGenericCallback("^2"),
50
- EXP_3: buildGenericCallback("^3"),
51
77
  GEQ: buildGenericCallback("\\geq"),
52
78
  GT: buildGenericCallback(">"),
53
79
  LEQ: buildGenericCallback("\\leq"),
54
- LN: buildGenericCallback("\\ln"),
55
- LOG: buildGenericCallback("\\log"),
56
80
  LT: buildGenericCallback("<"),
57
81
  MINUS: buildGenericCallback("-"),
58
82
  NEGATIVE: buildGenericCallback("-"),
@@ -60,13 +84,12 @@ const keyToMathquillMap: Record<Key, MathQuillUpdaterCallback> = {
60
84
  PERCENT: buildGenericCallback("%"),
61
85
  PERIOD: buildGenericCallback("."),
62
86
  PLUS: buildGenericCallback("+"),
63
- SIN: buildGenericCallback("sin"),
64
- TAN: buildGenericCallback("tan"),
65
87
  TIMES: buildGenericCallback("\\times"),
66
88
 
67
89
  // The `FRAC_EXCLUSIVE` variant is handled manually, since we may need to do
68
90
  // some additional navigation depending on the cursor position.
69
91
  FRAC_INCLUSIVE: buildGenericCallback("/", ActionType.CMD),
92
+ FRAC: buildGenericCallback("\\frac", ActionType.CMD),
70
93
  LEFT_PAREN: buildGenericCallback("(", ActionType.CMD),
71
94
  RIGHT_PAREN: buildGenericCallback(")", ActionType.CMD),
72
95
  SQRT: buildGenericCallback("sqrt", ActionType.CMD),
@@ -75,6 +98,7 @@ const keyToMathquillMap: Record<Key, MathQuillUpdaterCallback> = {
75
98
  THETA: buildGenericCallback("theta", ActionType.CMD),
76
99
  RADICAL: buildGenericCallback("nthroot", ActionType.CMD),
77
100
 
101
+ BACKSPACE: buildGenericCallback("Backspace", ActionType.KEYSTROKE),
78
102
  UP: buildGenericCallback("Up", ActionType.KEYSTROKE),
79
103
  DOWN: buildGenericCallback("Down", ActionType.KEYSTROKE),
80
104
 
@@ -87,7 +111,8 @@ const keyToMathquillMap: Record<Key, MathQuillUpdaterCallback> = {
87
111
  const cursor = mathQuill.__controller.cursor;
88
112
  // If there's nothing to the left of the cursor, then we want to
89
113
  // leave the cursor to the left of the fraction after creating it.
90
- const shouldNavigateLeft = cursor[MQ.L] === ActionType.MQ_END;
114
+ const shouldNavigateLeft =
115
+ cursor[mathQuillInstance.L] === ActionType.MQ_END;
91
116
  mathQuill.cmd("\\frac");
92
117
  if (shouldNavigateLeft) {
93
118
  mathQuill.keystroke("Left");
@@ -128,17 +153,7 @@ const keyToMathquillMap: Record<Key, MathQuillUpdaterCallback> = {
128
153
 
129
154
  // These need to be overwritten by the consumer
130
155
  // if they're going to be used
131
- FRAC: () => {},
132
- RIGHT: () => {},
133
- LEFT: () => {},
134
- BACKSPACE: () => {},
135
156
  DISMISS: () => {},
136
- JUMP_OUT_PARENTHESES: () => {},
137
- JUMP_OUT_EXPONENT: () => {},
138
- JUMP_OUT_BASE: () => {},
139
- JUMP_INTO_NUMERATOR: () => {},
140
- JUMP_OUT_NUMERATOR: () => {},
141
- JUMP_OUT_DENOMINATOR: () => {},
142
157
  NOOP: () => {},
143
158
  MANY: () => {},
144
159
 
@@ -0,0 +1,51 @@
1
+ import {render, screen} from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import * as React from "react";
4
+ import "@testing-library/jest-dom";
5
+
6
+ import Button from "../button";
7
+
8
+ describe("<Button />", () => {
9
+ it("uses the aria label", () => {
10
+ // Arrange
11
+ render(
12
+ <Button onPress={() => {}} ariaLabel="Oranges">
13
+ <p />
14
+ </Button>,
15
+ );
16
+
17
+ // Assert
18
+ expect(
19
+ screen.getByRole("button", {name: "Oranges"}),
20
+ ).toBeInTheDocument();
21
+ });
22
+
23
+ it("handles callback on press", () => {
24
+ // Arrange
25
+ const mockPressCallback = jest.fn();
26
+ render(
27
+ <Button onPress={mockPressCallback} ariaLabel="Oranges">
28
+ <p />
29
+ </Button>,
30
+ );
31
+
32
+ // Act
33
+ userEvent.click(screen.getByRole("button", {name: "Oranges"}));
34
+
35
+ // Assert
36
+ expect(mockPressCallback).toHaveBeenCalled();
37
+ });
38
+
39
+ it("renders child", () => {
40
+ // Arrange
41
+ render(
42
+ <Button onPress={() => {}} ariaLabel="Test">
43
+ <img aria-label="child1" />
44
+ </Button>,
45
+ );
46
+
47
+ // Assert
48
+ expect(screen.getByRole("button", {name: "Test"})).toBeInTheDocument();
49
+ expect(screen.getByRole("img", {name: "child1"})).toBeInTheDocument();
50
+ });
51
+ });
@@ -0,0 +1,41 @@
1
+ import {render, screen} from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import * as React from "react";
4
+ import "@testing-library/jest-dom";
5
+
6
+ import Keys from "../../../data/key-configs";
7
+ import {KeypadButton} from "../keypad-page-items";
8
+
9
+ describe("<KeypadButton />", () => {
10
+ it("uses the aria label from the key config", () => {
11
+ // Arrange
12
+ render(
13
+ <KeypadButton
14
+ onClickKey={(config) => {}}
15
+ keyConfig={Keys.LEFT_PAREN}
16
+ />,
17
+ );
18
+
19
+ // Assert
20
+ expect(
21
+ screen.getByRole("button", {name: "Left parenthesis"}),
22
+ ).toBeInTheDocument();
23
+ });
24
+
25
+ it("handles onClickKey callback", () => {
26
+ // Arrange
27
+ const mockClickKeyCallback = jest.fn();
28
+ render(
29
+ <KeypadButton
30
+ onClickKey={mockClickKeyCallback}
31
+ keyConfig={Keys.LEFT_PAREN}
32
+ />,
33
+ );
34
+
35
+ // Act
36
+ userEvent.click(screen.getByRole("button", {name: "Left parenthesis"}));
37
+
38
+ // Assert
39
+ expect(mockClickKeyCallback).toHaveBeenCalledWith("LEFT_PAREN");
40
+ });
41
+ });
@@ -0,0 +1,231 @@
1
+ import Color from "@khanacademy/wonder-blocks-color";
2
+ import {Popover} from "@khanacademy/wonder-blocks-popover";
3
+ import {render, screen} from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+ import * as React from "react";
6
+
7
+ import "@testing-library/jest-dom";
8
+
9
+ import Key from "../../../data/keys";
10
+ import {createMathField} from "../../input/mathquill-instance";
11
+ import {MathFieldInterface} from "../../input/mathquill-types";
12
+ import keyTranslator from "../../key-handlers/key-translator";
13
+ import Keypad from "../index";
14
+
15
+ type Props = {
16
+ onChangeMathInput: (mathInputTex: string) => void;
17
+ };
18
+
19
+ function V2KeypadWithMathquill(props: Props) {
20
+ const mathFieldWrapperRef = React.useRef<HTMLDivElement>(null);
21
+ const [mathField, setMathField] = React.useState<MathFieldInterface>();
22
+
23
+ React.useEffect(() => {
24
+ if (!mathField && mathFieldWrapperRef.current) {
25
+ const mathFieldInstance = createMathField(
26
+ mathFieldWrapperRef.current,
27
+ (baseConfig) => {
28
+ return {
29
+ ...baseConfig,
30
+ handlers: {
31
+ edit: (mathField) => {
32
+ props.onChangeMathInput(mathField.latex());
33
+ },
34
+ },
35
+ };
36
+ },
37
+ );
38
+ setMathField(mathFieldInstance);
39
+ }
40
+ }, [mathField, props]);
41
+
42
+ function handleClickKey(key: Key) {
43
+ if (!mathField) {
44
+ return;
45
+ }
46
+
47
+ const mathFieldCallback = keyTranslator[key];
48
+ if (mathFieldCallback) {
49
+ mathFieldCallback(mathField, key);
50
+ }
51
+ }
52
+
53
+ return (
54
+ <div style={{maxWidth: "400px", margin: "2em"}}>
55
+ <Popover
56
+ content={
57
+ <div>
58
+ <Keypad
59
+ extraKeys={["a", "b", "c"]}
60
+ onClickKey={handleClickKey}
61
+ advancedRelations
62
+ basicRelations
63
+ divisionKey
64
+ logarithms
65
+ multiplicationDot
66
+ preAlgebra
67
+ trigonometry
68
+ />
69
+ </div>
70
+ }
71
+ dismissEnabled
72
+ opened
73
+ >
74
+ <div
75
+ style={{
76
+ width: "100%",
77
+ marginBottom: "1em",
78
+ border: `1px solid ${Color.offBlack16}`,
79
+ }}
80
+ ref={mathFieldWrapperRef}
81
+ />
82
+ </Popover>
83
+ </div>
84
+ );
85
+ }
86
+
87
+ describe("Keypad v2 with MathQuill", () => {
88
+ it("can write the Pythagorean theorem (simple)", () => {
89
+ // Arrange
90
+ const mockMathInputCallback = jest.fn();
91
+ render(
92
+ <V2KeypadWithMathquill onChangeMathInput={mockMathInputCallback} />,
93
+ );
94
+
95
+ // Act
96
+
97
+ // a^2
98
+ userEvent.click(screen.getByRole("button", {name: "Extras"}));
99
+ userEvent.click(screen.getByRole("button", {name: "a"}));
100
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
101
+ userEvent.click(screen.getByRole("button", {name: "Square"}));
102
+
103
+ // +
104
+ userEvent.click(screen.getByRole("button", {name: "Numbers"}));
105
+ userEvent.click(screen.getByRole("button", {name: "Plus"}));
106
+
107
+ // b^2 =
108
+ userEvent.click(screen.getByRole("button", {name: "Extras"}));
109
+ userEvent.click(screen.getByRole("button", {name: "b"}));
110
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
111
+ userEvent.click(screen.getByRole("button", {name: "Square"}));
112
+ userEvent.click(screen.getByRole("button", {name: "Equals sign"}));
113
+
114
+ // c^2
115
+ userEvent.click(screen.getByRole("button", {name: "Extras"}));
116
+ userEvent.click(screen.getByRole("button", {name: "c"}));
117
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
118
+ userEvent.click(screen.getByRole("button", {name: "Square"}));
119
+
120
+ // Assert
121
+ expect(mockMathInputCallback).toHaveBeenLastCalledWith("a^2+b^2=c^2");
122
+ });
123
+
124
+ it("can write the Pythagorean theorem (complex)", () => {
125
+ // Arrange
126
+ const mockMathInputCallback = jest.fn();
127
+ render(
128
+ <V2KeypadWithMathquill onChangeMathInput={mockMathInputCallback} />,
129
+ );
130
+
131
+ // Act
132
+
133
+ // c = /Square root
134
+ userEvent.click(screen.getByRole("button", {name: "Extras"}));
135
+ userEvent.click(screen.getByRole("button", {name: "c"}));
136
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
137
+ userEvent.click(screen.getByRole("button", {name: "Equals sign"}));
138
+ userEvent.click(screen.getByRole("button", {name: "Square root"}));
139
+
140
+ // a^2
141
+ userEvent.click(screen.getByRole("button", {name: "Extras"}));
142
+ userEvent.click(screen.getByRole("button", {name: "a"}));
143
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
144
+ userEvent.click(screen.getByRole("button", {name: "Square"}));
145
+
146
+ // +
147
+ userEvent.click(screen.getByRole("button", {name: "Numbers"}));
148
+ userEvent.click(screen.getByRole("button", {name: "Plus"}));
149
+
150
+ // b^2
151
+ userEvent.click(screen.getByRole("button", {name: "Extras"}));
152
+ userEvent.click(screen.getByRole("button", {name: "b"}));
153
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
154
+ userEvent.click(screen.getByRole("button", {name: "Square"}));
155
+
156
+ // Assert
157
+ expect(mockMathInputCallback).toHaveBeenLastCalledWith(
158
+ "c=\\sqrt{a^2+b^2}",
159
+ );
160
+ });
161
+
162
+ it("writes the Pythagorean theorem using typing/clicking together", () => {
163
+ // Arrange
164
+ const mockMathInputCallback = jest.fn();
165
+ render(
166
+ <V2KeypadWithMathquill onChangeMathInput={mockMathInputCallback} />,
167
+ );
168
+
169
+ // Act
170
+
171
+ // Argument is empty because mathquill generates textarea w/o label
172
+ userEvent.type(screen.getByRole("textbox"), "a");
173
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
174
+ userEvent.click(screen.getByRole("button", {name: "Square"}));
175
+
176
+ userEvent.type(screen.getByRole("textbox"), "+");
177
+
178
+ // b^2
179
+ userEvent.click(screen.getByRole("button", {name: "Extras"}));
180
+ userEvent.click(screen.getByRole("button", {name: "b"}));
181
+ userEvent.click(screen.getByRole("button", {name: "Operators"}));
182
+ userEvent.click(screen.getByRole("button", {name: "Square"}));
183
+ userEvent.type(screen.getByRole("textbox"), "=c^2");
184
+
185
+ // Assert
186
+ expect(mockMathInputCallback).toHaveBeenLastCalledWith("a^2+b^2=c^2");
187
+ });
188
+
189
+ it("deletes from the input using the backspace button", () => {
190
+ // Arrange
191
+ const mockMathInputCallback = jest.fn();
192
+ render(
193
+ <V2KeypadWithMathquill onChangeMathInput={mockMathInputCallback} />,
194
+ );
195
+
196
+ // Act
197
+ // Write `a^2+b^2=c^2` using the keypad
198
+ const buttonPressesForFormula = [
199
+ "Extras",
200
+ "a",
201
+ "Operators",
202
+ "Square",
203
+ "Numbers",
204
+ "Plus",
205
+ "Extras",
206
+ "b",
207
+ "Operators",
208
+ "Square",
209
+ "Equals sign",
210
+ "Extras",
211
+ "c",
212
+ "Operators",
213
+ "Square",
214
+ ];
215
+ buttonPressesForFormula.forEach((button) => {
216
+ userEvent.click(screen.getByRole("button", {name: button}));
217
+ });
218
+
219
+ // Assert
220
+ // make sure the formula was typed correctly
221
+ expect(mockMathInputCallback).toHaveBeenLastCalledWith("a^2+b^2=c^2");
222
+
223
+ userEvent.click(screen.getByRole("button", {name: "Numbers"}));
224
+ // delete: need 14 backspaces in MathQuill to delete `a^2+b^2=c^2`
225
+ for (let i = 0; i < 14; i++) {
226
+ userEvent.click(screen.getByRole("button", {name: "Delete"}));
227
+ }
228
+
229
+ expect(mockMathInputCallback).toHaveBeenLastCalledWith("");
230
+ });
231
+ });
@@ -0,0 +1,46 @@
1
+ import renderSingleKeypad from "../../../../../../testing/render-keypad-with-cypress";
2
+ import KeyConfigs from "../../../data/key-configs";
3
+
4
+ const tabs = [
5
+ {
6
+ name: "Operators",
7
+ specialButton: "EXP_2",
8
+ label: KeyConfigs["EXP_2"].ariaLabel,
9
+ },
10
+ {name: "Extras", specialButton: "PI", label: KeyConfigs["PI"].ariaLabel},
11
+
12
+ {
13
+ name: "Geometry",
14
+ specialButton: "COS",
15
+ label: KeyConfigs["COS"].ariaLabel,
16
+ },
17
+ {
18
+ name: "Numbers",
19
+ specialButton: "NUM_7",
20
+ label: KeyConfigs["NUM_7"].ariaLabel,
21
+ },
22
+ ];
23
+
24
+ describe("Keypad v2", () => {
25
+ tabs.forEach((tab) => {
26
+ it(`switches to the correct tab: ${tab.name}`, () => {
27
+ renderSingleKeypad((key) => {});
28
+
29
+ // currently clicking on the bottom left due to button re-rendering
30
+ // after mousedown but before mouseup (only in Cypress)
31
+ cy.get('[aria-label="' + tab.name + '"]').click("bottomLeft");
32
+ cy.get('[aria-label="' + tab.label + '"]').should("exist");
33
+ });
34
+
35
+ it(`calls ${tab.specialButton} key callback in ${tab.name} tab`, () => {
36
+ const onClickKeySpy = cy.spy().as("onClickKeySpy");
37
+ renderSingleKeypad(onClickKeySpy);
38
+ cy.get('[aria-label="' + tab.name + '"]').click();
39
+ cy.get('[aria-label="' + tab.label + '"]').click();
40
+ cy.get("@onClickKeySpy").should(
41
+ "have.been.calledOnceWithExactly",
42
+ tab.specialButton,
43
+ );
44
+ });
45
+ });
46
+ });
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
 
3
3
  import Keys from "../../../data/key-configs";
4
+ import {ClickKeyCallback} from "../../../types";
4
5
  import {
5
6
  KeypadPageContainer,
6
7
  SecondaryKeypadButton,
@@ -8,7 +9,7 @@ import {
8
9
  } from "../keypad-page-items";
9
10
 
10
11
  type Props = {
11
- onClickKey: (keyConfig: string) => void;
12
+ onClickKey: ClickKeyCallback;
12
13
  };
13
14
 
14
15
  export default class GeometryPage extends React.Component<Props> {
@@ -4,6 +4,7 @@ import {StyleSheet} from "aphrodite";
4
4
  import * as React from "react";
5
5
 
6
6
  import Key from "../../data/keys";
7
+ import {ClickKeyCallback} from "../../types";
7
8
  import Tabbar from "../tabbar/tabbar";
8
9
  import {TabbarItemType} from "../tabbar/types";
9
10
 
@@ -15,7 +16,7 @@ import OperatorsPage from "./operators-page";
15
16
  import {OperatorsButtonSets} from "./operators-page/types";
16
17
 
17
18
  export type Props = {
18
- onClickKey: (keyConfig: string) => void;
19
+ onClickKey: ClickKeyCallback;
19
20
  trigonometry?: boolean;
20
21
  extraKeys: ReadonlyArray<Key>;
21
22
  } & OperatorsButtonSets &
@@ -1,10 +1,11 @@
1
1
  import Color from "@khanacademy/wonder-blocks-color";
2
2
  import {Popover} from "@khanacademy/wonder-blocks-popover";
3
- import MathQuill from "mathquill";
4
3
  import * as React from "react";
5
4
 
6
5
  import Key from "../../data/keys";
7
- import keyTranslator from "../key-translator";
6
+ import {createMathField} from "../input/mathquill-instance";
7
+ import {MathFieldInterface} from "../input/mathquill-types";
8
+ import keyTranslator from "../key-handlers/key-translator";
8
9
 
9
10
  import Keypad from "./index";
10
11
 
@@ -12,36 +13,27 @@ export default {
12
13
  title: "v2 Keypad With Mathquill",
13
14
  };
14
15
 
15
- const mathQuillConfig = {
16
- autoCommands: "pi theta phi sqrt nthroot",
17
- charsThatBreakOutOfSupSub: "+-*/=<>≠≤≥",
18
- supSubsRequireOperand: true,
19
- spaceBehavesLikeTab: true,
20
- };
21
-
22
16
  export function V2KeypadWithMathquill() {
23
- const mathquillWrapperRef = React.useRef<HTMLDivElement>(null);
24
- const [mathQuill, setMathQuill] = React.useState<MathQuill>();
17
+ const mathFieldWrapperRef = React.useRef<HTMLDivElement>(null);
18
+ const [mathField, setMathField] = React.useState<MathFieldInterface>();
25
19
 
26
20
  React.useEffect(() => {
27
- if (!mathQuill && mathquillWrapperRef.current) {
28
- const MQ = MathQuill.getInterface(2);
29
- const mathQuillInstance = MQ.MathField(
30
- mathquillWrapperRef.current,
31
- mathQuillConfig,
21
+ if (!mathField && mathFieldWrapperRef.current) {
22
+ const mathFieldInstance = createMathField(
23
+ mathFieldWrapperRef.current,
32
24
  );
33
- setMathQuill(mathQuillInstance);
25
+ setMathField(mathFieldInstance);
34
26
  }
35
- }, [mathQuill]);
27
+ }, [mathField]);
36
28
 
37
29
  function handleClickKey(key: Key) {
38
- if (!mathQuill) {
30
+ if (!mathField) {
39
31
  return;
40
32
  }
41
33
 
42
- const mathQuillCallback = keyTranslator[key];
43
- if (mathQuillCallback) {
44
- mathQuillCallback(mathQuill, key);
34
+ const mathFieldCallback = keyTranslator[key];
35
+ if (mathFieldCallback) {
36
+ mathFieldCallback(mathField, key);
45
37
  } else {
46
38
  // eslint-disable-next-line no-console
47
39
  console.warn(`No translation to Mathquill for: ${key}`);
@@ -74,7 +66,7 @@ export function V2KeypadWithMathquill() {
74
66
  marginBottom: "1em",
75
67
  border: `1px solid ${Color.offBlack16}`,
76
68
  }}
77
- ref={mathquillWrapperRef}
69
+ ref={mathFieldWrapperRef}
78
70
  />
79
71
  </Popover>
80
72
  </div>
@@ -1,7 +1,7 @@
1
1
  import {View} from "@khanacademy/wonder-blocks-core";
2
2
  import * as React from "react";
3
3
 
4
- import {KeyConfig} from "../../types";
4
+ import {KeyConfig, ClickKeyCallback} from "../../types";
5
5
 
6
6
  import Button from "./button";
7
7
  import ButtonAsset from "./button-assets";
@@ -30,11 +30,14 @@ export const KeypadPageContainer = ({
30
30
  </View>
31
31
  );
32
32
 
33
- export type KeypadButtonProps = {
33
+ type SharedButtonProps = {
34
34
  keyConfig: KeyConfig;
35
- tintColor?: string;
36
35
  style?: StyleType;
37
- onClickKey: (keyConfig: string) => void;
36
+ onClickKey: ClickKeyCallback;
37
+ };
38
+
39
+ export type KeypadButtonProps = SharedButtonProps & {
40
+ tintColor?: string;
38
41
  };
39
42
 
40
43
  export const KeypadButton = ({
@@ -47,23 +50,17 @@ export const KeypadButton = ({
47
50
  onPress={() => onClickKey(keyConfig.id)}
48
51
  tintColor={tintColor}
49
52
  style={style}
50
- ariaLabel={keyConfig.id}
53
+ ariaLabel={keyConfig.ariaLabel}
51
54
  >
52
55
  <ButtonAsset id={keyConfig.id} />
53
56
  </Button>
54
57
  );
55
58
 
56
- type SecondaryKeypadButtonProps = {
57
- keyConfig: KeyConfig;
58
- style?: StyleType;
59
- onClickKey: (keyConfig: string) => void;
60
- };
61
-
62
59
  export const SecondaryKeypadButton = ({
63
60
  keyConfig,
64
61
  onClickKey,
65
62
  style,
66
- }: SecondaryKeypadButtonProps): React.ReactElement => (
63
+ }: SharedButtonProps): React.ReactElement => (
67
64
  <KeypadButton
68
65
  keyConfig={keyConfig}
69
66
  onClickKey={onClickKey}
@@ -72,17 +69,11 @@ export const SecondaryKeypadButton = ({
72
69
  />
73
70
  );
74
71
 
75
- type KeypadActionButtonProps = {
76
- keyConfig: KeyConfig;
77
- style?: StyleType;
78
- onClickKey: (keyConfig: string) => void;
79
- };
80
-
81
72
  export const KeypadActionButton = ({
82
73
  keyConfig,
83
74
  onClickKey,
84
75
  style,
85
- }: KeypadActionButtonProps): React.ReactElement => (
76
+ }: SharedButtonProps): React.ReactElement => (
86
77
  <KeypadButton
87
78
  keyConfig={keyConfig}
88
79
  onClickKey={onClickKey}