@khanacademy/perseus 8.0.0 → 10.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/article-renderer.d.ts +2 -1
  3. package/dist/components/info-tip.d.ts +2 -1
  4. package/dist/components/input-with-examples.d.ts +1 -1
  5. package/dist/components/sortable.d.ts +1 -1
  6. package/dist/dependencies.d.ts +7 -1
  7. package/dist/es/index.css +3 -5
  8. package/dist/es/index.js +72 -45
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/hint-renderer.d.ts +2 -1
  11. package/dist/hints-renderer.d.ts +2 -1
  12. package/dist/index.css +3 -5
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +72 -46
  15. package/dist/index.js.map +1 -1
  16. package/dist/interaction-tracker.d.ts +3 -3
  17. package/dist/item-renderer.d.ts +3 -1
  18. package/dist/multi-items/multi-renderer.d.ts +4 -2
  19. package/dist/perseus-api.d.ts +1 -1
  20. package/dist/renderer.d.ts +4 -9
  21. package/dist/server-item-renderer.d.ts +3 -1
  22. package/dist/types.d.ts +30 -15
  23. package/dist/widget-container.d.ts +2 -1
  24. package/dist/widgets/expression.d.ts +409 -12
  25. package/dist/widgets/graded-group.d.ts +2 -2
  26. package/dist/widgets/grapher.d.ts +2 -1
  27. package/dist/widgets/passage-ref.d.ts +2 -1
  28. package/dist/widgets/sequence.d.ts +2 -2
  29. package/package.json +3 -3
  30. package/src/__stories__/article-renderer.stories.tsx +35 -2
  31. package/src/__testdata__/article-renderer.testdata.ts +71 -0
  32. package/src/__tests__/item-renderer.test.tsx +12 -1
  33. package/src/__tests__/renderer.test.tsx +0 -2
  34. package/src/__tests__/server-item-renderer.test.tsx +11 -5
  35. package/src/article-renderer.tsx +38 -30
  36. package/src/components/info-tip.tsx +3 -4
  37. package/src/components/input-with-examples.tsx +1 -1
  38. package/src/components/sortable.tsx +1 -1
  39. package/src/dependencies.ts +16 -1
  40. package/src/hint-renderer.tsx +2 -1
  41. package/src/hints-renderer.tsx +2 -9
  42. package/src/index.ts +0 -1
  43. package/src/interaction-tracker.ts +3 -3
  44. package/src/item-renderer.tsx +57 -44
  45. package/src/multi-items/__tests__/multi-renderer.test.tsx +6 -1
  46. package/src/multi-items/multi-renderer.tsx +18 -11
  47. package/src/renderer.tsx +4 -4
  48. package/src/server-item-renderer.tsx +21 -17
  49. package/src/types.ts +50 -21
  50. package/src/widget-container.tsx +2 -1
  51. package/src/widgets/__stories__/definition.stories.tsx +8 -1
  52. package/src/widgets/__stories__/expression.stories.tsx +2 -2
  53. package/src/widgets/__tests__/{expression.test.ts → expression.test.tsx} +73 -8
  54. package/src/widgets/__tests__/renderQuestion.tsx +29 -29
  55. package/src/widgets/expression.tsx +77 -48
  56. package/src/widgets/graded-group.tsx +11 -2
  57. package/src/widgets/grapher.tsx +4 -12
  58. package/src/widgets/passage-ref.tsx +1 -1
  59. package/src/widgets/sequence.tsx +10 -2
  60. package/tsconfig-build.tsbuildinfo +1 -1
@@ -1,5 +1,37 @@
1
1
  import type {PerseusRenderer} from "../perseus-types";
2
2
 
