@khanacademy/perseus 8.0.0 → 9.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 (39) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/article-renderer.d.ts +2 -1
  3. package/dist/dependencies.d.ts +7 -1
  4. package/dist/es/index.js +66 -29
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.js +66 -29
  7. package/dist/index.js.map +1 -1
  8. package/dist/interaction-tracker.d.ts +3 -3
  9. package/dist/item-renderer.d.ts +2 -1
  10. package/dist/multi-items/multi-renderer.d.ts +2 -1
  11. package/dist/perseus-api.d.ts +1 -1
  12. package/dist/renderer.d.ts +1 -1
  13. package/dist/server-item-renderer.d.ts +3 -1
  14. package/dist/types.d.ts +29 -9
  15. package/dist/widgets/expression.d.ts +409 -12
  16. package/dist/widgets/graded-group.d.ts +2 -2
  17. package/dist/widgets/sequence.d.ts +2 -2
  18. package/package.json +3 -3
  19. package/src/__stories__/article-renderer.stories.tsx +35 -2
  20. package/src/__testdata__/article-renderer.testdata.ts +71 -0
  21. package/src/__tests__/item-renderer.test.tsx +12 -1
  22. package/src/__tests__/server-item-renderer.test.tsx +9 -1
  23. package/src/article-renderer.tsx +38 -30
  24. package/src/dependencies.ts +16 -1
  25. package/src/interaction-tracker.ts +3 -3
  26. package/src/item-renderer.tsx +62 -44
  27. package/src/multi-items/__tests__/multi-renderer.test.tsx +6 -1
  28. package/src/multi-items/multi-renderer.tsx +16 -4
  29. package/src/renderer.tsx +1 -1
  30. package/src/server-item-renderer.tsx +21 -16
  31. package/src/types.ts +49 -13
  32. package/src/widgets/__stories__/definition.stories.tsx +8 -1
  33. package/src/widgets/__stories__/expression.stories.tsx +2 -2
  34. package/src/widgets/__tests__/{expression.test.ts → expression.test.tsx} +73 -8
  35. package/src/widgets/__tests__/renderQuestion.tsx +26 -18
  36. package/src/widgets/expression.tsx +82 -48
  37. package/src/widgets/graded-group.tsx +11 -2
  38. package/src/widgets/sequence.tsx +10 -2
  39. package/tsconfig-build.tsbuildinfo +1 -1
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import type { PerseusGradedGroupWidgetOptions } from "../perseus-types";
3
- import type { WidgetProps } from "../types";
3
+ import type { TrackingGradedGroupExtraArguments, WidgetProps } from "../types";
4
4
  import type { ANSWER_BAR_STATES } from "./graded-group-answer-bar";
