@khanacademy/perseus 6.1.0 → 6.2.1

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.
@@ -1,7 +1,7 @@
1
1
  import type { Coord } from "./interactive2/types";
2
2
  export type Range = [number, number];
3
3
  export type Size = [number, number];
4
- type Empty = Record<string, never>;
4
+ type Empty = Record<never, never>;
5
5
  export type PerseusItem = {
6
6
  question: PerseusRenderer;
7
7
  hints: ReadonlyArray<PerseusRenderer>;
@@ -7,7 +7,7 @@
7
7
  import type { Coord } from "./interactive2/types";
8
8
  export type Range = [number, number];
9
9
  export type Size = [number, number];
10
- declare type Empty = { [key: string]: empty };
10
+ declare type Empty = { [key: empty]: empty };
11
11
  export type PerseusItem = {|
12
12
  question: PerseusRenderer,
13
13
  hints: $ReadOnlyArray<PerseusRenderer>,
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import type { PerseusRenderer, PerseusDefinitionWidgetOptions } from "../perseus-types";
3
3
  import type { PerseusScore, WidgetProps } from "../types";
4
- type Empty = Record<string, never>;
4
+ type Empty = Record<never, never>;
5
5
  type RenderProps = PerseusDefinitionWidgetOptions;
6
6
  type Rubric = PerseusDefinitionWidgetOptions;
7
7
  type UserInput = Empty;
@@ -11,7 +11,7 @@ import type {
11
11
  PerseusDefinitionWidgetOptions,
12
12
  } from "../perseus-types";
13
13
  import type { PerseusScore, WidgetProps } from "../types";
14
- declare type Empty = { [key: string]: empty };
14
+ declare type Empty = { [key: empty]: empty };
15
15
  declare type RenderProps = PerseusDefinitionWidgetOptions;
16
16
  declare type Rubric = PerseusDefinitionWidgetOptions;
17
17
  declare type UserInput = Empty;
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import type { PerseusExplanationWidgetOptions } from "../perseus-types";
3
3
  import type { PerseusScore, WidgetProps } from "../types";
4
- type Empty = Record<string, never>;
4
+ type Empty = Record<never, never>;
5
5
  type RenderProps = PerseusExplanationWidgetOptions;
6
6
  type Rubric = PerseusExplanationWidgetOptions;
7
7
  type UserInput = Empty;
@@ -8,7 +8,7 @@ import * as $Flowgen$Import$_2e__2e__2f_types from "../types";
8
8
  import * as React from "react";
9
9
  import type { PerseusExplanationWidgetOptions } from "../perseus-types";
10
10
  import type { PerseusScore, WidgetProps } from "../types";
11
- declare type Empty = { [key: string]: empty };
11
+ declare type Empty = { [key: empty]: empty };
12
12
  declare type RenderProps = PerseusExplanationWidgetOptions;
13
13
  declare type Rubric = PerseusExplanationWidgetOptions;
14
14
  declare type UserInput = Empty;
@@ -1,3 +1,4 @@
1
+ import { Keys as Key } from "@khanacademy/math-input";
1
2
  import * as React from "react";
2
3
  import type { PerseusExpressionWidgetOptions } from "../perseus-types";
3
4
  import type { PerseusScore, WidgetProps } from "../types";
@@ -51,7 +52,7 @@ export declare class Expression extends React.Component<Props, ExpressionState>
51
52
  focus: () => boolean;
52
53
  focusInputPath: (inputPath: InputPath) => void;
53
54
  blurInputPath: (inputPath: InputPath) => void;
54
- insert: (text: string) => void;
55
+ insert(keyPressed: Key): void;
55
56
  getInputPaths: () => ReadonlyArray<ReadonlyArray<any>>;
56
57
  getGrammarTypeForPath: (inputPath: string) => string;
57
58
  setInputValue: (path: string, newValue: string, cb: () => void) => void;
@@ -5,6 +5,7 @@
5
5
  * @flow
6
6
  */
7
7
  import * as $Flowgen$Import$_2e__2e__2f_types from "../types";
8
+ import { Keys as Key } from "@khanacademy/math-input";
8
9
  import * as React from "react";
9
10
  import type { PerseusExpressionWidgetOptions } from "../perseus-types";
10
11
  import type { PerseusScore, WidgetProps } from "../types";
@@ -74,7 +75,7 @@ declare export class Expression
74
75
  focus: () => boolean;
75
76
  focusInputPath: (inputPath: InputPath) => void;
76
77
  blurInputPath: (inputPath: InputPath) => void;
77
- insert: (text: string) => void;
78
+ insert(keyPressed: Key): void;
78
79
  getInputPaths: () => $ReadOnlyArray<$ReadOnlyArray<any>>;
79
80
  getGrammarTypeForPath: (inputPath: string) => string;
80
81
  setInputValue: (path: string, newValue: string, cb: () => void) => void;
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.1.0",
6
+ "version": "6.2.1",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -23,7 +23,7 @@
23
23
  "dependencies": {
24
24
  "@khanacademy/kas": "^0.3.1",
25
25
  "@khanacademy/kmath": "^0.1.1",
26
- "@khanacademy/math-input": "^3.0.0",
26
+ "@khanacademy/math-input": "^4.1.0",
27
27
  "@khanacademy/perseus-linter": "^0.3.1",
28
28
  "@khanacademy/pure-markdown": "^0.2.3",
29
29
  "@khanacademy/simple-markdown": "^0.9.3",
@@ -2,7 +2,7 @@ import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
2
2
  import {fireEvent, render, screen} from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import * as React from "react";
5
- import "@testing-library/jest-dom"; // Imports custom mathers
5
+ import "@testing-library/jest-dom"; // Imports custom matchers
6
6
 
7
7
  import {testDependencies} from "../../../../testing/test-dependencies";
8
8
  import {
@@ -181,7 +181,6 @@ describe("Sortable.itemsFromProps", () => {
181
181
  expect(items).toMatchInlineSnapshot(`
182
182
  [
183
183
  {
184
- "endPosition": {},
185
184
  "height": 0,
186
185
  "key": 0,
187
186
  "option": "a",
@@ -189,7 +188,6 @@ describe("Sortable.itemsFromProps", () => {
189
188
  "width": 0,
190
189
  },
191
190
  {
192
- "endPosition": {},
193
191
  "height": 0,
194
192
  "key": 1,
195
193
  "option": "b",
@@ -197,7 +195,6 @@ describe("Sortable.itemsFromProps", () => {
197
195
  "width": 0,
198
196
  },
199
197
  {
200
- "endPosition": {},
201
198
  "height": 0,
202
199
  "key": 2,
203
200
  "option": "c",
@@ -1,3 +1,4 @@
1
+ import {keyTranslator} from "@khanacademy/math-input";
1
2
  import classNames from "classnames";
2
3
  import $ from "jquery";
3
4
  import MathQuill from "mathquill";
@@ -33,6 +34,20 @@ type State = {
33
34
  focused: boolean;
34
35
  };
35
36
 
37
+ const customKeyTranslator = {
38
+ ...keyTranslator,
39
+ // If there's something in the input that can become part of a
40
+ // fraction, typing "/" puts it in the numerator. If not, typing
41
+ // "/" does nothing. In that case, enter a \frac.
42
+ FRAC: (mathQuill) => {
43
+ const contents = mathQuill.latex();
44
+ mathQuill.typedText("/");
45
+ if (mathQuill.latex() === contents) {
46
+ mathQuill.cmd("\\frac");
47
+ }
48
+ },
49
+ };
50
+
36
51
  // A WYSIWYG math input that calls `onChange(LaTeX-string)`
37
52
  class MathInput extends React.Component<Props, State> {
38
53
  // @ts-expect-error [FEI-5003] - TS2564 - Property 'mouseDown' has no initializer and is not definitely assigned in the constructor.
@@ -208,9 +223,20 @@ class MathInput extends React.Component<Props, State> {
208
223
  return this.state.focused;
209
224
  };
210
225
 
211
- insert: (arg1: any) => void = (value) => {
226
+ insert: (value: any) => void = (value) => {
212
227
  // @ts-expect-error [FEI-5003] - TS2554 - Expected 1 arguments, but got 0.
213
228
  const input = this.mathField();
229
+ const inputModifier = customKeyTranslator[value];
230
+ if (inputModifier) {
231
+ inputModifier(input, value);
232
+ input.focus();
233
+ return;
234
+ }
235
+
236
+ // note(Matthew): I'm not sure this is still being used
237
+ // but it fails tests when I remove it and the way we call
238
+ // methods directly on components makes it difficult to confirm
239
+ // if it's dead code
214
240
  if (_(value).isFunction()) {
215
241
  value(input);
216
242
  } else if (value[0] === "\\") {
@@ -72,7 +72,7 @@ class Placeholder extends React.Component<PlaceholderProps> {
72
72
 
73
73
  type DraggableProps = {
74
74
  content: string;
75
- endPosition: {left: number; top: number} | Record<string, never>;
75
+ endPosition?: {left: number; top: number};
76
76
  includePadding: boolean;
77
77
  layout: Layout;
78
78
  width?: number;
@@ -251,7 +251,10 @@ class Draggable extends React.Component<DraggableProps, DraggableState> {
251
251
  return;
252
252
  }
253
253
 
254
- if (this.props.state === ItemState.ANIMATING) {
254
+ if (
255
+ this.props.state === ItemState.ANIMATING &&
256
+ this.props.endPosition
257
+ ) {
255
258
  // Start animating
256
259
  const current = this.getCurrentPosition();
257
260
  const duration =
@@ -433,7 +436,7 @@ type SortableItem = {
433
436
  option: SortableOption;
434
437
  key: number;
435
438
  state: ItemState;
436
- endPosition: Position | Record<string, never>;
439
+ endPosition?: Position;
437
440
  width: number;
438
441
  height: number;
439
442
  };
@@ -513,7 +516,6 @@ class Sortable extends React.Component<SortableProps, SortableState> {
513
516
  option: option,
514
517
  key: i,
515
518
  state,
516
- endPosition: {},
517
519
  width: 0,
518
520
  height: 0,
519
521
  };
@@ -1,3 +1,4 @@
1
+ import {Keys} from "@khanacademy/math-input";
1
2
  import * as i18n from "@khanacademy/wonder-blocks-i18n";
2
3
  import PropTypes from "prop-types";
3
4
  import * as React from "react";
@@ -9,8 +10,7 @@ const prettyBig = {fontSize: "150%"} as const;
9
10
  const slightlyBig = {fontSize: "120%"} as const;
10
11
  const symbStyle = {fontSize: "130%"} as const;
11
12
 
12
- type Inserter = string | ((input: any) => void);
13
- type ButtonSet = (props: any) => [React.ReactNode, Inserter];
13
+ type ButtonSet = (props: any) => [React.ReactNode, Keys];
14
14
 
15
15
  type ButtonSets = {
16
16
  readonly [key: string]: ReadonlyArray<ButtonSet>;
@@ -26,13 +26,13 @@ const basic: ReadonlyArray<ButtonSet> = [
26
26
  <span key="plus" style={slightlyBig}>
27
27
  +
28
28
  </span>,
29
- "+",
29
+ "PLUS",
30
30
  ],
31
31
  () => [
32
32
  <span key="minus" style={prettyBig}>
33
33
  -
34
34
  </span>,
35
- "-",
35
+ "MINUS",
36
36
  ],
37
37
 
38
38
  // TODO(joel) - display as \cdot when appropriate
@@ -43,14 +43,14 @@ const basic: ReadonlyArray<ButtonSet> = [
43
43
  <TeX key="times" style={prettyBig}>
44
44
  {i18n.doNotTranslate("\\times")}
45
45
  </TeX>,
46
- "\\times",
46
+ "TIMES",
47
47
  ];
48
48
  }
49
49
  return [
50
50
  <TeX key="times" style={prettyBig}>
51
51
  {i18n.doNotTranslate("\\cdot")}
52
52
  </TeX>,
53
- "\\cdot",
53
+ "CDOT",
54
54
  ];
55
55
  },
56
56
  () => {
@@ -59,17 +59,7 @@ const basic: ReadonlyArray<ButtonSet> = [
59
59
  <TeX key="frac" style={prettyBig}>
60
60
  {i18n.doNotTranslate("\\frac{x}{y}")}
61
61
  </TeX>,
62
-
63
- // If there's something in the input that can become part of a
64
- // fraction, typing "/" puts it in the numerator. If not, typing
65
- // "/" does nothing. In that case, enter a \frac.
66
- (input: any) => {
67
- const contents = input.latex();
68
- input.typedText("/");
69
- if (input.latex() === contents) {
70
- input.cmd("\\frac");
71
- }
72
- },
62
+ "FRAC",
73
63
  ];
74
64
  },
75
65
  ];
@@ -82,7 +72,7 @@ const buttonSets: ButtonSets = {
82
72
  const {TeX} = getDependencies();
83
73
  return [
84
74
  <TeX key="div">{i18n.doNotTranslate("\\div")}</TeX>,
85
- "\\div",
75
+ "DIVIDE",
86
76
  ];
87
77
  },
88
78
  ]),
@@ -90,24 +80,15 @@ const buttonSets: ButtonSets = {
90
80
  trig: [
91
81
  () => {
92
82
  const {TeX} = getDependencies();
93
- return [
94
- <TeX key="sin">{i18n.doNotTranslate("\\sin")}</TeX>,
95
- "\\sin",
96
- ];
83
+ return [<TeX key="sin">{i18n.doNotTranslate("\\sin")}</TeX>, "SIN"];
97
84
  },
98
85
  () => {
99
86
  const {TeX} = getDependencies();
100
- return [
101
- <TeX key="cos">{i18n.doNotTranslate("\\cos")}</TeX>,
102
- "\\cos",
103
- ];
87
+ return [<TeX key="cos">{i18n.doNotTranslate("\\cos")}</TeX>, "COS"];
104
88
  },
105
89
  () => {
106
90
  const {TeX} = getDependencies();
107
- return [
108
- <TeX key="tan">{i18n.doNotTranslate("\\tan")}</TeX>,
109
- "\\tan",
110
- ];
91
+ return [<TeX key="tan">{i18n.doNotTranslate("\\tan")}</TeX>, "TAN"];
111
92
  },
112
93
  () => {
113
94
  const {TeX} = getDependencies();
@@ -115,7 +96,7 @@ const buttonSets: ButtonSets = {
115
96
  <TeX key="theta" style={symbStyle}>
116
97
  {i18n.doNotTranslate("\\theta")}
117
98
  </TeX>,
118
- "\\theta",
99
+ "THETA",
119
100
  ];
120
101
  },
121
102
  () => {
@@ -130,7 +111,7 @@ const buttonSets: ButtonSets = {
130
111
  {i18n.doNotTranslate("\\phi")}
131
112
  </TeX>
132
113
  </span>,
133
- "\\phi",
114
+ "PHI",
134
115
  ];
135
116
  },
136
117
  ],
@@ -140,7 +121,7 @@ const buttonSets: ButtonSets = {
140
121
  const {TeX} = getDependencies();
141
122
  return [
142
123
  <TeX key="sqrt">{i18n.doNotTranslate("\\sqrt{x}")}</TeX>,
143
- "\\sqrt",
124
+ "SQRT",
144
125
  ];
145
126
  },
146
127
  // TODO(joel) - how does desmos do this?
@@ -148,10 +129,7 @@ const buttonSets: ButtonSets = {
148
129
  const {TeX} = getDependencies();
149
130
  return [
150
131
  <TeX key="nthroot">{i18n.doNotTranslate("\\sqrt[3]{x}")}</TeX>,
151
- (input: any) => {
152
- input.typedText("nthroot3");
153
- input.keystroke("Right");
154
- },
132
+ "NTHROOT3",
155
133
  ];
156
134
  },
157
135
  () => {
@@ -160,17 +138,7 @@ const buttonSets: ButtonSets = {
160
138
  <TeX key="pow" style={slightlyBig}>
161
139
  {i18n.doNotTranslate("a^b")}
162
140
  </TeX>,
163
- (input: any) => {
164
- const contents = input.latex();
165
- input.typedText("^");
166
-
167
- // If the input hasn't changed (for example, if we're
168
- // attempting to add an exponent on an empty input or an empty
169
- // denominator), insert our own "a^b"
170
- if (input.latex() === contents) {
171
- input.typedText("a^b");
172
- }
173
- },
141
+ "POW",
174
142
  ];
175
143
  },
176
144
  () => {
@@ -179,7 +147,7 @@ const buttonSets: ButtonSets = {
179
147
  <TeX key="pi" style={slightlyBig}>
180
148
  {i18n.doNotTranslate("\\pi")}
181
149
  </TeX>,
182
- "\\pi",
150
+ "PI",
183
151
  ];
184
152
  },
185
153
  ],
@@ -187,26 +155,17 @@ const buttonSets: ButtonSets = {
187
155
  logarithms: [
188
156
  () => {
189
157
  const {TeX} = getDependencies();
190
- return [
191
- <TeX key="log">{i18n.doNotTranslate("\\log")}</TeX>,
192
- "\\log",
193
- ];
158
+ return [<TeX key="log">{i18n.doNotTranslate("\\log")}</TeX>, "LOG"];
194
159
  },
195
160
  () => {
196
161
  const {TeX} = getDependencies();
197
- return [<TeX key="ln">{i18n.doNotTranslate("\\ln")}</TeX>, "\\ln"];
162
+ return [<TeX key="ln">{i18n.doNotTranslate("\\ln")}</TeX>, "LN"];
198
163
  },
199
164
  () => {
200
165
  const {TeX} = getDependencies();
201
166
  return [
202
167
  <TeX key="log_b">{i18n.doNotTranslate("\\log_b")}</TeX>,
203
- (input) => {
204
- input.typedText("log_");
205
- input.keystroke("Right");
206
- input.typedText("(");
207
- input.keystroke("Left");
208
- input.keystroke("Left");
209
- },
168
+ "LOG_B",
210
169
  ];
211
170
  },
212
171
  ],
@@ -214,39 +173,30 @@ const buttonSets: ButtonSets = {
214
173
  "basic relations": [
215
174
  () => {
216
175
  const {TeX} = getDependencies();
217
- return [<TeX key="eq">{"="}</TeX>, "="];
176
+ return [<TeX key="eq">{"="}</TeX>, "EQUAL"];
218
177
  },
219
178
  () => {
220
179
  const {TeX} = getDependencies();
221
- return [<TeX key="lt">{i18n.doNotTranslate("\\lt")}</TeX>, "\\lt"];
180
+ return [<TeX key="lt">{i18n.doNotTranslate("\\lt")}</TeX>, "LT"];
222
181
  },
223
182
  () => {
224
183
  const {TeX} = getDependencies();
225
- return [<TeX key="gt">{i18n.doNotTranslate("\\gt")}</TeX>, "\\gt"];
184
+ return [<TeX key="gt">{i18n.doNotTranslate("\\gt")}</TeX>, "GT"];
226
185
  },
227
186
  ],
228
187
 
229
188
  "advanced relations": [
230
189
  () => {
231
190
  const {TeX} = getDependencies();
232
- return [
233
- <TeX key="neq">{i18n.doNotTranslate("\\neq")}</TeX>,
234
- "\\neq",
235
- ];
191
+ return [<TeX key="neq">{i18n.doNotTranslate("\\neq")}</TeX>, "NEQ"];
236
192
  },
237
193
  () => {
238
194
  const {TeX} = getDependencies();
239
- return [
240
- <TeX key="leq">{i18n.doNotTranslate("\\leq")}</TeX>,
241
- "\\leq",
242
- ];
195
+ return [<TeX key="leq">{i18n.doNotTranslate("\\leq")}</TeX>, "LEQ"];
243
196
  },
244
197
  () => {
245
198
  const {TeX} = getDependencies();
246
- return [
247
- <TeX key="geq">{i18n.doNotTranslate("\\geq")}</TeX>,
248
- "\\geq",
249
- ];
199
+ return [<TeX key="geq">{i18n.doNotTranslate("\\geq")}</TeX>, "GEQ"];
250
200
  },
251
201
  ],
252
202
  };
@@ -255,7 +205,7 @@ export type ButtonSetsType = ReadonlyArray<keyof typeof buttonSets>;
255
205
 
256
206
  type Props = {
257
207
  sets: ButtonSetsType;
258
- onInsert: (arg1: Inserter) => void;
208
+ onInsert: (key: Keys) => void;
259
209
  className?: string;
260
210
  convertDotToTimes?: boolean;
261
211
  };
@@ -7,7 +7,7 @@ export type Range = [number, number];
7
7
  export type Size = [number, number];
8
8
 
9
9
  // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
10
- type Empty = Record<string, never>;
10
+ type Empty = Record<never, never>;
11
11
 
12
12
  export type PerseusItem = {
13
13
  // The details of the question being asked to the user.
@@ -13,7 +13,7 @@ import type {
13
13
  import type {PerseusScore, WidgetExports, WidgetProps} from "../types";
14
14
 
15
15
  // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
16
- type Empty = Record<string, never>;
16
+ type Empty = Record<never, never>;
17
17
 
18
18
  type RenderProps = PerseusDefinitionWidgetOptions;
19
19
 
@@ -13,7 +13,7 @@ import type {PerseusExplanationWidgetOptions} from "../perseus-types";
13
13
  import type {PerseusScore, WidgetExports, WidgetProps} from "../types";
14
14
 
15
15
  // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
16
- type Empty = Record<string, never>;
16
+ type Empty = Record<never, never>;
17
17
 
18
18
  type RenderProps = PerseusExplanationWidgetOptions; // transform = _.identity
19
19
 
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable react/prop-types */
2
2
  import * as KAS from "@khanacademy/kas";
3
- import {KeypadInput, KeypadType} from "@khanacademy/math-input";
3
+ import {KeypadInput, KeypadType, Keys as Key} from "@khanacademy/math-input";
4
4
  import {linterContextDefault} from "@khanacademy/perseus-linter";
5
5
  import * as i18n from "@khanacademy/wonder-blocks-i18n";
6
6
  import classNames from "classnames";
@@ -413,11 +413,11 @@ export class Expression extends React.Component<Props, ExpressionState> {
413
413
  };
414
414
 
415
415
  // HACK(joel)
416
- insert: (text: string) => void = (text: string) => {
416
+ insert(keyPressed: Key) {
417
417
  // eslint-disable-next-line react/no-string-refs
418
418
  // @ts-expect-error [FEI-5003] - TS2339 - Property 'insert' does not exist on type 'ReactInstance'.
419
- this.refs.input.insert(text);
420
- };
419
+ this.refs.input.insert(keyPressed);
420
+ }
421
421
 
422
422
  getInputPaths: () => ReadonlyArray<ReadonlyArray<any>> = () => {
423
423
  // The widget itself is an input, so we return a single empty list to