@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.
- package/CHANGELOG.md +33 -0
- package/dist/article-renderer.d.ts +2 -1
- package/dist/components/info-tip.d.ts +2 -1
- package/dist/components/input-with-examples.d.ts +1 -1
- package/dist/components/sortable.d.ts +1 -1
- package/dist/dependencies.d.ts +7 -1
- package/dist/es/index.css +3 -5
- package/dist/es/index.js +72 -45
- package/dist/es/index.js.map +1 -1
- package/dist/hint-renderer.d.ts +2 -1
- package/dist/hints-renderer.d.ts +2 -1
- package/dist/index.css +3 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +72 -46
- package/dist/index.js.map +1 -1
- package/dist/interaction-tracker.d.ts +3 -3
- package/dist/item-renderer.d.ts +3 -1
- package/dist/multi-items/multi-renderer.d.ts +4 -2
- package/dist/perseus-api.d.ts +1 -1
- package/dist/renderer.d.ts +4 -9
- package/dist/server-item-renderer.d.ts +3 -1
- package/dist/types.d.ts +30 -15
- package/dist/widget-container.d.ts +2 -1
- package/dist/widgets/expression.d.ts +409 -12
- package/dist/widgets/graded-group.d.ts +2 -2
- package/dist/widgets/grapher.d.ts +2 -1
- package/dist/widgets/passage-ref.d.ts +2 -1
- package/dist/widgets/sequence.d.ts +2 -2
- package/package.json +3 -3
- package/src/__stories__/article-renderer.stories.tsx +35 -2
- package/src/__testdata__/article-renderer.testdata.ts +71 -0
- package/src/__tests__/item-renderer.test.tsx +12 -1
- package/src/__tests__/renderer.test.tsx +0 -2
- package/src/__tests__/server-item-renderer.test.tsx +11 -5
- package/src/article-renderer.tsx +38 -30
- package/src/components/info-tip.tsx +3 -4
- package/src/components/input-with-examples.tsx +1 -1
- package/src/components/sortable.tsx +1 -1
- package/src/dependencies.ts +16 -1
- package/src/hint-renderer.tsx +2 -1
- package/src/hints-renderer.tsx +2 -9
- package/src/index.ts +0 -1
- package/src/interaction-tracker.ts +3 -3
- package/src/item-renderer.tsx +57 -44
- package/src/multi-items/__tests__/multi-renderer.test.tsx +6 -1
- package/src/multi-items/multi-renderer.tsx +18 -11
- package/src/renderer.tsx +4 -4
- package/src/server-item-renderer.tsx +21 -17
- package/src/types.ts +50 -21
- package/src/widget-container.tsx +2 -1
- package/src/widgets/__stories__/definition.stories.tsx +8 -1
- package/src/widgets/__stories__/expression.stories.tsx +2 -2
- package/src/widgets/__tests__/{expression.test.ts → expression.test.tsx} +73 -8
- package/src/widgets/__tests__/renderQuestion.tsx +29 -29
- package/src/widgets/expression.tsx +77 -48
- package/src/widgets/graded-group.tsx +11 -2
- package/src/widgets/grapher.tsx +4 -12
- package/src/widgets/passage-ref.tsx +1 -1
- package/src/widgets/sequence.tsx +10 -2
- 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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
);
|
package/src/article-renderer.tsx
CHANGED
|
@@ -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
|
-
<
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
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 "
|
|
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",
|
package/src/dependencies.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
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
|
+
};
|
package/src/hint-renderer.tsx
CHANGED
|
@@ -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
|
|
11
|
+
import type {APIOptions} from "./types";
|
|
12
|
+
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
12
13
|
|
|
13
14
|
type Props = {
|
|
14
15
|
apiOptions: APIOptions;
|
package/src/hints-renderer.tsx
CHANGED
|
@@ -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 =
|
|
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
|
@@ -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?:
|
|
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:
|
|
49
|
+
_track: (extraData: T) => void = (extraData) => {
|
|
50
50
|
if (this._tracked && !this.setting) {
|
|
51
51
|
return;
|
|
52
52
|
}
|
package/src/item-renderer.tsx
CHANGED
|
@@ -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,
|
|
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
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.props.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
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 {
|
|
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 />;
|