3
+ export const singleSectionArticle: PerseusRenderer = {
4
+ content:
5
+ "[[☃ image 2]]\n\n**Part A:** Return to the main characters from your three favorite films. \n\n- What was one important choice they had to make where the stakes were **high**?\n\n- What were the **stakes**?\n\n- Can you identify them as internal, external or philosophical?\n\n**Part B:** Think about a difficult choice you had to make in your own life. What was at stake? \n\n**Part C:** Return to one of the obstacles your character might face from the previous exercise. Now think of the **choice** this obstacle forces them to make. Answer the following:\n\n- What are the possible stakes of this choice?\n\n- Can you come up with an internal, external or philosophical stake which applies to this choice? \n",
6
+ images: {},
7
+ widgets: {
8
+ "image 2": {
9
+ type: "image",
10
+ alignment: "block",
11
+ static: false,
12
+ graded: true,
13
+ options: {
14
+ static: false,
15
+ title: "",
16
+ range: [
17
+ [0, 10],
18
+ [0, 10],
19
+ ],
20
+ box: [1258, 703],
21
+ backgroundImage: {
22
+ url: "https://ka-perseus-images.s3.amazonaws.com/b08029bc1786fbe54468a2ddc96aaa20be7a663a.png",
23
+ width: 1258,
24
+ height: 703,
25
+ },
26
+ labels: [],
27
+ alt: 'A scene from Pixar\'s film "Toy Story 3" where the characters are swimming in a sea of trash and look very afraid.',
28
+ caption: "",
29
+ },
30
+ version: {major: 0, minor: 0},
31
+ },
32
+ },
33
+ };
34
+
3
35
  export const passageArticle: PerseusRenderer = {
4
36
  content:
5
37
  "###Group/Pair Activity \n\nThis passage is adapted from Ed Yong, “Turtles Use the Earth’s Magnetic Field as Global GPS.” ©2011 by Kalmbach Publishing Co.\n\n[[☃ passage 1]]\n\n**Question 9**\n\nThe passage most strongly suggests that Adelita used which of the following to navigate her 9,000-mile journey?\n\nA) The current of the North Atlantic gyre\n\nB) Cues from electromagnetic coils designed by Putman and Lohmann\n\nC) The inclination and intensity of Earth’s magnetic field\n\nD) A simulated “magnetic signature” configured by Lohmann\n\n10) Which choice provides the best evidence for the answer to the previous question?\n\nA) Lines 1–2 (“In 1996...way”)\n\nB) Lines 20–21 (“Using...surface”)\n\nC) Lines 36–37 (“In the wild...stars”)\n\nD) Lines 43–45 (“Neither...it is”)\n\n**Question 12** \n\nBased on the passage, which choice best describes the relationship between Putman’s and Lohmann’s research?\n\nA) Putman’s research contradicts Lohmann’s.\n\nB) Putman’s research builds on Lohmann’s.\n\nC) Lohmann’s research confirms Putman’s.\n\nD) Lohmann’s research corrects Putman’s.",
@@ -25,3 +57,42 @@ export const passageArticle: PerseusRenderer = {
25
57
  },
26
58
  },
27
59
  };
60
+
61
+ export const multiSectionArticle: ReadonlyArray<PerseusRenderer> = [
62
+ {
63
+ content:
64
+ "## What is a matrix?\n\nA matrix is a rectangular array of numbers arranged in rows and columns.\n\nMatrices can be useful for organizing and manipulating data. They can also be used as a tool to help solve systems of equations.",
65
+ images: {},
66
+ widgets: {},
67
+ },
68
+ {
69
+ content:
70
+ "## How do we multiply a matrix by a scalar?\n\nTo multiply a matrix by a scalar, we multiply each entry in the matrix by the scalar. For example, if \n\n$A = \\begin{bmatrix} \n2 & 3 \\\\\n1 & 4 \\\\\n\\end{bmatrix}$\n\nand we want to multiply $A$ by $2$, we can multiply each entry by $2$ to get:\n\n$2A = \\begin{bmatrix} \n4 & 6 \\\\\n2 & 8 \\\\\n\\end{bmatrix}$",
71
+ images: {},
72
+ widgets: {},
73
+ },
74
+ {
75
+ content:
76
+ "## How do we add or subtract two matrices?\n\nTo add or subtract two matrices, we add or subtract corresponding entries. So if \n\n$A = \\begin{bmatrix} \n2 & 3 \\\\\n1 & 4 \\\\\n\\end{bmatrix}$ \n\nand \n\n$B = \\begin{bmatrix} \n5 & 2 \\\\\n3 & 1 \\\\\n\\end{bmatrix}$,\n\nthen \n\n$A+B = \\begin{bmatrix} \n7 & 5 \\\\\n4 & 5 \\\\\n\\end{bmatrix}$\n\nand \n\n$A-B = \\begin{bmatrix} \n-3 & 1 \\\\\n-2 & 3 \\\\\n\\end{bmatrix}$",
77
+ images: {},
78
+ widgets: {},
79
+ },
80
+ {
81
+ content:
82
+ "## How do we multiply two matrices together?\n\nThis one's a bit trickier. To multiply two matrices together, we take the dot product of the rows from the first matrix with the columns from the second matrix. The result is a new matrix. For example, if \n\n$A = \\begin{bmatrix} \n2 & 3 \\\\\n1 & 4 \\\\\n\\end{bmatrix}$ \n\nand \n\n$B = \\begin{bmatrix} \n5 & 2 \\\\\n3 & 1 \\\\\n\\end{bmatrix}$,\n\nthen \n\n$AB = \\begin{bmatrix} \n2\\cdot5+3\\cdot3 & 2\\cdot2+3\\cdot1 \\\\\n1\\cdot5+4\\cdot3 & 1\\cdot2+4\\cdot1 \\\\\n\\end{bmatrix} = \\begin{bmatrix} \n19 & 7 \\\\\n17 & 6 \\\\\n\\end{bmatrix}.$",
83
+ images: {},
84
+ widgets: {},
85
+ },
86
+ {
87
+ content:
88
+ "## What can we do with the inverse of a matrix?\n\nFinding the inverse of a matrix can be useful for solving linear systems of equations. If $A$ is the matrix representing the system of equations, and $b$ is the vector of solutions, then $Ax=b$. If we can find the inverse of $A$, we can multiply both sides of the equation by it to isolate $x$:\n\n$A^{-1}Ax=A^{-1}b \\Rightarrow x=A^{-1}b.$",
89
+ images: {},
90
+ widgets: {},
91
+ },
92
+ {
93
+ content:
94
+ "## Where are matrices used in the real world?\n\nMatrices have tons of real-world applications! They can be used in computer graphics to perform transformations on images, in physics to model physical systems, and in statistics to analyze data, just to name a few.",
95
+ images: {},
96
+ widgets: {},
97
+ },
98
+ ];
@@ -4,7 +4,10 @@ import userEvent from "@testing-library/user-event";
4
4
  import * as React from "react";
