@khanacademy/perseus 6.2.2 → 6.3.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/dist/strings.js CHANGED
@@ -52,6 +52,8 @@ module.exports = [
52
52
  i18n._("Correct (but you crossed it out)", {}),
53
53
  i18n._("Correct (selected)", {}),
54
54
  i18n._("Correct!", {}),
55
+ i18n._("Cosine", {}),
56
+ i18n._("Cube root", {}),
55
57
  i18n._("Current", {}),
56
58
  // I18N: As in the command, "Dilate the polygon"
57
59
  i18n._("Dilate", {}),
@@ -59,13 +61,19 @@ module.exports = [
59
61
  i18n._("Dilation", {}),
60
62
  i18n.$_("Dilation about %(point)s by %(scale)s", {}),
61
63
  i18n.$_("Dilation of scale %(scale)s about %(point)s", {}),
64
+ i18n._("Divide", {}),
62
65
  i18n._("Drag handles to make graph", {}),
63
66
  i18n._("End of reading passage.", {}),
64
67
  i18n._("entered in an invalid answer.", {}),
68
+ i18n._("Equals sign", {}),
65
69
  i18n.$_("Error rendering: %(error)s", {}),
66
70
  i18n._("Explain", {}),
71
+ i18n._("Exponent", {}),
67
72
  i18n._("False", {}),
73
+ i18n._("Fraction, excluding the current expression", {}),
68
74
  i18n._("Get another hint", {}),
75
+ i18n._("Greater than or equal to sign", {}),
76
+ i18n._("Greater than sign", {}),
69
77
  i18n._("Hide explanation", {}),
70
78
  i18n._("Hint #%(pos)s", {}),
71
79
  i18n._("Hints", {}),
@@ -77,25 +85,36 @@ module.exports = [
77
85
  i18n._("It looks like you left something blank or ", {}),
78
86
  i18n._("Keep trying", {}),
79
87
  i18n._("Khan Academy video wrapper", {}),
88
+ i18n._("Less than or equal to sign", {}),
89
+ i18n._("Less than sign", {}),
80
90
  // I18N: a label next to a reading passage to denote the line number
81
91
  i18n._("Line ", {}),
82
92
  i18n.$_("line %(lineNumber)s", {}),
83
93
  i18n.$_("lines %(lineRange)s", {}),
84
94
  i18n._("Loading...", {}),
95
+ i18n._("Logarithm with base 10", {}),
96
+ i18n._("Logarithm with custom base", {}),
85
97
  i18n._("Make circle filled", {}),
86
98
  i18n._("Make circle open", {}),
87
99
  i18n._("Make sure you fill in all cells in the matrix.", {}),
88
100
  i18n._("Make sure you select something for every row.", {}),
101
+ i18n._("Minus", {}),
89
102
  i18n.ngettext("Moves: %(num)s","Moves: %(num)s", {}),
103
+ i18n._("Multiply", {}),
104
+ i18n._("Natural logarithm", {}),
90
105
  i18n._("Next question", {}),
91
106
  i18n._("No", {}),
92
107
  i18n._("None of the above", {}),
108
+ i18n._("Not-equals sign", {}),
93
109
  i18n._("Note: For this question, the shape will not move.", {}),
94
110
  i18n._("Number of divisions:", {}),
95
111
  i18n._("Number of trials:", {}),
96
112
  i18n._("Parameter", {}),
113
+ i18n._("Phi", {}),
114
+ i18n._("Pi", {}),
97
115
  i18n._("Please choose the correct number of answers.", {}),
98
116
  i18n.$_("Please make sure the number of divisions is in the range %(divRangeString)s.", {}),
117
+ i18n._("Plus", {}),
99
118
  i18n._("Reaction arrow pointing to the right.", {}),
100
119
  // I18N: As in the command, "Reflect the polygon"
101
120
  i18n._("Reflect", {}),
@@ -113,15 +132,19 @@ module.exports = [
113
132
  i18n._("Run simulation", {}),
114
133
  i18n._("Sample size:", {}),
115
134
  i18n._("See video transcript", {}),
135
+ i18n._("Sine", {}),
116
136
  i18n._("Skip to %(title)s", {}),
117
137
  i18n._("Something went wrong.", {}),
118
138
  i18n._("Sorry, I don't understand that!", {}),
139
+ i18n._("Square root", {}),
119
140
  i18n._("Switch direction", {}),
141
+ i18n._("Tangent", {}),
120
142
  i18n._("Tap each dot on the image to select all answers that apply.", {}),
121
143
  i18n._("Tap each dot on the image to select an answer.", {}),
122
144
  i18n._("Tap to add points", {}),
123
145
  i18n._("That answer is numerically incorrect.", {}),
124
146
  i18n._("The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.", {}),
147
+ i18n._("Theta", {}),
125
148
  i18n._("This controls the proportion or percentage that will be used in your simulation.", {}),
126
149
  i18n._("This controls the sample size that will be used in your simulation. For example, if you set this to 100, then for each trial, responses from 100 participants will be simulated.", {}),
127
150
  i18n._("This is the number of trials used in the simulation. For example, if set to 50, then the survey will be conducted 50 times.", {}),
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Core Perseus API (includes renderers and widgets)",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "6.2.2",
6
+ "version": "6.3.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -23,11 +23,10 @@
23
23
  "dependencies": {
24
24
  "@khanacademy/kas": "^0.3.1",
25
25
  "@khanacademy/kmath": "^0.1.1",
26
- "@khanacademy/math-input": "^4.1.1",
26
+ "@khanacademy/math-input": "^4.2.0",
27
27
  "@khanacademy/perseus-linter": "^0.3.1",
28
28
  "@khanacademy/pure-markdown": "^0.2.3",
29
- "@khanacademy/simple-markdown": "^0.9.3",
30
- "mathquill": "git+https://git@github.com/Khan/mathquill.git#a9ae54e057c5c1acc8244a5627acbff29901d992"
29
+ "@khanacademy/simple-markdown": "^0.9.3"
31
30
  },
32
31
  "devDependencies": {
33
32
  "@khanacademy/wonder-blocks-button": "^4.0.8",
@@ -92,4 +91,4 @@
92
91
  "underscore": "^1.4.4"
93
92
  },
94
93
  "keywords": []
95
- }
94
+ }
@@ -0,0 +1,60 @@
1
+ import {render, screen} from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import * as React from "react";
4
+
5
+ import "@testing-library/jest-dom/extend-expect";
6
+ import {testDependencies} from "../../../../../testing/test-dependencies";
7
+ import * as Dependencies from "../../dependencies";
8
+ import MathInput from "../math-input";
9
+
10
+ describe("Perseus' MathInput", () => {
11
+ beforeEach(() => {
12
+ jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
13
+ testDependencies,
14
+ );
15
+ });
16
+
17
+ it("renders", () => {
18
+ // Assemble
19
+ render(
20
+ <MathInput
21
+ onChange={() => {}}
22
+ buttonSets={["basic"]}
23
+ labelText="test"
24
+ />,
25
+ );
26
+
27
+ // Assert
28
+ expect(screen.getByLabelText("test")).toBeInTheDocument();
29
+ });
30
+
31
+ it("is possible to type in the input", () => {
32
+ // Assemble
33
+ const mockOnChange = jest.fn();
34
+ render(<MathInput onChange={mockOnChange} buttonSets={["basic"]} />);
35
+
36
+ // Act
37
+ userEvent.type(screen.getByRole("textbox"), "12345");
38
+
39
+ // Assert
40
+ expect(mockOnChange).toHaveBeenLastCalledWith("12345");
41
+ });
42
+
43
+ it("is possible to use buttons", () => {
44
+ // Assemble
45
+ const mockOnChange = jest.fn();
46
+ render(<MathInput onChange={mockOnChange} buttonSets={["basic"]} />);
47
+
48
+ // Act
49
+ // focusing the input triggers the popover
50
+ screen.getByRole("textbox").focus();
51
+ userEvent.type(screen.getByRole("textbox"), "1");
52
+ userEvent.click(screen.getByRole("button", {name: "Plus"}));
53
+ userEvent.type(screen.getByRole("textbox"), "2");
54
+ userEvent.click(screen.getByRole("button", {name: "Minus"}));
55
+ userEvent.type(screen.getByRole("textbox"), "3");
56
+
57
+ // Assert
58
+ expect(mockOnChange).toHaveBeenLastCalledWith("1+2-3");
59
+ });
60
+ });
@@ -1,7 +1,11 @@
1
- import {keyTranslator} from "@khanacademy/math-input";
1
+ import {
2
+ keyTranslator,
3
+ createMathField,
4
+ mathQuillInstance,
5
+ MathFieldInterface,
6
+ } from "@khanacademy/math-input";
2
7
  import classNames from "classnames";
3
8
  import $ from "jquery";
4
- import MathQuill from "mathquill";
5
9
  import * as React from "react";
6
10
  import ReactDOM from "react-dom";
7
11
  import _ from "underscore";
@@ -52,6 +56,8 @@ const customKeyTranslator = {
52
56
  class MathInput extends React.Component<Props, State> {
53
57
  // @ts-expect-error [FEI-5003] - TS2564 - Property 'mouseDown' has no initializer and is not definitely assigned in the constructor.
54
58
  mouseDown: boolean;
59
+ __mathFieldWrapperRef: HTMLSpanElement | null = null;
60
+ __mathField: MathFieldInterface | null = null;
55
61
 
56
62
  static defaultProps: DefaultProps = {
57
63
  value: "",
@@ -65,100 +71,9 @@ class MathInput extends React.Component<Props, State> {
65
71
  window.addEventListener("mousedown", this.handleMouseDown);
66
72
  window.addEventListener("mouseup", this.handleMouseUp);
67
73
 
68
- let initialized = false;
69
-
70
- // Initialize MathQuill.MathField instance
71
- this.mathField({
72
- // LaTeX commands that, when typed, are immediately replaced by the
73
- // appropriate symbol. This does not include ln, log, or any of the
74
- // trig functions; those are always interpreted as commands.
75
- autoCommands: "pi theta phi sqrt nthroot",
76
-
77
- // Pop the cursor out of super/subscripts on arithmetic operators
78
- // or (in)equalities.
79
- charsThatBreakOutOfSupSub: "+-*/=<>≠≤≥",
80
-
81
- // Prevent excessive super/subscripts or fractions from being
82
- // created without operands, e.g. when somebody holds down a key
83
- supSubsRequireOperand: true,
84
-
85
- // The name of this option is somewhat misleading, as tabbing in
86
- // MathQuill breaks you out of a nested context (fraction/script)
87
- // if you're in one, but moves focus to the next input if you're
88
- // not. Spaces (with this option enabled) are just ignored in the
89
- // latter case.
90
- //
91
- // TODO(alex): In order to allow inputting mixed numbers, we will
92
- // have to accept spaces in certain cases. The desired behavior is
93
- // still to escape nested contexts if currently in one, but to
94
- // insert a space if not (we don't expect mixed numbers in nested
95
- // contexts). We should also limit to one consecutive space.
96
- spaceBehavesLikeTab: true,
97
-
98
- handlers: {
99
- edited: (mathField) => {
100
- // This handler is guaranteed to be called on change, but
101
- // unlike React it sometimes generates false positives.
102
- // One of these is on initialization (with an empty string
103
- // value), so we have to guard against that below.
104
- let value = mathField.latex();
105
-
106
- // Provide a MathQuill-compatible way to generate the
107
- // not-equals sign without pasting unicode or typing TeX
108
- value = value.replace(/<>/g, "\\ne");
109
-
110
- // Use the specified symbol to represent multiplication
111
- // TODO(alex): Add an option to disallow variables, in
112
- // which case 'x' should get converted to '\\times'
113
- if (this.props.convertDotToTimes) {
114
- value = value.replace(/\\cdot/g, "\\times");
115
-
116
- // Preserve cursor position in the common case:
117
- // typing '*' to insert a multiplication sign.
118
- // We do this by modifying internal MathQuill state
119
- // directly, instead of waiting for `.latex()` to be
120
- // called in `componentDidUpdate()`.
121
- const left = mathField.__controller.cursor[MathQuill.L];
122
- if (left && left.ctrlSeq === "\\cdot ") {
123
- mathField.__controller.backspace();
124
- mathField.cmd("\\times");
125
- }
126
- } else {
127
- value = value.replace(/\\times/g, "\\cdot");
128
- }
129
-
130
- if (initialized && this.props.value !== value) {
131
- this.props.onChange(value);
132
- }
133
- },
134
- enter: () => {
135
- // NOTE(kevinb): This isn't how answers to exercises are
136
- // submitted. The actual mechanism for this can be found
137
- // in exercise-problem-template.jsx, see `handleSubmit`.
138
-
139
- // This handler is called when the user presses the enter
140
- // key. Since this isn't an actual <input> element, we have
141
- // to manually trigger the usually automatic form submit.
142
- // eslint-disable-next-line react/no-string-refs
143
- // @ts-expect-error [FEI-5003] - TS2769 - No overload matches this call. | TS2339 - Property 'submit' does not exist on type 'JQueryStatic'.
144
- $(ReactDOM.findDOMNode(this.refs.mathinput)).submit();
145
- },
146
- upOutOf: (mathField) => {
147
- // This handler is called when the user presses the up
148
- // arrow key, but there is nowhere in the expression to go
149
- // up to (no numerator or exponent). For ease of use,
150
- // interpret this as an attempt to create an exponent.
151
- mathField.typedText("^");
152
- },
153
- },
154
- });
155
-
156
74
  // Ideally, we would be able to pass an initial value directly into
157
- // the constructor above
158
- // @ts-expect-error [FEI-5003] - TS2554 - Expected 1 arguments, but got 0.
159
- this.mathField().latex(this.props.value);
160
-
161
- initialized = true;
75
+ // the constructor
76
+ this.mathField()?.latex(this.props.value);
162
77
  }
163
78
 
164
79
  componentWillUnmount() {
@@ -224,12 +139,11 @@ class MathInput extends React.Component<Props, State> {
224
139
  };
225
140
 
226
141
  insert: (value: any) => void = (value) => {
227
- // @ts-expect-error [FEI-5003] - TS2554 - Expected 1 arguments, but got 0.
228
142
  const input = this.mathField();
229
143
  const inputModifier = customKeyTranslator[value];
230
144
  if (inputModifier) {
231
145
  inputModifier(input, value);
232
- input.focus();
146
+ input?.focus();
233
147
  return;
234
148
  }
235
149
 
@@ -240,36 +154,93 @@ class MathInput extends React.Component<Props, State> {
240
154
  if (_(value).isFunction()) {
241
155
  value(input);
242
156
  } else if (value[0] === "\\") {
243
- input.cmd(value).focus();
157
+ input?.cmd(value).focus();
244
158
  } else {
245
- input.write(value).focus();
159
+ input?.write(value).focus();
246
160
  }
247
- input.focus();
161
+ input?.focus();
248
162
  };
249
163
 
250
- mathField: (arg1: any) => any = (options) => {
251
- // The MathQuill API is now "versioned" through its own "InterVer"
252
- // system.
253
- // See: https://github.com/mathquill/mathquill/pull/459
254
- const MQ = MathQuill.getInterface(2);
255
-
256
- // MathQuill.MathField takes a DOM node, MathQuill-ifies it if it's
257
- // seeing that node for the first time, then returns the associated
258
- // MathQuill object for that node. It is stable - will always return
259
- // the same object when called on the same DOM node.
260
- // eslint-disable-next-line react/no-string-refs
261
- return MQ.MathField(ReactDOM.findDOMNode(this.refs.mathinput), options);
164
+ mathField: () => MathFieldInterface | null = () => {
165
+ if (!this.__mathField && this.__mathFieldWrapperRef) {
166
+ // Initialize MathQuill.MathField instance
167
+ this.__mathField = createMathField(
168
+ this.__mathFieldWrapperRef,
169
+ (baseConfig) => ({
170
+ ...baseConfig,
171
+ handlers: {
172
+ edit: (mathField) => {
173
+ // This handler is guaranteed to be called on change, but
174
+ // unlike React it sometimes generates false positives.
175
+ // One of these is on initialization (with an empty string
176
+ // value), so we have to guard against that below.
177
+ let value = mathField.latex();
178
+
179
+ // Provide a MathQuill-compatible way to generate the
180
+ // not-equals sign without pasting unicode or typing TeX
181
+ value = value.replace(/<>/g, "\\ne");
182
+
183
+ // Use the specified symbol to represent multiplication
184
+ // TODO(alex): Add an option to disallow variables, in
185
+ // which case 'x' should get converted to '\\times'
186
+ if (this.props.convertDotToTimes) {
187
+ value = value.replace(/\\cdot/g, "\\times");
188
+
189
+ // Preserve cursor position in the common case:
190
+ // typing '*' to insert a multiplication sign.
191
+ // We do this by modifying internal MathQuill state
192
+ // directly, instead of waiting for `.latex()` to be
193
+ // called in `componentDidUpdate()`.
194
+ const left =
195
+ mathField.__controller.cursor[
196
+ mathQuillInstance.L
197
+ ];
198
+ if (left && left.ctrlSeq === "\\cdot ") {
199
+ mathField.__controller.backspace();
200
+ mathField.cmd("\\times");
201
+ }
202
+ } else {
203
+ value = value.replace(/\\times/g, "\\cdot");
204
+ }
205
+
206
+ if (this.props.value !== value) {
207
+ this.props.onChange(value);
208
+ }
209
+ },
210
+ enter: () => {
211
+ // NOTE(kevinb): This isn't how answers to exercises are
212
+ // submitted. The actual mechanism for this can be found
213
+ // in exercise-problem-template.jsx, see `handleSubmit`.
214
+
215
+ // This handler is called when the user presses the enter
216
+ // key. Since this isn't an actual <input> element, we have
217
+ // to manually trigger the usually automatic form submit.
218
+ if (this.__mathFieldWrapperRef) {
219
+ $(this.__mathFieldWrapperRef).submit();
220
+ }
221
+ },
222
+ upOutOf: (mathField) => {
223
+ // This handler is called when the user presses the up
224
+ // arrow key, but there is nowhere in the expression to go
225
+ // up to (no numerator or exponent). For ease of use,
226
+ // interpret this as an attempt to create an exponent.
227
+ mathField.typedText("^");
228
+ },
229
+ },
230
+ }),
231
+ );
232
+ }
233
+
234
+ return this.__mathField;
262
235
  };
263
236
 
264
237
  focus: () => void = () => {
265
- // @ts-expect-error [FEI-5003] - TS2554 - Expected 1 arguments, but got 0.
266
- this.mathField().focus();
238
+ this.mathField()?.focus();
267
239
  this.setState({focused: true});
268
240
  };
269
241
 
270
242
  blur: () => void = () => {
271
- // @ts-expect-error [FEI-5003] - TS2554 - Expected 1 arguments, but got 0.
272
- this.mathField().blur();
243
+ this.mathField()?.blur();
273
244
  this.setState({focused: false});
274
245
  };
275
246
 
@@ -305,8 +276,7 @@ class MathInput extends React.Component<Props, State> {
305
276
  <div style={{display: "inline-block"}}>
306
277
  <span
307
278
  className={className}
308
- // eslint-disable-next-line react/no-string-refs
309
- ref="mathinput"
279
+ ref={(ref) => (this.__mathFieldWrapperRef = ref)}
310
280
  aria-label={this.props.labelText}
311
281
  onFocus={this.handleFocus}
312
282
  onBlur={this.handleBlur}
@@ -10,7 +10,14 @@ const prettyBig = {fontSize: "150%"} as const;
10
10
  const slightlyBig = {fontSize: "120%"} as const;
11
11
  const symbStyle = {fontSize: "130%"} as const;
12
12
 
13
- type ButtonSet = (props: any) => [React.ReactNode, Keys];
13
+ type ButtonSet = (props: any) => [
14
+ // inside of button
15
+ React.ReactNode,
16
+ // the key message it sends on click
17
+ Keys,
18
+ // label
19
+ string,
20
+ ];
14
21
 
15
22
  type ButtonSets = {
16
23
  readonly [key: string]: ReadonlyArray<ButtonSet>;
@@ -27,12 +34,14 @@ const basic: ReadonlyArray<ButtonSet> = [
27
34
  +
28
35
  </span>,
29
36
  "PLUS",
37
+ i18n._("Plus"),
30
38
  ],
31
39
  () => [
32
40
  <span key="minus" style={prettyBig}>
33
41
  -
34
42
  </span>,
35
43
  "MINUS",
44
+ i18n._("Minus"),
36
45
  ],
37
46
 
38
47
  // TODO(joel) - display as \cdot when appropriate
@@ -44,6 +53,7 @@ const basic: ReadonlyArray<ButtonSet> = [
44
53
  {i18n.doNotTranslate("\\times")}
45
54
  </TeX>,
46
55
  "TIMES",
56
+ i18n._("Multiply"),
47
57
  ];
48
58
  }
49
59
  return [
@@ -51,6 +61,7 @@ const basic: ReadonlyArray<ButtonSet> = [
51
61
  {i18n.doNotTranslate("\\cdot")}
52
62
  </TeX>,
53
63
  "CDOT",
64
+ i18n._("Multiply"),
54
65
  ];
55
66
  },
56
67
  () => {
@@ -60,6 +71,7 @@ const basic: ReadonlyArray<ButtonSet> = [
60
71
  {i18n.doNotTranslate("\\frac{x}{y}")}
61
72
  </TeX>,
62
73
  "FRAC",
74
+ i18n._("Fraction, excluding the current expression"),
63
75
  ];
64
76
  },
65
77
  ];
@@ -73,6 +85,7 @@ const buttonSets: ButtonSets = {
73
85
  return [
74
86
  <TeX key="div">{i18n.doNotTranslate("\\div")}</TeX>,
75
87
  "DIVIDE",
88
+ i18n._("Divide"),
76
89
  ];
77
90
  },
78
91
  ]),
@@ -80,15 +93,27 @@ const buttonSets: ButtonSets = {
80
93
  trig: [
81
94
  () => {
82
95
  const {TeX} = getDependencies();
83
- return [<TeX key="sin">{i18n.doNotTranslate("\\sin")}</TeX>, "SIN"];
96
+ return [
97
+ <TeX key="sin">{i18n.doNotTranslate("\\sin")}</TeX>,
98
+ "SIN",
99
+ i18n._("Sine"),
100
+ ];
84
101
  },
85
102
  () => {
86
103
  const {TeX} = getDependencies();
87
- return [<TeX key="cos">{i18n.doNotTranslate("\\cos")}</TeX>, "COS"];
104
+ return [
105
+ <TeX key="cos">{i18n.doNotTranslate("\\cos")}</TeX>,
106
+ "COS",
107
+ i18n._("Cosine"),
108
+ ];
88
109
  },
89
110
  () => {
90
111
  const {TeX} = getDependencies();
91
- return [<TeX key="tan">{i18n.doNotTranslate("\\tan")}</TeX>, "TAN"];
112
+ return [
113
+ <TeX key="tan">{i18n.doNotTranslate("\\tan")}</TeX>,
114
+ "TAN",
115
+ i18n._("Tangent"),
116
+ ];
92
117
  },
93
118
  () => {
94
119
  const {TeX} = getDependencies();
@@ -97,6 +122,7 @@ const buttonSets: ButtonSets = {
97
122
  {i18n.doNotTranslate("\\theta")}
98
123
  </TeX>,
99
124
  "THETA",
125
+ i18n._("Theta"),
100
126
  ];
101
127
  },
102
128
  () => {
@@ -112,6 +138,7 @@ const buttonSets: ButtonSets = {
112
138
  </TeX>
113
139
  </span>,
114
140
  "PHI",
141
+ i18n._("Phi"),
115
142
  ];
116
143
  },
117
144
  ],
@@ -122,6 +149,7 @@ const buttonSets: ButtonSets = {
122
149
  return [
123
150
  <TeX key="sqrt">{i18n.doNotTranslate("\\sqrt{x}")}</TeX>,
124
151
  "SQRT",
152
+ i18n._("Square root"),
125
153
  ];
126
154
  },
127
155
  // TODO(joel) - how does desmos do this?
@@ -130,6 +158,7 @@ const buttonSets: ButtonSets = {
130
158
  return [
131
159
  <TeX key="nthroot">{i18n.doNotTranslate("\\sqrt[3]{x}")}</TeX>,
132
160
  "NTHROOT3",
161
+ i18n._("Cube root"),
133
162
  ];
134
163
  },
135
164
  () => {
@@ -139,6 +168,7 @@ const buttonSets: ButtonSets = {
139
168
  {i18n.doNotTranslate("a^b")}
140
169
  </TeX>,
141
170
  "POW",
171
+ i18n._("Exponent"),
142
172
  ];
143
173
  },
144
174
  () => {
@@ -148,6 +178,7 @@ const buttonSets: ButtonSets = {
148
178
  {i18n.doNotTranslate("\\pi")}
149
179
  </TeX>,
150
180
  "PI",
181
+ i18n._("Pi"),
151
182
  ];
152
183
  },
153
184
  ],
@@ -155,17 +186,26 @@ const buttonSets: ButtonSets = {
155
186
  logarithms: [
156
187
  () => {
157
188
  const {TeX} = getDependencies();
158
- return [<TeX key="log">{i18n.doNotTranslate("\\log")}</TeX>, "LOG"];
189
+ return [
190
+ <TeX key="log">{i18n.doNotTranslate("\\log")}</TeX>,
191
+ "LOG",
192
+ i18n._("Logarithm with base 10"),
193
+ ];
159
194
  },
160
195
  () => {
161
196
  const {TeX} = getDependencies();
162
- return [<TeX key="ln">{i18n.doNotTranslate("\\ln")}</TeX>, "LN"];
197
+ return [
198
+ <TeX key="ln">{i18n.doNotTranslate("\\ln")}</TeX>,
199
+ "LN",
200
+ i18n._("Natural logarithm"),
201
+ ];
163
202
  },
164
203
  () => {
165
204
  const {TeX} = getDependencies();
166
205
  return [
167
206
  <TeX key="log_b">{i18n.doNotTranslate("\\log_b")}</TeX>,
168
207
  "LOG_B",
208
+ i18n._("Logarithm with custom base"),
169
209
  ];
170
210
  },
171
211
  ],
@@ -173,30 +213,50 @@ const buttonSets: ButtonSets = {
173
213
  "basic relations": [
174
214
  () => {
175
215
  const {TeX} = getDependencies();
176
- return [<TeX key="eq">{"="}</TeX>, "EQUAL"];
216
+ return [<TeX key="eq">{"="}</TeX>, "EQUAL", i18n._("Equals sign")];
177
217
  },
178
218
  () => {
179
219
  const {TeX} = getDependencies();
180
- return [<TeX key="lt">{i18n.doNotTranslate("\\lt")}</TeX>, "LT"];
220
+ return [
221
+ <TeX key="lt">{i18n.doNotTranslate("\\lt")}</TeX>,
222
+ "LT",
223
+ i18n._("Less than sign"),
224
+ ];
181
225
  },
182
226
  () => {
183
227
  const {TeX} = getDependencies();
184
- return [<TeX key="gt">{i18n.doNotTranslate("\\gt")}</TeX>, "GT"];
228
+ return [
229
+ <TeX key="gt">{i18n.doNotTranslate("\\gt")}</TeX>,
230
+ "GT",
231
+ i18n._("Greater than sign"),
232
+ ];
185
233
  },
186
234
  ],
187
235
 
188
236
  "advanced relations": [
189
237
  () => {
190
238
  const {TeX} = getDependencies();
191
- return [<TeX key="neq">{i18n.doNotTranslate("\\neq")}</TeX>, "NEQ"];
239
+ return [
240
+ <TeX key="neq">{i18n.doNotTranslate("\\neq")}</TeX>,
241
+ "NEQ",
242
+ i18n._("Not-equals sign"),
243
+ ];
192
244
  },
193
245
  () => {
194
246
  const {TeX} = getDependencies();
195
- return [<TeX key="leq">{i18n.doNotTranslate("\\leq")}</TeX>, "LEQ"];
247
+ return [
248
+ <TeX key="leq">{i18n.doNotTranslate("\\leq")}</TeX>,
249
+ "LEQ",
250
+ i18n._("Less than or equal to sign"),
251
+ ];
196
252
  },
197
253
  () => {
198
254
  const {TeX} = getDependencies();
199
- return [<TeX key="geq">{i18n.doNotTranslate("\\geq")}</TeX>, "GEQ"];
255
+ return [
256
+ <TeX key="geq">{i18n.doNotTranslate("\\geq")}</TeX>,
257
+ "GEQ",
258
+ i18n._("Greater than or equal to sign"),
259
+ ];
200
260
  },
201
261
  ],
202
262
  };
@@ -242,6 +302,7 @@ class TexButtons extends React.Component<Props> {
242
302
  // @ts-expect-error [FEI-5003] - TS2533 - Object is possibly 'null' or 'undefined'. | TS2339 - Property 'key' does not exist on type 'boolean | ReactChild | ReactFragment | ReactPortal'.
243
303
  key={symbol[0].key}
244
304
  type="button"
305
+ aria-label={symbol[2]}
245
306
  >
246
307
  {symbol[0]}
247
308
  </button>