5
5
  declare const GRADING_STATUSES: {
6
6
  readonly ungraded: "ungraded";
@@ -10,7 +10,7 @@ declare const GRADING_STATUSES: {
10
10
  };
11
11
  type Rubric = PerseusGradedGroupWidgetOptions;
12
12
  type RenderProps = PerseusGradedGroupWidgetOptions;
13
- type Props = WidgetProps<RenderProps, Rubric> & {
13
+ type Props = WidgetProps<RenderProps, Rubric, TrackingGradedGroupExtraArguments> & {
14
14
  inGradedGroupSet?: boolean;
15
15
  onNextQuestion?: () => unknown;
16
16
  };
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
2
  import type { PerseusSequenceWidgetOptions } from "../perseus-types";
3
- import type { WidgetProps } from "../types";
3
+ import type { TrackingSequenceExtraArguments, WidgetProps } from "../types";
4
4
  type Rubric = PerseusSequenceWidgetOptions;
5
- type ExternalProps = WidgetProps<PerseusSequenceWidgetOptions, Rubric>;
5
+ type ExternalProps = WidgetProps<PerseusSequenceWidgetOptions, Rubric, TrackingSequenceExtraArguments>;
6
6
  type Props = ExternalProps;
7
7
  type DefaultProps = {
8
8
  json: Props["json"];
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": "8.0.0",
6
+ "version": "9.0.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -23,8 +23,8 @@
23
23
  "dependencies": {
24
24
  "@khanacademy/kas": "^0.3.1",
25
25
  "@khanacademy/kmath": "^0.1.2",
26
- "@khanacademy/math-input": "^9.0.0",
27
- "@khanacademy/perseus-core": "0.1.1",
26
+ "@khanacademy/math-input": "^10.0.0",
27
+ "@khanacademy/perseus-core": "0.2.0",
28
28
  "@khanacademy/perseus-linter": "^0.3.2",
29
29
  "@khanacademy/pure-markdown": "^0.2.4",
30
30
  "@khanacademy/simple-markdown": "^0.10.0"
@@ -1,6 +1,11 @@
1
1
  import React from "react";
2
2
 
3
- import {passageArticle} from "../__testdata__/article-renderer.testdata";
3
+ import {storybookDependenciesV2} from "../../../../testing/test-dependencies";
4
+ import {
5
+ singleSectionArticle,
6
+ multiSectionArticle,
7
+ passageArticle,
8
+ } from "../__testdata__/article-renderer.testdata";
4
9
  import ArticleRenderer from "../article-renderer";
5
10
 
6
11
  export default {
@@ -12,6 +17,34 @@ export default {
12
17
  },
13
18
  };
14
19
 
20
+ export const ASingleSectionArticle = (args: {
21
+ useNewStyles;
22
+ }): React.ReactElement => {
23
+ return (
24
+ <ArticleRenderer
25
+ json={singleSectionArticle}
26
+ dependencies={storybookDependenciesV2}
27
+ useNewStyles={args.useNewStyles}
28
+ />
29
+ );
30
+ };
31
+
32
+ export const BMultiSectionArticle = (args: {
33
+ useNewStyles;
34
+ }): React.ReactElement => {
35
+ return (
36
+ <ArticleRenderer
37
+ json={multiSectionArticle}
38
+ dependencies={storybookDependenciesV2}
39
+ useNewStyles={args.useNewStyles}
40
+ />
41
+ );
42
+ };
43
+
15
44
  export const PassageArticle = ({useNewStyles}): any => (
16
- <ArticleRenderer json={passageArticle} useNewStyles={useNewStyles} />
45
+ <ArticleRenderer
46
+ json={passageArticle}
47
+ dependencies={storybookDependenciesV2}
48
+ useNewStyles={useNewStyles}
49
+ />
17
50
  );
@@ -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
  );
@@ -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,
@@ -51,6 +54,7 @@ const renderQuestion = (
51
54
  item={question}
52
55
  problemNum={0}
53
56
  reviewMode={false}
57
+ dependencies={testDependenciesV2}
54
58
  {...optionalProps}
55
59
  />
56
60
  </RenderStateRoot>,
@@ -228,6 +232,7 @@ describe("server item renderer", () => {
228
232
  item={itemWithInput}
229
233
  problemNum={0}
230
234
  reviewMode={false}
235
+ dependencies={testDependenciesV2}
231
236
  />
232
237
  </RenderStateRoot>,
233
238
  );
@@ -243,6 +248,7 @@ describe("server item renderer", () => {
243
248
  item={itemWithInput}
244
249
  problemNum={1} // to force componentDidUpdate
245
250
  reviewMode={false}
251
+ dependencies={testDependenciesV2}
246
252
  />
247
253
  ,
248
254
  </RenderStateRoot>,
@@ -272,6 +278,7 @@ describe("server item renderer", () => {
272
278
  problemNum={0}
273
279
  reviewMode={false}
274
280
  onRendered={onRendered}
281
+ dependencies={testDependenciesV2}
275
282
  />
276
283
  </RenderStateRoot>,
277
284
  );
@@ -293,6 +300,7 @@ describe("server item renderer", () => {
293
300
  problemNum={1}
294
301
  reviewMode={false}
295
302
  onRendered={onRendered}
303
+ dependencies={testDependenciesV2}
296
304
  />
297
305
  </RenderStateRoot>,
298
306
  );
@@ -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
 
@@ -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,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,7 +17,12 @@ 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 {
21
+ APIOptions,
22
+ FocusPath,
23
+ LinterContextProps,
24
+ PerseusDependenciesV2,
25
+ } from "./types";
20
26
  import type {KEScore} from "@khanacademy/perseus-core";
21
27
 
22
28
  const {mapObject} = Objective;
@@ -46,6 +52,8 @@ type Props = // These props are used by the ProvideKeypad mixin.
46
52
  savedState: any;
47
53
  linterContext: LinterContextProps;
48
54
  legacyPerseusLint?: ReadonlyArray<string>;
55
+
56
+ dependencies: PerseusDependenciesV2;
49
57
  };
50
58
 
51
59
  type DefaultProps = {
@@ -176,53 +184,59 @@ class ItemRenderer extends React.Component<Props, State> {
176
184
  // strangeness instead of relying on React's normal render() method.
177
185
  // TODO(alpert): Figure out how to clean this up somehow
178
186
  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
- />,
187
+ <DependenciesContext.Provider value={this.props.dependencies}>
188
+ <Renderer
189
+ ref={(node) => {
190
+ if (!node) {
191
+ return;
192
+ }
193
+ this.questionRenderer = node;
194
+
195
+ // NOTE(jeremy): Why don't we just pass this into the
196
+ // renderer as a prop?
197
+ const {answerableCallback} = apiOptions;
198
+ if (answerableCallback) {
199
+ const isAnswerable =
200
+ this.questionRenderer.emptyWidgets().length ===
201
+ 0;
202
+ answerableCallback(isAnswerable);
203
+ }
204
+ }}
205
+ keypadElement={this.keypadElement()}
206
+ problemNum={this.props.problemNum}
207
+ onInteractWithWidget={this.handleInteractWithWidget}
208
+ highlightedWidgets={this.state.questionHighlightedWidgets}
209
+ apiOptions={apiOptions}
210
+ questionCompleted={this.state.questionCompleted}
211
+ reviewMode={this.props.reviewMode}
212
+ savedState={this.props.savedState}
213
+ linterContext={PerseusLinter.pushContextStack(
214
+ this.props.linterContext,
215
+ "question",
216
+ )}
217
+ {...this.props.item.question}
218
+ legacyPerseusLint={this.props.legacyPerseusLint}
219
+ />
220
+ </DependenciesContext.Provider>,
210
221
  // @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
211
222
  workArea,
212
223
  );
213
224
 
214
225
  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
- />,
226
+ <DependenciesContext.Provider value={this.props.dependencies}>
227
+ <HintsRenderer
228
+ ref={(node) => (this.hintsRenderer = node)}
229
+ hints={this.props.item.hints}
230
+ hintsVisible={this.state.hintsVisible}
231
+ // @ts-expect-error - TS2769 - No overload matches this call.
232
+ apiOptions={apiOptions}
233
+ linterContext={PerseusLinter.pushContextStack(
234
+ this.props.linterContext,
235
+ "hints",
236
+ )}
237
+ />
238
+ </DependenciesContext.Provider>,
239
+
226
240
  // @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
227
241
  hintsArea,
228
242
  );
@@ -504,7 +518,11 @@ class ItemRenderer extends React.Component<Props, State> {
504
518
  }
505
519
 
506
520
  render(): React.ReactNode {
507
- return <div />;
521
+ return (
522
+ <DependenciesContext.Provider value={this.props.dependencies}>
523
+ <div />
524
+ </DependenciesContext.Provider>
525
+ );
508
526
  }
509
527
  }
510
528
 
@@ -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 />;
@@ -41,6 +41,7 @@ import {StyleSheet, css} from "aphrodite"; // eslint-disable-line import/no-extr
41
41
  import lens from "hubble"; // eslint-disable-line import/no-extraneous-dependencies
42
42
  import * as React from "react";
43
43
 
44
+ import {DependenciesContext} from "../dependencies";
44
45
  import HintsRenderer from "../hints-renderer";
45
46
  import {Errors, Log} from "../logging/log";
46
47
  import Renderer from "../renderer";
@@ -50,7 +51,12 @@ import {itemToTree} from "./items";
50
51
  import {buildMapper} from "./trees";
51
52
 
52
53
  import type {Widget} from "../renderer";
53
- import type {APIOptions, FilterCriterion, PerseusScore} from "../types";
54
+ import type {
55
+ APIOptions,
56
+ FilterCriterion,
57
+ PerseusDependenciesV2,
58
+ PerseusScore,
59
+ } from "../types";
54
60
  import type {Item, ContentNode, HintNode, TagsNode} from "./item-types";
55
61
  import type {Shape, ArrayShape} from "./shape-types";
56
62
  import type {Tree} from "./tree-types";
@@ -109,6 +115,8 @@ type Props = {
109
115
  onInteractWithWidget?: (id: string) => void;
110
116
  apiOptions?: APIOptions;
111
117
  reviewMode?: boolean | null | undefined;
118
+
119
+ dependencies: PerseusDependenciesV2;
112
120
  };
113
121
  type State = {
114
122
  // We cache functions to generate renderers and refs in `rendererDataTree`,
@@ -547,9 +555,13 @@ class MultiRenderer extends React.Component<Props, State> {
547
555
 
548
556
  // Pass the renderer tree to the `children` function, which will
549
557
  // determine the actual content of this component.
550
- return this.props.children({
551
- renderers: this._getRenderers(),
552
- });
558
+ return (
559
+ <DependenciesContext.Provider value={this.props.dependencies}>
560
+ {this.props.children({
561
+ renderers: this._getRenderers(),
562
+ })}
563
+ </DependenciesContext.Provider>
564
+ );
553
565
  }
554
566
  }
555
567
 
package/src/renderer.tsx CHANGED
@@ -213,7 +213,7 @@ class Renderer extends React.Component<Props, State> {
213
213
  _foundTextNodes: boolean;
214
214
  // @ts-expect-error - TS2564 - Property '_interactionTrackers' has no initializer and is not definitely assigned in the constructor.
215
215
  _interactionTrackers: {
216
- [id: string]: InteractionTracker;
216
+ [id: string]: InteractionTracker<any>;
217
217
  };
218
218
  // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
219
219
  _isMounted: boolean;