5
5
  import "@testing-library/jest-dom"; // Imports custom matchers
6
6
 
7
- import {testDependencies} from "../../../../testing/test-dependencies";
7
+ import {
8
+ testDependencies,
9
+ testDependenciesV2,
10
+ } from "../../../../testing/test-dependencies";
8
11
  import {
9
12
  itemWithInput,
10
13
  labelImageItem,
@@ -76,6 +79,7 @@ export const renderQuestion = (
76
79
  reviewMode={false}
77
80
  savedState=""
78
81
  controlPeripherals={false}
82
+ dependencies={testDependenciesV2}
79
83
  {...optionalProps}
80
84
  />
81
85
  {/* The ItemRenderer _requires_ two divs: a work area and hints
@@ -132,6 +136,13 @@ describe("item renderer", () => {
132
136
  });
133
137
 
134
138
  beforeEach(() => {
139
+ // Mock ResizeObserver used by the mobile keypad
140
+ window.ResizeObserver = jest.fn().mockImplementation(() => ({
141
+ observe: jest.fn(),
142
+ unobserve: jest.fn(),
143
+ disconnect: jest.fn(),
144
+ }));
145
+
135
146
  jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
136
147
  testDependencies,
137
148
  );
@@ -149,7 +149,6 @@ describe("renderer", () => {
149
149
  mockRunLinter.mockClear();
150
150
 
151
151
  // Act
152
- // @ts-expect-error - TS2345 - Argument of type '{ problemNum: number; linterContext: { readonly contentType: "exercise"; readonly highlightLint: true; readonly paths: readonly []; readonly stack: readonly []; }; }' is not assignable to parameter of type 'Pick<{}, never> & InexactPartial<Pick<{}, never>> & InexactPartial<Pick<DefaultProps, keyof DefaultProps>>'.
153
152
  rerender(question1, {...extraProps, problemNum: 1});
154
153
 
155
154
  // Assert
@@ -1334,7 +1333,6 @@ describe("renderer", () => {
1334
1333
  },
1335
1334
  },
1336
1335
  {},
1337
- // @ts-expect-error - TS2345 - Argument of type '{ problemNum: number; }' is not assignable to parameter of type 'Pick<{}, never> & InexactPartial<Pick<{}, never>> & InexactPartial<Pick<DefaultProps, keyof DefaultProps>>'.
1338
1336
  {problemNum: 1},
1339
1337
  );
1340
1338
 
@@ -4,7 +4,10 @@ import userEvent from "@testing-library/user-event";
4
4
  import * as React from "react";
5
5
  import "@testing-library/jest-dom"; // Imports custom matchers
6
6
 
7
- import {testDependencies} from "../../../../testing/test-dependencies";
7
+ import {
8
+ testDependencies,
9
+ testDependenciesV2,
10
+ } from "../../../../testing/test-dependencies";
8
11
  import {
9
12
  itemWithInput,
10
13
  mockedItem,
@@ -25,6 +28,7 @@ import MockWidgetExport from "./mock-widget";
25
28
  import type {PerseusItem} from "../perseus-types";
26
29
  import type {APIOptions} from "../types";
27
30
  import type {MockAssetLoadingWidget} from "./mock-asset-loading-widget";
31
+ import type {PropsFor} from "@khanacademy/wonder-blocks-core";
28
32
 
29
33
  // This looks alot like `widgets/__tests__/renderQuestion.jsx', except we use
30
34
  // the ServerItemRenderer instead of Renderer
@@ -32,10 +36,7 @@ const renderQuestion = (
32
36
  question: PerseusItem,
33
37
  apiOptions: APIOptions = Object.freeze({}),
34
38
  optionalProps: Partial<
35
- JSX.LibraryManagedAttributes<
36
- typeof WrappedServerItemRenderer,
37
- React.ComponentProps<typeof WrappedServerItemRenderer>
38
- >
39
+ PropsFor<typeof WrappedServerItemRenderer>
39
40
  > = Object.freeze({}),
40
41
  ): {
41
42
  container: HTMLElement;
@@ -51,6 +52,7 @@ const renderQuestion = (
51
52
  item={question}
52
53
  problemNum={0}
53
54
  reviewMode={false}
55
+ dependencies={testDependenciesV2}
54
56
  {...optionalProps}
55
57
  />
56
58
  </RenderStateRoot>,
@@ -228,6 +230,7 @@ describe("server item renderer", () => {
228
230
  item={itemWithInput}
229
231
  problemNum={0}
230
232
  reviewMode={false}
233
+ dependencies={testDependenciesV2}
231
234
  />
232
235
  </RenderStateRoot>,
233
236
  );
@@ -243,6 +246,7 @@ describe("server item renderer", () => {
243
246
  item={itemWithInput}
244
247
  problemNum={1} // to force componentDidUpdate
245
248
  reviewMode={false}
249
+ dependencies={testDependenciesV2}
246
250
  />
247
251
  ,
248
252
  </RenderStateRoot>,
@@ -272,6 +276,7 @@ describe("server item renderer", () => {
272
276
  problemNum={0}
273
277
  reviewMode={false}
274
278
  onRendered={onRendered}
279
+ dependencies={testDependenciesV2}
275
280
  />
276
281
  </RenderStateRoot>,
277
282
  );
@@ -293,6 +298,7 @@ describe("server item renderer", () => {
293
298
  problemNum={1}
294
299
  reviewMode={false}
295
300
  onRendered={onRendered}
301
+ dependencies={testDependenciesV2}
296
302
  />
297
303
  </RenderStateRoot>,
298
304
  );
@@ -7,6 +7,7 @@ import * as PerseusLinter from "@khanacademy/perseus-linter";
7
7
  import classNames from "classnames";
8
8
  import * as React from "react";
9
9
 
10
+ import {DependenciesContext} from "./dependencies";
10
11
  import ProvideKeypad from "./mixins/provide-keypad";
11
12
  import {ClassNames as ApiClassNames, ApiOptions} from "./perseus-api";
12
13
  import Renderer from "./renderer";
@@ -14,13 +15,12 @@ import Util from "./util";
14
15
 
15
16
  import type {KeypadProps} from "./mixins/provide-keypad";
16
17
  import type {PerseusRenderer} from "./perseus-types";
17
- import type {APIOptions} from "./types";
18
+ import type {APIOptions, PerseusDependenciesV2} from "./types";
18
19
  import type {LinterContextProps} from "@khanacademy/perseus-linter";
19
20
 
20
21
  type Props = {
21
22
  apiOptions: APIOptions;
22
23
  json: PerseusRenderer | ReadonlyArray<PerseusRenderer>;
23
-
24
24
  // Whether to use the new Bibliotron styles for articles
25
25
  /**
26
26
  * @deprecated Does nothing
@@ -28,6 +28,8 @@ type Props = {
28
28
  useNewStyles: boolean;
29
29
  linterContext: LinterContextProps;
30
30
  legacyPerseusLint?: ReadonlyArray<string>;
31
+
32
+ dependencies: PerseusDependenciesV2;
31
33
  } & KeypadProps;
32
34
 
33
35
  type DefaultProps = {
@@ -180,34 +182,40 @@ class ArticleRenderer extends React.Component<Props, State> {
180
182
  const sections = this._sections().map((section, i) => {
181
183
  const refForSection = `section-${i}`;
182
184
  return (
183
- <div key={i} className="clearfix">
184
- <Renderer
185
- {...section}
186
- ref={refForSection}
187
- key={i}
188
- key_={i}
189
- keypadElement={this.keypadElement()}
190
- apiOptions={{
191
- ...apiOptions,
192
- onFocusChange: (newFocusPath, oldFocusPath) => {
193
- // Prefix the paths with the relevant section,
194
- // so as to allow us to distinguish between
195
- // equivalently-named inputs across Renderers.
196
- this._handleFocusChange(
197
- newFocusPath &&
198
- [refForSection].concat(newFocusPath),
199
- oldFocusPath &&
200
- [refForSection].concat(oldFocusPath),
201
- );
202
- },
203
- }}
204
- linterContext={PerseusLinter.pushContextStack(
205
- this.props.linterContext,
206
- "article",
207
- )}
208
- legacyPerseusLint={this.props.legacyPerseusLint}
209
- />
210
- </div>
185
+ <DependenciesContext.Provider value={this.props.dependencies}>
186
+ <div key={i} className="clearfix">
187
+ <Renderer
188
+ {...section}
189
+ ref={refForSection}
190
+ key={i}
191
+ key_={i}
192
+ keypadElement={this.keypadElement()}
193
+ apiOptions={{
194
+ ...apiOptions,
195
+ onFocusChange: (newFocusPath, oldFocusPath) => {
196
+ // Prefix the paths with the relevant section,
197
+ // so as to allow us to distinguish between
198
+ // equivalently-named inputs across Renderers.
199
+ this._handleFocusChange(
200
+ newFocusPath &&
201
+ [refForSection].concat(
202
+ newFocusPath,
203
+ ),
204
+ oldFocusPath &&
205
+ [refForSection].concat(
206
+ oldFocusPath,
207
+ ),
208
+ );
209
+ },
210
+ }}
211
+ linterContext={PerseusLinter.pushContextStack(
212
+ this.props.linterContext,
213
+ "article",
214
+ )}
215
+ legacyPerseusLint={this.props.legacyPerseusLint}
216
+ />
217
+ </div>
218
+ </DependenciesContext.Provider>
211
219
  );
212
220
  });
213
221
 
@@ -9,10 +9,9 @@ import * as React from "react";
9
9
 
10
10
  import ReactComponentsInfoTip from "./info-tip/info-tip";
11
11
 
12
- type Props = JSX.LibraryManagedAttributes<
13
- typeof ReactComponentsInfoTip,
14
- React.ComponentProps<typeof ReactComponentsInfoTip>
15
- >;
12
+ import type {PropsFor} from "@khanacademy/wonder-blocks-core";
13
+
14
+ type Props = PropsFor<typeof ReactComponentsInfoTip>;
16
15
 
17
16
  type State = {
18
17
  didMount: boolean;
@@ -12,7 +12,7 @@ import MathOutput from "./math-output";
12
12
  import TextInput from "./text-input";
13
13
  import Tooltip, {HorizontalDirection, VerticalDirection} from "./tooltip";
14
14
 
15
- import type {LinterContextProps} from "../types";
15
+ import type {LinterContextProps} from "@khanacademy/perseus-linter";
16
16
  import type {StyleType} from "@khanacademy/wonder-blocks-core";
17
17
 
18
18
  const {captureScratchpadTouchStart} = Util;
@@ -13,8 +13,8 @@ import {ClassNames as ApiClassNames} from "../perseus-api";
13
13
  import Renderer from "../renderer";
14
14
  import Util from "../util";
15
15
 
16
- import type {LinterContextProps} from "../types";
17
16
  import type {Position} from "../util";
17
+ import type {LinterContextProps} from "@khanacademy/perseus-linter";
18
18
 
19
19
  export enum Layout {
20
20
  HORIZONTAL = "horizontal",
@@ -1,4 +1,6 @@
1
- import type {PerseusDependencies} from "./types";
1
+ import * as React from "react";
2
+
3
+ import type {PerseusDependencies, PerseusDependenciesV2} from "./types";
2
4
 
3
5
  let _dependencies: PerseusDependencies | null | undefined = null;
4
6
 
@@ -19,3 +21,16 @@ export const getDependencies = (): PerseusDependencies => {
19
21
  ].join("\n"),
20
22
  );
21
23
  };
24
+
25
+ export const DependenciesContext = React.createContext<PerseusDependenciesV2>({
26
+ analytics: {onAnalyticsEvent: async () => {}},
27
+ });
28
+
29
+ /**
30
+ * useDependencies provides easy access to the current Perseus dependencies.
31
+ */
32
+ export const useDependencies = (): PerseusDependenciesV2 => {
33
+ const deps = React.useContext(DependenciesContext);
34
+
35
+ return deps;
36
+ };
@@ -8,7 +8,8 @@ import Renderer from "./renderer";
8
8
  import {baseUnitPx, hintBorderWidth, kaGreen, gray97} from "./styles/constants";
9
9
  import mediaQueries from "./styles/media-queries";
10
10
 
11
- import type {APIOptions, LinterContextProps} from "./types";
11
+ import type {APIOptions} from "./types";
12
+ import type {LinterContextProps} from "@khanacademy/perseus-linter";
12
13
 
13
14
  type Props = {
14
15
  apiOptions: APIOptions;
@@ -22,12 +22,9 @@ import Util from "./util";
22
22
 
23
23
  import type Renderer from "./renderer";
24
24
  import type {APIOptionsWithDefaults} from "./types";
25
+ import type {PropsFor} from "@khanacademy/wonder-blocks-core";
25
26
 
26
- type Props = JSX.LibraryManagedAttributes<
27
- Renderer,
28
- // @ts-expect-error - TS2344 - Type 'Renderer' does not satisfy the constraint 'keyof IntrinsicElements | JSXElementConstructor<any>'.
29
- React.ComponentProps<typeof Renderer>
30
- > & {
27
+ type Props = PropsFor<typeof Renderer> & {
31
28
  className?: string;
32
29
  // note (mcurtis): I think this should be $ReadOnlyArray<PerseusRenderer>,
33
30
  // but things spiraled out of control when I tried to change it
@@ -36,7 +33,6 @@ type Props = JSX.LibraryManagedAttributes<
36
33
  };
37
34
 
38
35
  type DefaultProps = {
39
- // @ts-expect-error - TS2339 - Property 'linterContext' does not exist on type '{ className?: string | undefined; hints: readonly any[]; hintsVisible?: number | undefined; }'.
40
36
  linterContext: Props["linterContext"];
41
37
  };
42
38
 
@@ -126,7 +122,6 @@ class HintsRenderer extends React.Component<Props, State> {
126
122
  // false in hints.
127
123
  return {
128
124
  ...ApiOptions.defaults,
129
- // @ts-expect-error - TS2339 - Property 'apiOptions' does not exist on type 'Readonly<{ className?: string | undefined; hints: readonly any[]; hintsVisible?: number | undefined; }> & Readonly<{ children?: ReactNode; }>'.
130
125
  ...this.props.apiOptions,
131
126
  readOnly: false,
132
127
  };
@@ -196,10 +191,8 @@ class HintsRenderer extends React.Component<Props, State> {
196
191
  ref={"hintRenderer" + i}
197
192
  key={"hintRenderer" + i}
198
193
  apiOptions={apiOptions}
199
- // @ts-expect-error - TS2339 - Property 'findExternalWidgets' does not exist on type 'Readonly<{ className?: string | undefined; hints: readonly any[]; hintsVisible?: number | undefined; }> & Readonly<{ children?: ReactNode; }>'.
200
194
  findExternalWidgets={this.props.findExternalWidgets}
201
195
  linterContext={PerseusLinter.pushContextStack(
202
- // @ts-expect-error - TS2339 - Property 'linterContext' does not exist on type 'Readonly<{ className?: string | undefined; hints: readonly any[]; hintsVisible?: number | undefined; }> & Readonly<{ children?: ReactNode; }>'.
203
196
  this.props.linterContext,
204
197
  "hints[" + i + "]",
205
198
  )}
package/src/index.ts CHANGED
@@ -109,7 +109,6 @@ export type {
109
109
  ImageUploader,
110
110
  JiptLabelStore,
111
111
  JiptRenderer,
112
- LinterContextProps,
113
112
  PerseusDependencies,
114
113
  PerseusScore,
115
114
  Version,
@@ -8,12 +8,12 @@ function _noop() {}
8
8
  /**
9
9
  * Wrapper for the trackInteraction apiOption.
10
10
  */
11
- class InteractionTracker {
11
+ class InteractionTracker<T> {
12
12
  // @ts-expect-error - TS2564 - Property '_tracked' has no initializer and is not definitely assigned in the constructor.
13
13
  _tracked: boolean;
14
14
  // @ts-expect-error - TS2564 - Property 'setting' has no initializer and is not definitely assigned in the constructor.
15
15
  setting: Tracking;
16
- track: (extraData?: any) => void;
16
+ track: (extraData?: T) => void;
17
17
  trackApi: any;
18
18
  // @ts-expect-error - TS2564 - Property 'widgetID' has no initializer and is not definitely assigned in the constructor.
19
19
  widgetID: string;
@@ -46,7 +46,7 @@ class InteractionTracker {
46
46
  * @param extraData Any extra data to track about the event.
47
47
  * @private
48
48
  */
49
- _track: (extraData: Record<string, any>) => void = (extraData) => {
49
+ _track: (extraData: T) => void = (extraData) => {
50
50
  if (this._tracked && !this.setting) {
51
51
  return;
52
52
  }
@@ -6,6 +6,7 @@ import * as React from "react";
6
6
  import * as ReactDOM from "react-dom";
7
7
  import _ from "underscore";
8
8
 
9
+ import {DependenciesContext} from "./dependencies";
9
10
  import HintsRenderer from "./hints-renderer";
10
11
  import Objective from "./interactive2/objective_";
11
12
  import ProvideKeypad from "./mixins/provide-keypad";
@@ -16,8 +17,9 @@ import reactRender from "./util/react-render";
16
17
 
17
18
  import type {KeypadProps} from "./mixins/provide-keypad";
18
19
  import type {PerseusItem} from "./perseus-types";
19
- import type {APIOptions, FocusPath, LinterContextProps} from "./types";
20
+ import type {APIOptions, FocusPath, PerseusDependenciesV2} from "./types";
20
21
  import type {KEScore} from "@khanacademy/perseus-core";
22
+ import type {LinterContextProps} from "@khanacademy/perseus-linter";
21
23
 
22
24
  const {mapObject} = Objective;
23
25
 
@@ -46,6 +48,8 @@ type Props = // These props are used by the ProvideKeypad mixin.
46
48
  savedState: any;
47
49
  linterContext: LinterContextProps;
48
50
  legacyPerseusLint?: ReadonlyArray<string>;
51
+
52
+ dependencies: PerseusDependenciesV2;
49
53
  };
50
54
 
51
55
  type DefaultProps = {
@@ -176,53 +180,58 @@ class ItemRenderer extends React.Component<Props, State> {
176
180
  // strangeness instead of relying on React's normal render() method.
177
181
  // TODO(alpert): Figure out how to clean this up somehow
178
182
  reactRender(
179
- <Renderer
180
- ref={(node) => {
181
- if (!node) {
182
- return;
183
- }
184
- this.questionRenderer = node;
185
-
186
- // NOTE(jeremy): Why don't we just pass this into the
187
- // renderer as a prop?
188
- const {answerableCallback} = apiOptions;
189
- if (answerableCallback) {
190
- const isAnswerable =
191
- this.questionRenderer.emptyWidgets().length === 0;
192
- answerableCallback(isAnswerable);
193
- }
194
- }}
195
- keypadElement={this.keypadElement()}
196
- problemNum={this.props.problemNum}
197
- onInteractWithWidget={this.handleInteractWithWidget}
198
- highlightedWidgets={this.state.questionHighlightedWidgets}
199
- apiOptions={apiOptions}
200
- questionCompleted={this.state.questionCompleted}
201
- reviewMode={this.props.reviewMode}
202
- savedState={this.props.savedState}
203
- linterContext={PerseusLinter.pushContextStack(
204
- this.props.linterContext,
205
- "question",
206
- )}
207
- {...this.props.item.question}
208
- legacyPerseusLint={this.props.legacyPerseusLint}
209
- />,
183
+ <DependenciesContext.Provider value={this.props.dependencies}>
184
+ <Renderer
185
+ ref={(node) => {
186
+ if (!node) {
187
+ return;
188
+ }
189
+ this.questionRenderer = node;
190
+
191
+ // NOTE(jeremy): Why don't we just pass this into the
192
+ // renderer as a prop?
193
+ const {answerableCallback} = apiOptions;
194
+ if (answerableCallback) {
195
+ const isAnswerable =
196
+ this.questionRenderer.emptyWidgets().length ===
197
+ 0;
198
+ answerableCallback(isAnswerable);
199
+ }
200
+ }}
201
+ keypadElement={this.keypadElement()}
202
+ problemNum={this.props.problemNum}
203
+ onInteractWithWidget={this.handleInteractWithWidget}
204
+ highlightedWidgets={this.state.questionHighlightedWidgets}
205
+ apiOptions={apiOptions}
206
+ questionCompleted={this.state.questionCompleted}
207
+ reviewMode={this.props.reviewMode}
208
+ savedState={this.props.savedState}
209
+ linterContext={PerseusLinter.pushContextStack(
210
+ this.props.linterContext,
211
+ "question",
212
+ )}
213
+ {...this.props.item.question}
214
+ legacyPerseusLint={this.props.legacyPerseusLint}
215
+ />
216
+ </DependenciesContext.Provider>,
210
217
  // @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
211
218
  workArea,
212
219
  );
213
220
 
214
221
  reactRender(
215
- <HintsRenderer
216
- ref={(node) => (this.hintsRenderer = node)}
217
- hints={this.props.item.hints}
218
- hintsVisible={this.state.hintsVisible}
219
- // @ts-expect-error - TS2769 - No overload matches this call.
220
- apiOptions={apiOptions}
221
- linterContext={PerseusLinter.pushContextStack(
222
- this.props.linterContext,
223
- "hints",
224
- )}
225
- />,
222
+ <DependenciesContext.Provider value={this.props.dependencies}>
223
+ <HintsRenderer
224
+ ref={(node) => (this.hintsRenderer = node)}
225
+ hints={this.props.item.hints}
226
+ hintsVisible={this.state.hintsVisible}
227
+ apiOptions={apiOptions}
228
+ linterContext={PerseusLinter.pushContextStack(
229
+ this.props.linterContext,
230
+ "hints",
231
+ )}
232
+ />
233
+ </DependenciesContext.Provider>,
234
+
226
235
  // @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
227
236
  hintsArea,
228
237
  );
@@ -504,7 +513,11 @@ class ItemRenderer extends React.Component<Props, State> {
504
513
  }
505
514
 
506
515
  render(): React.ReactNode {
507
- return <div />;
516
+ return (
517
+ <DependenciesContext.Provider value={this.props.dependencies}>
518
+ <div />
519
+ </DependenciesContext.Provider>
520
+ );
508
521
  }
509
522
  }
510
523
 
@@ -4,7 +4,10 @@ import userEvent from "@testing-library/user-event";
4
4
  import * as React from "react";
5
5
  import "@testing-library/jest-dom"; // Imports custom mathers
6
6
 
7
- import {testDependencies} from "../../../../../testing/test-dependencies";
7
+ import {
8
+ testDependencies,
9
+ testDependenciesV2,
10
+ } from "../../../../../testing/test-dependencies";
8
11
  import MockWidgetExport from "../../__tests__/mock-widget";
9
12
  import * as Dependencies from "../../dependencies";
10
13
  import {registerAllWidgetsForTesting} from "../../util/register-all-widgets-for-testing";
@@ -50,6 +53,7 @@ const renderSimpleQuestion = (question: Item) => {
50
53
  item={question}
51
54
  shape={simpleQuestionShape}
52
55
  ref={(r) => (renderer = r)}
56
+ dependencies={testDependenciesV2}
53
57
  >
54
58
  {({renderers}) => <SimpleLayout renderers={renderers} />}
55
59
  </MultiRenderer>
@@ -119,6 +123,7 @@ describe("multi-item renderer", () => {
119
123
  <MultiRenderer
120
124
  item={question1}
121
125
  shape={shapes.shape({broken: shapes.content})}
126
+ dependencies={testDependenciesV2}
122
127
  >
123
128
  {({renderers}) => {
124
129
  return <div />;