@khanacademy/perseus 11.6.1 → 12.0.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.
@@ -133,9 +133,10 @@ export type PerseusExplanationWidgetOptions = {
133
133
  };
134
134
  static: boolean;
135
135
  };
136
+ export type LegacyButtonSets = ReadonlyArray<"basic" | "basic+div" | "trig" | "prealgebra" | "logarithms" | "basic relations" | "advanced relations">;
136
137
  export type PerseusExpressionWidgetOptions = {
137
138
  answerForms: ReadonlyArray<PerseusExpressionAnswerForm>;
138
- buttonSets: ReadonlyArray<string>;
139
+ buttonSets: LegacyButtonSets;
139
140
  functions: ReadonlyArray<string>;
140
141
  times: boolean;
141
142
  buttonsVisible?: "always" | "never" | "focused";
package/dist/strings.js CHANGED
@@ -48,6 +48,7 @@ module.exports = [
48
48
  i18n._("Click on the tiles to change the lights.", {}),
49
49
  i18n._("Click to add points", {}),
50
50
  i18n._("Click to add vertices", {}),
51
+ i18n._("close math keypad", {}),
51
52
  i18n._("Correct", {}),
52
53
  i18n._("Correct (but you crossed it out)", {}),
53
54
  i18n._("Correct (selected)", {}),
@@ -109,6 +110,8 @@ module.exports = [
109
110
  i18n._("Note: For this question, the shape will not move.", {}),
110
111
  i18n._("Number of divisions:", {}),
111
112
  i18n._("Number of trials:", {}),
113
+ i18n._("Oops!", {}),
114
+ i18n._("open math keypad", {}),
112
115
  i18n._("Parameter", {}),
113
116
  i18n._("Phi", {}),
114
117
  i18n._("Pi", {}),
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { useDependencies } from "../dependencies";
2
+ import type { DependenciesContext } from "../dependencies";
3
3
  import type { PerseusExpressionWidgetOptions } from "../perseus-types";
4
4
  import type { PerseusScore, WidgetProps } from "../types";
5
5
  import type { Keys as Key, KeypadConfiguration } from "@khanacademy/math-input";
@@ -13,7 +13,7 @@ type RenderProps = {
13
13
  keypadConfiguration: ReturnType<typeof keypadConfigurationForProps>;
14
14
  };
15
15
  type ExternalProps = WidgetProps<RenderProps, Rubric>;
16
- export type Props = ExternalProps & ReturnType<typeof useDependencies> & {
16
+ export type Props = ExternalProps & Partial<React.ContextType<typeof DependenciesContext>> & {
17
17
  apiOptions: NonNullable<ExternalProps["apiOptions"]>;
18
18
  buttonSets: NonNullable<ExternalProps["buttonSets"]>;
19
19
  functions: NonNullable<ExternalProps["functions"]>;
@@ -24,8 +24,9 @@ export type Props = ExternalProps & ReturnType<typeof useDependencies> & {
24
24
  value: string;
25
25
  };
26
26
  export type ExpressionState = {
27
+ invalid: boolean;
27
28
  showErrorTooltip: boolean;
28
- showErrorText: boolean;
29
+ showErrorStyle: boolean;
29
30
  };
30
31
  type DefaultProps = {
31
32
  apiOptions: Props["apiOptions"];
@@ -40,7 +41,6 @@ type DefaultProps = {
40
41
  type OnInputErrorFunctionType = (arg1?: any, arg2?: any, arg3?: any) => boolean | null | undefined;
41
42
  export declare class Expression extends React.Component<Props, ExpressionState> {
42
43
  _isMounted: boolean;
43
- errorTimeout: null | number;
44
44
  static validate(userInput: string, rubric: Rubric, onInputError?: OnInputErrorFunctionType): PerseusScore;
45
45
  static getUserInputFromProps(props: Props): string;
46
46
  static getOneCorrectAnswerFromRubric(rubric: Rubric): string | null | undefined;
@@ -50,6 +50,7 @@ export declare class Expression extends React.Component<Props, ExpressionState>
50
50
  componentDidMount: () => void;
51
51
  componentDidUpdate: (prevProps: Props) => void;
52
52
  componentWillUnmount: () => void;
53
+ _handleMouseDown: () => void;
53
54
  simpleValidate: (rubric: Rubric, onInputError: OnInputErrorFunctionType) => PerseusScore;
54
55
  getUserInput: () => string;
55
56
  change: (...args: any) => any | undefined;
@@ -151,7 +152,7 @@ declare const _default: Readonly<{
151
152
  isLastUsedWidget: boolean;
152
153
  linterContext: import("@khanacademy/perseus-linter").LinterContextProps;
153
154
  containerSizeClass: import("../util/sizing-utils").SizeClass;
154
- } & import("../types").PerseusDependenciesV2 & {
155
+ } & Partial<import("../types").PerseusDependenciesV2> & {
155
156
  apiOptions: Readonly<Readonly<{
156
157
  isArticle?: boolean | undefined;
157
158
  onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
@@ -205,14 +206,14 @@ declare const _default: Readonly<{
205
206
  showAlignmentOptions: NonNullable<boolean | undefined>;
206
207
  useDraftEditor: NonNullable<boolean | undefined>;
207
208
  }>;
208
- buttonSets: readonly string[];
209
+ buttonSets: import("../perseus-types").LegacyButtonSets;
209
210
  functions: readonly string[];
210
211
  linterContext: import("@khanacademy/perseus-linter").LinterContextProps;
211
212
  onBlur: (blurPath: import("../types").FocusPath) => void;
212
213
  onFocus: (blurPath: import("../types").FocusPath) => void;
213
214
  times: boolean;
214
215
  value: string;
215
- }, "static" | "trackInteraction" | "onChange" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget" | "analytics"> & {
216
+ }, "static" | "trackInteraction" | "onChange" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "analytics" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget"> & {
216
217
  value?: string | undefined;
217
218
  onFocus?: ((blurPath: import("../types").FocusPath) => void) | undefined;
218
219
  onBlur?: ((blurPath: import("../types").FocusPath) => void) | undefined;
@@ -270,9 +271,9 @@ declare const _default: Readonly<{
270
271
  showAlignmentOptions: NonNullable<boolean | undefined>;
271
272
  useDraftEditor: NonNullable<boolean | undefined>;
272
273
  }> | undefined;
273
- times?: boolean | undefined;
274
- buttonSets?: readonly string[] | undefined;
274
+ buttonSets?: import("../perseus-types").LegacyButtonSets | undefined;
275
275
  functions?: readonly string[] | undefined;
276
+ times?: boolean | undefined;
276
277
  } & {}, "analytics"> & React.RefAttributes<Expression>>) | undefined;
277
278
  accessible?: boolean | ((props: any) => boolean) | undefined;
278
279
  hidden?: boolean | undefined;
@@ -357,7 +358,7 @@ declare const _default: Readonly<{
357
358
  isLastUsedWidget: boolean;
358
359
  linterContext: import("@khanacademy/perseus-linter").LinterContextProps;
359
360
  containerSizeClass: import("../util/sizing-utils").SizeClass;
360
- } & import("../types").PerseusDependenciesV2 & {
361
+ } & Partial<import("../types").PerseusDependenciesV2> & {
361
362
  apiOptions: Readonly<Readonly<{
362
363
  isArticle?: boolean | undefined;
363
364
  onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
@@ -411,14 +412,14 @@ declare const _default: Readonly<{
411
412
  showAlignmentOptions: NonNullable<boolean | undefined>;
412
413
  useDraftEditor: NonNullable<boolean | undefined>;
413
414
  }>;
414
- buttonSets: readonly string[];
415
+ buttonSets: import("../perseus-types").LegacyButtonSets;
415
416
  functions: readonly string[];
416
417
  linterContext: import("@khanacademy/perseus-linter").LinterContextProps;
417
418
  onBlur: (blurPath: import("../types").FocusPath) => void;
418
419
  onFocus: (blurPath: import("../types").FocusPath) => void;
419
420
  times: boolean;
420
421
  value: string;
421
- }, "static" | "trackInteraction" | "onChange" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget" | "analytics"> & {
422
+ }, "static" | "trackInteraction" | "onChange" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "analytics" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget"> & {
422
423
  value?: string | undefined;
423
424
  onFocus?: ((blurPath: import("../types").FocusPath) => void) | undefined;
424
425
  onBlur?: ((blurPath: import("../types").FocusPath) => void) | undefined;
@@ -476,9 +477,9 @@ declare const _default: Readonly<{
476
477
  showAlignmentOptions: NonNullable<boolean | undefined>;
477
478
  useDraftEditor: NonNullable<boolean | undefined>;
478
479
  }> | undefined;
479
- times?: boolean | undefined;
480
- buttonSets?: readonly string[] | undefined;
480
+ buttonSets?: import("../perseus-types").LegacyButtonSets | undefined;
481
481
  functions?: readonly string[] | undefined;
482
+ times?: boolean | undefined;
482
483
  } & {}, "analytics"> & React.RefAttributes<Expression>>;
483
484
  }>;
484
485
  export default _default;
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": "11.6.1",
6
+ "version": "12.0.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@khanacademy/kas": "^0.3.2",
26
26
  "@khanacademy/kmath": "^0.1.3",
27
- "@khanacademy/math-input": "^14.0.1",
27
+ "@khanacademy/math-input": "^14.1.0",
28
28
  "@khanacademy/perseus-core": "1.1.1",
29
29
  "@khanacademy/perseus-linter": "^0.3.5",
30
30
  "@khanacademy/pure-markdown": "^0.2.7",
@@ -5,14 +5,13 @@ import AssetContext from "../asset-context";
5
5
  import type {PerseusItem} from "../perseus-types";
6
6
  import type {WidgetExports} from "../types";
7
7
 
8
- // @ts-expect-error - TS2322 - Type '"mocked-asset-widget"' is not assignable to type '"categorizer" | "cs-program" | "definition" | "dropdown" | "example-graphie-widget" | "example-widget" | "explanation" | "expression" | "graded-group-set" | "graded-group" | ... 29 more ... | "video"'.
9
8
  export const mockedAssetItem: PerseusItem = {
10
9
  question: {
11
10
  content: "[[\u2603 mocked-asset-widget 1]]",
12
11
  images: Object.freeze({}),
13
12
  widgets: {
14
13
  "mocked-asset-widget 1": {
15
- type: "mocked-asset-widget",
14
+ type: "mocked-asset-widget" as any,
16
15
  alignment: "default",
17
16
  static: false,
18
17
  graded: true,
@@ -13,8 +13,16 @@ export default {
13
13
  } as Story;
14
14
 
15
15
  const defaultObject = {
16
- buttonSets: ["basic"],
16
+ keypadButtonSets: {
17
+ advancedRelations: true,
18
+ basicRelations: true,
19
+ divisionKey: true,
20
+ logarithms: true,
21
+ preAlgebra: true,
22
+ trigonometry: true,
23
+ },
17
24
  onChange: () => {},
25
+ analytics: {onAnalyticsEvent: () => Promise.resolve()},
18
26
  } as const;
19
27
 
20
28
  export const DefaultWithBasicButtonSet = (
@@ -22,9 +30,14 @@ export const DefaultWithBasicButtonSet = (
22
30
  ): React.ReactElement => {
23
31
  return <MathInput {...defaultObject} />;
24
32
  };
25
- export const AlwaysVisibleButtonSet = (args: StoryArgs): React.ReactElement => {
26
- return <MathInput {...defaultObject} buttonsVisible="always" />;
27
- };
28
33
  export const DefaultWithAriaLabel = (args: StoryArgs): React.ReactElement => {
29
34
  return <MathInput {...defaultObject} labelText="Sample label" />;
30
35
  };
36
+
37
+ export const KeypadOpenByDefault = (args: StoryArgs): React.ReactElement => {
38
+ return <MathInput {...defaultObject} buttonsVisible="always" />;
39
+ };
40
+
41
+ export const KeypadNeverVisible = (args: StoryArgs): React.ReactElement => {
42
+ return <MathInput {...defaultObject} buttonsVisible="never" />;
43
+ };
@@ -7,6 +7,15 @@ import {testDependencies} from "../../../../../testing/test-dependencies";
7
7
  import * as Dependencies from "../../dependencies";
8
8
  import MathInput from "../math-input";
9
9
 
10
+ const allButtonSets = {
11
+ advancedRelations: true,
12
+ basicRelations: true,
13
+ divisionKey: true,
14
+ logarithms: true,
15
+ preAlgebra: true,
16
+ trigonometry: true,
17
+ };
18
+
10
19
  describe("Perseus' MathInput", () => {
11
20
  beforeEach(() => {
12
21
  jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
@@ -19,8 +28,9 @@ describe("Perseus' MathInput", () => {
19
28
  render(
20
29
  <MathInput
21
30
  onChange={() => {}}
22
- buttonSets={["basic"]}
31
+ keypadButtonSets={allButtonSets}
23
32
  labelText="test"
33
+ analytics={{onAnalyticsEvent: () => Promise.resolve()}}
24
34
  />,
25
35
  );
26
36
 
@@ -31,7 +41,13 @@ describe("Perseus' MathInput", () => {
31
41
  it("is possible to type in the input", () => {
32
42
  // Assemble
33
43
  const mockOnChange = jest.fn();
34
- render(<MathInput onChange={mockOnChange} buttonSets={["basic"]} />);
44
+ render(
45
+ <MathInput
46
+ onChange={mockOnChange}
47
+ keypadButtonSets={allButtonSets}
48
+ analytics={{onAnalyticsEvent: () => Promise.resolve()}}
49
+ />,
50
+ );
35
51
 
36
52
  // Act
37
53
  userEvent.type(screen.getByRole("textbox"), "12345");
@@ -43,18 +59,89 @@ describe("Perseus' MathInput", () => {
43
59
  it("is possible to use buttons", () => {
44
60
  // Assemble
45
61
  const mockOnChange = jest.fn();
46
- render(<MathInput onChange={mockOnChange} buttonSets={["basic"]} />);
62
+ render(
63
+ <MathInput
64
+ onChange={mockOnChange}
65
+ keypadButtonSets={allButtonSets}
66
+ analytics={{onAnalyticsEvent: () => Promise.resolve()}}
67
+ />,
68
+ );
47
69
 
48
70
  // Act
49
- // focusing the input triggers the popover
50
- screen.getByRole("textbox").focus();
51
- userEvent.type(screen.getByRole("textbox"), "1");
71
+ screen.getByRole("switch").click();
72
+ userEvent.click(screen.getByRole("button", {name: "1"}));
52
73
  userEvent.click(screen.getByRole("button", {name: "Plus"}));
53
- userEvent.type(screen.getByRole("textbox"), "2");
74
+ userEvent.click(screen.getByRole("button", {name: "2"}));
54
75
  userEvent.click(screen.getByRole("button", {name: "Minus"}));
55
- userEvent.type(screen.getByRole("textbox"), "3");
76
+ userEvent.click(screen.getByRole("button", {name: "3"}));
56
77
 
57
78
  // Assert
58
79
  expect(mockOnChange).toHaveBeenLastCalledWith("1+2-3");
59
80
  });
81
+
82
+ it("is possible to use buttons with legacy props", () => {
83
+ // Assemble
84
+ const mockOnChange = jest.fn();
85
+ render(
86
+ <MathInput
87
+ onChange={mockOnChange}
88
+ buttonSets={["basic+div"]}
89
+ analytics={{onAnalyticsEvent: () => Promise.resolve()}}
90
+ />,
91
+ );
92
+
93
+ // Act
94
+ // focusing the input triggers the popover
95
+ screen.getByRole("switch").click();
96
+ userEvent.click(screen.getByRole("button", {name: "1"}));
97
+ userEvent.click(screen.getByRole("button", {name: "Plus"}));
98
+ userEvent.click(screen.getByRole("button", {name: "2"}));
99
+ userEvent.click(screen.getByRole("button", {name: "Divide"}));
100
+ userEvent.click(screen.getByRole("button", {name: "3"}));
101
+
102
+ // Assert
103
+ expect(mockOnChange).toHaveBeenLastCalledWith("1+2\\div3");
104
+ });
105
+
106
+ it("returns focus to input after button click", () => {
107
+ // Assemble
108
+ render(
109
+ <MathInput
110
+ onChange={() => {}}
111
+ keypadButtonSets={allButtonSets}
112
+ analytics={{onAnalyticsEvent: () => Promise.resolve()}}
113
+ />,
114
+ );
115
+
116
+ // Act
117
+ // focusing the input triggers the popover
118
+ screen.getByRole("switch").click();
119
+ userEvent.click(screen.getByRole("button", {name: "1"}));
120
+
121
+ // Assert
122
+ expect(screen.getByRole("textbox")).toHaveFocus();
123
+ });
124
+
125
+ it("does not return focus to input after button press via keyboard", () => {
126
+ // Assemble
127
+ render(
128
+ <MathInput
129
+ onChange={() => {}}
130
+ keypadButtonSets={allButtonSets}
131
+ analytics={{onAnalyticsEvent: () => Promise.resolve()}}
132
+ />,
133
+ );
134
+
135
+ // Act
136
+ // focusing the input triggers the popover
137
+ screen.getByRole("switch").click();
138
+ userEvent.tab(); // to "123" tab
139
+ userEvent.tab(); // to extra keys tab
140
+ userEvent.tab(); // to whole keypad
141
+ userEvent.tab(); // to "1" button
142
+ userEvent.keyboard("{enter}");
143
+
144
+ // Assert
145
+ expect(screen.getByRole("textbox")).not.toHaveFocus();
146
+ });
60
147
  });