@khanacademy/perseus 12.2.0 → 12.3.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 +23 -0
- package/dist/article-renderer.d.ts +3 -2
- package/dist/error-boundary.d.ts +1 -0
- package/dist/es/index.css +1 -66
- package/dist/es/index.js +86 -66
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +1 -66
- package/dist/index.js +77 -54
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts +4 -1
- package/dist/styles/constants.d.ts +2 -2
- package/dist/widget-container.d.ts +2 -0
- package/dist/widgets/expression.d.ts +5 -4
- package/package.json +8 -8
- package/src/__tests__/renderer-api.test.tsx +18 -23
- package/src/article-renderer.tsx +15 -13
- package/src/error-boundary.tsx +3 -0
- package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +5 -5
- package/src/renderer.tsx +29 -27
- package/src/server-item-renderer.tsx +1 -0
- package/src/styles/constants.ts +4 -4
- package/src/styles/perseus-renderer.less +0 -10
- package/src/styles/variables.less +2 -2
- package/src/styles/variables.shared.less +2 -2
- package/src/styles/widgets/expression.less +0 -77
- package/src/styles/widgets/passage.less +1 -19
- package/src/widget-container.tsx +32 -10
- package/src/widgets/__stories__/label-image.stories.tsx +30 -0
- package/src/widgets/__testdata__/label-image.testdata.ts +63 -0
- package/src/widgets/__tests__/__snapshots__/group.test.tsx.snap +5 -5
- package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +28 -28
- package/src/widgets/__tests__/expression.test.tsx +12 -9
- package/src/widgets/__tests__/renderQuestion.tsx +30 -13
- package/src/widgets/expression.tsx +19 -25
- package/src/widgets/transformer.tsx +1 -2
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/renderer.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import InteractionTracker from "./interaction-tracker";
|
|
3
3
|
import TranslationLinter from "./translation-linter";
|
|
4
|
+
import WidgetContainer from "./widget-container";
|
|
5
|
+
import type { DependenciesContext } from "./dependencies";
|
|
4
6
|
import type { PerseusRenderer, PerseusWidgetOptions } from "./perseus-types";
|
|
5
7
|
import type { APIOptions, APIOptionsWithDefaults, FilterCriterion, FocusPath, PerseusScore, WidgetInfo, WidgetProps } from "./types";
|
|
6
8
|
import type { LinterContextProps } from "@khanacademy/perseus-linter";
|
|
@@ -37,7 +39,7 @@ export type Widget = {
|
|
|
37
39
|
showRationalesForCurrentlySelectedChoices?: (options?: any) => void;
|
|
38
40
|
examples?: () => ReadonlyArray<string>;
|
|
39
41
|
};
|
|
40
|
-
type Props = {
|
|
42
|
+
type Props = Partial<React.ContextType<typeof DependenciesContext>> & {
|
|
41
43
|
apiOptions?: APIOptions;
|
|
42
44
|
alwaysUpdate?: boolean;
|
|
43
45
|
findExternalWidgets: any;
|
|
@@ -93,6 +95,7 @@ declare class Renderer extends React.Component<Props, State> {
|
|
|
93
95
|
_isMounted: boolean;
|
|
94
96
|
_isTwoColumn: boolean;
|
|
95
97
|
_translationLinter: TranslationLinter;
|
|
98
|
+
_widgetContainers: Map<string, WidgetContainer>;
|
|
96
99
|
lastRenderedMarkdown: React.ReactNode;
|
|
97
100
|
reuseMarkdown: boolean;
|
|
98
101
|
translationIndex: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const baseFontFamily = "'
|
|
2
|
-
export declare const boldFontFamily = "'
|
|
1
|
+
export declare const baseFontFamily = "'Lato', sans-serif";
|
|
2
|
+
export declare const boldFontFamily = "'Lato-Bold', 'Lato', sans-serif";
|
|
3
3
|
export declare const green = "#76A005";
|
|
4
4
|
export declare const kaGreen = "#71B307";
|
|
5
5
|
export declare const blue = "#1C758A";
|
|
@@ -5,6 +5,7 @@ import type { LinterContextProps } from "@khanacademy/perseus-linter";
|
|
|
5
5
|
type Props = {
|
|
6
6
|
shouldHighlight: boolean;
|
|
7
7
|
type: string;
|
|
8
|
+
id: string;
|
|
8
9
|
initialProps: WidgetProps<any, PerseusWidgetOptions>;
|
|
9
10
|
linterContext: LinterContextProps;
|
|
10
11
|
};
|
|
@@ -16,6 +17,7 @@ type State = {
|
|
|
16
17
|
widgetProps: WidgetProps<any, PerseusWidgetOptions>;
|
|
17
18
|
};
|
|
18
19
|
declare class WidgetContainer extends React.Component<Props, State> {
|
|
20
|
+
widgetRef: React.RefObject<React.ComponentType<any>>;
|
|
19
21
|
static defaultProps: DefaultProps;
|
|
20
22
|
state: any;
|
|
21
23
|
componentDidMount(): void;
|
|
@@ -51,7 +51,9 @@ export declare class Expression extends React.Component<Props, ExpressionState>
|
|
|
51
51
|
componentDidUpdate: (prevProps: Props) => void;
|
|
52
52
|
componentWillUnmount: () => void;
|
|
53
53
|
_handleMouseDown: () => void;
|
|
54
|
-
simpleValidate: (rubric: Rubric
|
|
54
|
+
simpleValidate: (rubric: Rubric & {
|
|
55
|
+
scoring?: boolean;
|
|
56
|
+
}, onInputError: OnInputErrorFunctionType) => PerseusScore;
|
|
55
57
|
getUserInput: () => string;
|
|
56
58
|
change: (...args: any) => any | undefined;
|
|
57
59
|
parse: (value: string, props: Props) => any;
|
|
@@ -65,7 +67,6 @@ export declare class Expression extends React.Component<Props, ExpressionState>
|
|
|
65
67
|
getInputPaths: () => ReadonlyArray<ReadonlyArray<any>>;
|
|
66
68
|
getGrammarTypeForPath: (inputPath: string) => string;
|
|
67
69
|
setInputValue: (path: string, newValue: string, cb: () => void) => void;
|
|
68
|
-
sendExpressionEvaluatedEvent(result: "correct" | "incorrect" | "invalid"): void;
|
|
69
70
|
render(): React.ReactNode | React.ReactElement<React.ComponentProps<"div">>;
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
@@ -213,7 +214,7 @@ declare const _default: Readonly<{
|
|
|
213
214
|
onFocus: (blurPath: import("../types").FocusPath) => void;
|
|
214
215
|
times: boolean;
|
|
215
216
|
value: string;
|
|
216
|
-
}, "static" | "trackInteraction" | "onChange" | "
|
|
217
|
+
}, "static" | "trackInteraction" | "onChange" | "analytics" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget"> & {
|
|
217
218
|
value?: string | undefined;
|
|
218
219
|
onFocus?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
|
219
220
|
onBlur?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
|
@@ -419,7 +420,7 @@ declare const _default: Readonly<{
|
|
|
419
420
|
onFocus: (blurPath: import("../types").FocusPath) => void;
|
|
420
421
|
times: boolean;
|
|
421
422
|
value: string;
|
|
422
|
-
}, "static" | "trackInteraction" | "onChange" | "
|
|
423
|
+
}, "static" | "trackInteraction" | "onChange" | "analytics" | "problemNum" | "questionCompleted" | "keypadElement" | "alignment" | "buttonsVisible" | "containerSizeClass" | "widgetId" | "findWidgets" | "keypadConfiguration" | "reviewModeRubric" | "isLastUsedWidget"> & {
|
|
423
424
|
value?: string | undefined;
|
|
424
425
|
onFocus?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
|
425
426
|
onBlur?: ((blurPath: import("../types").FocusPath) => void) | undefined;
|
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": "12.
|
|
6
|
+
"version": "12.3.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"test": "bash -c 'yarn --silent --cwd \"../..\" test ${@:0} $($([[ ${@: -1} = -* ]] || [[ ${@: -1} = bash ]]) && echo $PWD)'"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@khanacademy/kas": "^0.3.
|
|
26
|
-
"@khanacademy/kmath": "^0.1.
|
|
27
|
-
"@khanacademy/math-input": "^14.2.
|
|
28
|
-
"@khanacademy/perseus-core": "1.
|
|
29
|
-
"@khanacademy/perseus-linter": "^0.3.
|
|
30
|
-
"@khanacademy/pure-markdown": "^0.2.
|
|
31
|
-
"@khanacademy/simple-markdown": "^0.10.
|
|
25
|
+
"@khanacademy/kas": "^0.3.4",
|
|
26
|
+
"@khanacademy/kmath": "^0.1.5",
|
|
27
|
+
"@khanacademy/math-input": "^14.2.1",
|
|
28
|
+
"@khanacademy/perseus-core": "1.2.0",
|
|
29
|
+
"@khanacademy/perseus-linter": "^0.3.7",
|
|
30
|
+
"@khanacademy/pure-markdown": "^0.2.9",
|
|
31
|
+
"@khanacademy/simple-markdown": "^0.10.3"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@khanacademy/wonder-blocks-button": "^4.1.3",
|
|
@@ -8,9 +8,10 @@ import TestUtils from "react-dom/test-utils";
|
|
|
8
8
|
import _ from "underscore";
|
|
9
9
|
import "jest-enzyme";
|
|
10
10
|
|
|
11
|
-
import {ClassNames, Dependencies, Renderer} from "@khanacademy/perseus";
|
|
12
|
-
|
|
13
11
|
import {testDependencies} from "../../../../testing/test-dependencies";
|
|
12
|
+
import * as Dependencies from "../dependencies";
|
|
13
|
+
import {ClassNames} from "../perseus-api";
|
|
14
|
+
import Renderer from "../renderer";
|
|
14
15
|
import {registerAllWidgetsForTesting} from "../util/register-all-widgets-for-testing";
|
|
15
16
|
|
|
16
17
|
import imageItem from "./test-items/image-item";
|
|
@@ -18,6 +19,8 @@ import inputNumber1Item from "./test-items/input-number-1-item";
|
|
|
18
19
|
import inputNumber2Item from "./test-items/input-number-2-item";
|
|
19
20
|
import tableItem from "./test-items/table-item";
|
|
20
21
|
|
|
22
|
+
import type {APIOptions} from "../types";
|
|
23
|
+
|
|
21
24
|
const itemWidget = inputNumber1Item;
|
|
22
25
|
|
|
23
26
|
// Returns a promise that will resolve shortly after the end of this
|
|
@@ -30,16 +33,20 @@ const delayedPromise = (value: undefined) => {
|
|
|
30
33
|
return deferred.promise();
|
|
31
34
|
};
|
|
32
35
|
|
|
33
|
-
const renderQuestionArea = function (item, apiOptions): any {
|
|
36
|
+
const renderQuestionArea = function (item, apiOptions?: APIOptions): any {
|
|
34
37
|
const wrapper = mount(
|
|
35
38
|
<RenderStateRoot>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
<Dependencies.DependenciesContext.Provider
|
|
40
|
+
value={{analytics: {onAnalyticsEvent: async () => undefined}}}
|
|
41
|
+
>
|
|
42
|
+
<Renderer
|
|
43
|
+
content={item.question.content}
|
|
44
|
+
images={item.question.images}
|
|
45
|
+
widgets={item.question.widgets}
|
|
46
|
+
problemNum={0}
|
|
47
|
+
apiOptions={apiOptions}
|
|
48
|
+
/>
|
|
49
|
+
</Dependencies.DependenciesContext.Provider>
|
|
43
50
|
</RenderStateRoot>,
|
|
44
51
|
{includeDefaultTestHarness: false},
|
|
45
52
|
);
|
|
@@ -56,7 +63,6 @@ describe("Perseus API", function () {
|
|
|
56
63
|
|
|
57
64
|
describe("setInputValue", function () {
|
|
58
65
|
it("should be able to produce a correctly graded value", function () {
|
|
59
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
60
66
|
const renderer = renderQuestionArea(inputNumber1Item);
|
|
61
67
|
renderer.setInputValue(["input-number 1"], "5");
|
|
62
68
|
const score = renderer.guessAndScore()[1];
|
|
@@ -65,7 +71,6 @@ describe("Perseus API", function () {
|
|
|
65
71
|
});
|
|
66
72
|
|
|
67
73
|
it("should be able to produce a wrong value", function () {
|
|
68
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
69
74
|
const renderer = renderQuestionArea(inputNumber1Item);
|
|
70
75
|
renderer.setInputValue(["input-number 1"], "3", () => {});
|
|
71
76
|
const score = renderer.guessAndScore()[1];
|
|
@@ -74,7 +79,6 @@ describe("Perseus API", function () {
|
|
|
74
79
|
});
|
|
75
80
|
|
|
76
81
|
it("should be able to produce an empty score", function () {
|
|
77
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
78
82
|
const renderer = renderQuestionArea(inputNumber1Item);
|
|
79
83
|
renderer.setInputValue(["input-number 1"], "3");
|
|
80
84
|
let score = renderer.guessAndScore()[1];
|
|
@@ -86,7 +90,6 @@ describe("Perseus API", function () {
|
|
|
86
90
|
});
|
|
87
91
|
|
|
88
92
|
it("should be able to accept a callback", function (done) {
|
|
89
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
90
93
|
const renderer = renderQuestionArea(inputNumber1Item);
|
|
91
94
|
renderer.setInputValue(["input-number 1"], "3", function () {
|
|
92
95
|
const guess = renderer.getUserInput()[0];
|
|
@@ -99,14 +102,12 @@ describe("Perseus API", function () {
|
|
|
99
102
|
|
|
100
103
|
describe("getInputPaths", function () {
|
|
101
104
|
it("should be able to find all the input widgets", function () {
|
|
102
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
103
105
|
const renderer = renderQuestionArea(inputNumber2Item);
|
|
104
106
|
const numPaths = renderer.getInputPaths().length;
|
|
105
107
|
expect(numPaths).toBe(2);
|
|
106
108
|
});
|
|
107
109
|
|
|
108
110
|
it("should be able to find all inputs within widgets", function () {
|
|
109
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
110
111
|
const renderer = renderQuestionArea(tableItem);
|
|
111
112
|
const numPaths = renderer.getInputPaths().length;
|
|
112
113
|
expect(numPaths).toBe(8);
|
|
@@ -115,7 +116,6 @@ describe("Perseus API", function () {
|
|
|
115
116
|
|
|
116
117
|
describe("getDOMNodeForPath", function () {
|
|
117
118
|
it("should find one DOM node per <input>", function () {
|
|
118
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
119
119
|
const renderer = renderQuestionArea(inputNumber2Item);
|
|
120
120
|
const inputPaths = renderer.getInputPaths();
|
|
121
121
|
const allInputs = TestUtils.scryRenderedDOMComponentsWithTag(
|
|
@@ -126,7 +126,6 @@ describe("Perseus API", function () {
|
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
it("should find the right DOM nodes for the <input>s", function () {
|
|
129
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
130
129
|
const renderer = renderQuestionArea(inputNumber2Item);
|
|
131
130
|
const inputPaths = renderer.getInputPaths();
|
|
132
131
|
const allInputs = TestUtils.scryRenderedDOMComponentsWithTag(
|
|
@@ -165,11 +164,7 @@ describe("Perseus API", function () {
|
|
|
165
164
|
// version
|
|
166
165
|
expect(ClassNames.FOCUSED).toBe("perseus-focused");
|
|
167
166
|
|
|
168
|
-
const renderer = renderQuestionArea(inputNumber1Item
|
|
169
|
-
interceptInputFocus: function (widgetId) {
|
|
170
|
-
renderer.setInputValue(widgetId, "5");
|
|
171
|
-
},
|
|
172
|
-
});
|
|
167
|
+
const renderer = renderQuestionArea(inputNumber1Item);
|
|
173
168
|
|
|
174
169
|
const input =
|
|
175
170
|
// @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'querySelector' does not exist on type 'Element | Text'.
|
package/src/article-renderer.tsx
CHANGED
|
@@ -19,19 +19,20 @@ import type {KeypadAPI} from "@khanacademy/math-input";
|
|
|
19
19
|
import type {KeypadContextRendererInterface} from "@khanacademy/perseus-core";
|
|
20
20
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
21
21
|
|
|
22
|
-
type Props =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
type Props = Partial<React.ContextType<typeof DependenciesContext>> &
|
|
23
|
+
KeypadProps & {
|
|
24
|
+
apiOptions: APIOptions;
|
|
25
|
+
json: PerseusRenderer | ReadonlyArray<PerseusRenderer>;
|
|
26
|
+
// Whether to use the new Bibliotron styles for articles
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Does nothing
|
|
29
|
+
*/
|
|
30
|
+
useNewStyles: boolean;
|
|
31
|
+
linterContext: LinterContextProps;
|
|
32
|
+
legacyPerseusLint?: ReadonlyArray<string>;
|
|
33
|
+
keypadElement?: KeypadAPI | null | undefined;
|
|
34
|
+
dependencies: PerseusDependenciesV2;
|
|
35
|
+
};
|
|
35
36
|
|
|
36
37
|
type DefaultProps = {
|
|
37
38
|
apiOptions: Props["apiOptions"];
|
|
@@ -208,6 +209,7 @@ class ArticleRenderer
|
|
|
208
209
|
"article",
|
|
209
210
|
)}
|
|
210
211
|
legacyPerseusLint={this.props.legacyPerseusLint}
|
|
212
|
+
{...this.props.dependencies}
|
|
211
213
|
/>
|
|
212
214
|
</div>
|
|
213
215
|
);
|
package/src/error-boundary.tsx
CHANGED
|
@@ -5,6 +5,9 @@ import {Errors, Log} from "./logging/log";
|
|
|
5
5
|
type Props = {
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
metadata?: Record<string, string>;
|
|
8
|
+
|
|
9
|
+
// A callback that is called when the error boundary traps an error.
|
|
10
|
+
onError?: (error: Error, info: any) => void;
|
|
8
11
|
};
|
|
9
12
|
type State = {
|
|
10
13
|
error: string;
|
|
@@ -218,7 +218,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
|
|
|
218
218
|
style="border-color: transparent; border-radius: 50%;"
|
|
219
219
|
>
|
|
220
220
|
<div
|
|
221
|
-
class="
|
|
221
|
+
class="circle_1w6i3h1"
|
|
222
222
|
data-is-radio-icon="true"
|
|
223
223
|
data-test-id="choice-icon__library-choice-icon"
|
|
224
224
|
style="border-color: rgba(33,36,44,0.64); color: rgba(33, 36, 44, 0.64); border-radius: 24px;"
|
|
@@ -385,7 +385,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
|
|
|
385
385
|
style="border-color: transparent; border-radius: 50%;"
|
|
386
386
|
>
|
|
387
387
|
<div
|
|
388
|
-
class="
|
|
388
|
+
class="circle_1w6i3h1"
|
|
389
389
|
data-is-radio-icon="true"
|
|
390
390
|
data-test-id="choice-icon__library-choice-icon"
|
|
391
391
|
style="border-color: rgba(33,36,44,0.64); color: rgba(33, 36, 44, 0.64); border-radius: 24px;"
|
|
@@ -552,7 +552,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
|
|
|
552
552
|
style="border-color: transparent; border-radius: 50%;"
|
|
553
553
|
>
|
|
554
554
|
<div
|
|
555
|
-
class="
|
|
555
|
+
class="circle_1w6i3h1"
|
|
556
556
|
data-is-radio-icon="true"
|
|
557
557
|
data-test-id="choice-icon__library-choice-icon"
|
|
558
558
|
style="border-color: rgba(33,36,44,0.64); color: rgba(33, 36, 44, 0.64); border-radius: 24px;"
|
|
@@ -719,7 +719,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
|
|
|
719
719
|
style="border-color: transparent; border-radius: 50%;"
|
|
720
720
|
>
|
|
721
721
|
<div
|
|
722
|
-
class="
|
|
722
|
+
class="circle_1w6i3h1"
|
|
723
723
|
data-is-radio-icon="true"
|
|
724
724
|
data-test-id="choice-icon__library-choice-icon"
|
|
725
725
|
style="border-color: rgba(33,36,44,0.64); color: rgba(33, 36, 44, 0.64); border-radius: 24px;"
|
|
@@ -886,7 +886,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
|
|
|
886
886
|
style="border-color: transparent; border-radius: 50%;"
|
|
887
887
|
>
|
|
888
888
|
<div
|
|
889
|
-
class="
|
|
889
|
+
class="circle_1w6i3h1"
|
|
890
890
|
data-is-radio-icon="true"
|
|
891
891
|
data-test-id="choice-icon__library-choice-icon"
|
|
892
892
|
style="border-color: rgba(33,36,44,0.64); color: rgba(33, 36, 44, 0.64); border-radius: 24px;"
|
package/src/renderer.tsx
CHANGED
|
@@ -29,6 +29,7 @@ import preprocessTex from "./util/katex-preprocess";
|
|
|
29
29
|
import WidgetContainer from "./widget-container";
|
|
30
30
|
import * as Widgets from "./widgets";
|
|
31
31
|
|
|
32
|
+
import type {DependenciesContext} from "./dependencies";
|
|
32
33
|
import type {PerseusRenderer, PerseusWidgetOptions} from "./perseus-types";
|
|
33
34
|
import type {
|
|
34
35
|
APIOptions,
|
|
@@ -52,6 +53,8 @@ const noopOnRender = () => {};
|
|
|
52
53
|
|
|
53
54
|
const SHOULD_CLEAR_WIDGETS_PROP_LIST = ["content", "problemNum", "widgets"];
|
|
54
55
|
|
|
56
|
+
const makeContainerId = (id: string) => "container:" + id;
|
|
57
|
+
|
|
55
58
|
// Check if one focus path / id path is a prefix of another
|
|
56
59
|
// The focus path null will never be a prefix of any non-null
|
|
57
60
|
// path, since it represents no focus.
|
|
@@ -144,7 +147,7 @@ export type Widget = {
|
|
|
144
147
|
examples?: () => ReadonlyArray<string>;
|
|
145
148
|
};
|
|
146
149
|
|
|
147
|
-
type Props = {
|
|
150
|
+
type Props = Partial<React.ContextType<typeof DependenciesContext>> & {
|
|
148
151
|
apiOptions?: APIOptions;
|
|
149
152
|
alwaysUpdate?: boolean;
|
|
150
153
|
findExternalWidgets: any;
|
|
@@ -223,6 +226,8 @@ class Renderer extends React.Component<Props, State> {
|
|
|
223
226
|
// The i18n linter.
|
|
224
227
|
_translationLinter: TranslationLinter;
|
|
225
228
|
|
|
229
|
+
_widgetContainers: Map<string, WidgetContainer> = new Map();
|
|
230
|
+
|
|
226
231
|
lastRenderedMarkdown: React.ReactNode;
|
|
227
232
|
// @ts-expect-error - TS2564 - Property 'reuseMarkdown' has no initializer and is not definitely assigned in the constructor.
|
|
228
233
|
reuseMarkdown: boolean;
|
|
@@ -390,9 +395,7 @@ class Renderer extends React.Component<Props, State> {
|
|
|
390
395
|
// they are re-rendered, so even if they've been
|
|
391
396
|
// re-rendered we need to call these methods on them.
|
|
392
397
|
_.each(this.widgetIds, (id) => {
|
|
393
|
-
|
|
394
|
-
const container = this.refs["container:" + id];
|
|
395
|
-
// @ts-expect-error - TS2339 - Property 'replaceWidgetProps' does not exist on type 'ReactInstance'.
|
|
398
|
+
const container = this._widgetContainers.get(makeContainerId(id));
|
|
396
399
|
container && container.replaceWidgetProps(this.getWidgetProps(id));
|
|
397
400
|
});
|
|
398
401
|
|
|
@@ -550,21 +553,25 @@ class Renderer extends React.Component<Props, State> {
|
|
|
550
553
|
// filtered out in this.render(), so we shouldn't have to
|
|
551
554
|
// worry about using this widget key and ref:
|
|
552
555
|
return (
|
|
553
|
-
<
|
|
554
|
-
key={
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
556
|
+
<WidgetContainer
|
|
557
|
+
key={makeContainerId(id)}
|
|
558
|
+
id={id}
|
|
559
|
+
ref={(node) => {
|
|
560
|
+
const containerId = makeContainerId(id);
|
|
561
|
+
if (node != null) {
|
|
562
|
+
this._widgetContainers.set(containerId, node);
|
|
563
|
+
} else {
|
|
564
|
+
this._widgetContainers.delete(containerId);
|
|
565
|
+
}
|
|
566
|
+
}}
|
|
567
|
+
type={type}
|
|
568
|
+
initialProps={this.getWidgetProps(id)}
|
|
569
|
+
shouldHighlight={shouldHighlight}
|
|
570
|
+
linterContext={PerseusLinter.pushContextStack(
|
|
571
|
+
this.props.linterContext,
|
|
572
|
+
"widget",
|
|
573
|
+
)}
|
|
574
|
+
/>
|
|
568
575
|
);
|
|
569
576
|
}
|
|
570
577
|
return null;
|
|
@@ -603,7 +610,6 @@ class Renderer extends React.Component<Props, State> {
|
|
|
603
610
|
|
|
604
611
|
return {
|
|
605
612
|
...widgetProps,
|
|
606
|
-
ref: id,
|
|
607
613
|
widgetId: id,
|
|
608
614
|
alignment: widgetInfo && widgetInfo.alignment,
|
|
609
615
|
// When determining if a widget is static, we verify that the widget is not an
|
|
@@ -853,15 +859,11 @@ class Renderer extends React.Component<Props, State> {
|
|
|
853
859
|
];
|
|
854
860
|
};
|
|
855
861
|
|
|
856
|
-
getWidgetInstance: (id: string) => Widget | null | undefined = (
|
|
857
|
-
id
|
|
858
|
-
): Widget | null | undefined => {
|
|
859
|
-
// eslint-disable-next-line react/no-string-refs
|
|
860
|
-
const ref = this.refs["container:" + id];
|
|
862
|
+
getWidgetInstance: (id: string) => Widget | null | undefined = (id) => {
|
|
863
|
+
const ref = this._widgetContainers.get(makeContainerId(id));
|
|
861
864
|
if (!ref) {
|
|
862
865
|
return null;
|
|
863
866
|
}
|
|
864
|
-
// @ts-expect-error - TS2339 - Property 'getWidget' does not exist on type 'ReactInstance'.
|
|
865
867
|
return ref.getWidget();
|
|
866
868
|
};
|
|
867
869
|
|
|
@@ -1781,7 +1783,7 @@ class Renderer extends React.Component<Props, State> {
|
|
|
1781
1783
|
// widget can be undefined if it hasn't yet been rendered
|
|
1782
1784
|
if (widget && widget.simpleValidate) {
|
|
1783
1785
|
widgetScores[id] = widget.simpleValidate(
|
|
1784
|
-
props?.options,
|
|
1786
|
+
{...props?.options, scoring: true},
|
|
1785
1787
|
onInputError,
|
|
1786
1788
|
);
|
|
1787
1789
|
}
|
package/src/styles/constants.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// @baseFontFamily: "
|
|
2
|
-
export const baseFontFamily = "'
|
|
3
|
-
// @boldFontFamily: "
|
|
4
|
-
export const boldFontFamily = "'
|
|
1
|
+
// @baseFontFamily: "Lato", sans-serif;
|
|
2
|
+
export const baseFontFamily = "'Lato', sans-serif";
|
|
3
|
+
// @boldFontFamily: "Lato-Bold", "Lato", sans-serif;
|
|
4
|
+
export const boldFontFamily = "'Lato-Bold', 'Lato', sans-serif";
|
|
5
5
|
// @green: #76a005;
|
|
6
6
|
export const green = "#76A005";
|
|
7
7
|
// @kaGreen: #71b307;
|
|
@@ -113,16 +113,6 @@
|
|
|
113
113
|
|
|
114
114
|
// HACK(yejia): Override the font size and line height for blurbs
|
|
115
115
|
//
|
|
116
|
-
// NOTE(yejia): Minion Pro is only loaded on GTP pages, because those
|
|
117
|
-
// are the main call sites for blurbs and the passage widget. We
|
|
118
|
-
// considered inserting the relevant stylesheet whenever the passage
|
|
119
|
-
// widget loads, but this approach produced a "flash of unstyled
|
|
120
|
-
// text", so we decided to embed the stylesheet into the relevant
|
|
121
|
-
// page templates instead.
|
|
122
|
-
//
|
|
123
|
-
// NOTE(yejia): Minion Pro will not be available on ZNDs, due to
|
|
124
|
-
// restrictions in our Adobe TypeKit configuration.
|
|
125
|
-
//
|
|
126
116
|
// TODO(TP-3315): Remove once Perseus uses Wonder Blocks BodySerif
|
|
127
117
|
.test-prep-blurb div.paragraph {
|
|
128
118
|
font-size: 16px;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Steal from webapp/stylesheets/shared-package/variables.less
|
|
2
|
-
@baseFontFamily: "
|
|
3
|
-
@boldFontFamily: "
|
|
2
|
+
@baseFontFamily: "Lato", sans-serif;
|
|
3
|
+
@boldFontFamily: "Lato-Bold", "Lato", sans-serif;
|
|
4
4
|
@green: #76a005;
|
|
5
5
|
@kaGreen: #71b307;
|
|
6
6
|
@blue: #1c758a;
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
|
|
87
87
|
// Typography
|
|
88
88
|
@baseFontSize: 14px;
|
|
89
|
-
// The body of the page is setup to use 'Helvetica' by default and
|
|
90
|
-
//
|
|
89
|
+
// The body of the page is setup to use 'Helvetica' by default and
|
|
90
|
+
// 'Lato' if/once it downloads.
|
|
91
91
|
// For your elements to follow this pattern, all you need is to make the text
|
|
92
92
|
// in your element inherit from the body using the "inherit" value.
|
|
93
93
|
@baseFontFamily: inherit;
|
|
@@ -63,80 +63,3 @@
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
// Old expression widget
|
|
68
|
-
.perseus-widget-expression-old {
|
|
69
|
-
&,
|
|
70
|
-
> .output,
|
|
71
|
-
> .output > .tex,
|
|
72
|
-
> .output > .placeholder {
|
|
73
|
-
display: block;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.perseus-widget-expression-old input,
|
|
78
|
-
#answer_area .perseus-widget-expression-old input {
|
|
79
|
-
direction: ltr;
|
|
80
|
-
border: 1px solid #a4a4a4;
|
|
81
|
-
border-radius: 5px;
|
|
82
|
-
box-sizing: border-box;
|
|
83
|
-
font-size: 14px;
|
|
84
|
-
margin-bottom: 5px;
|
|
85
|
-
max-width: 240px;
|
|
86
|
-
padding: 6px;
|
|
87
|
-
width: 100%;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.perseus-widget-expression-old > .output {
|
|
91
|
-
background: #f2f2f2;
|
|
92
|
-
border-radius: 5px;
|
|
93
|
-
padding: 10px;
|
|
94
|
-
margin: 10px 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#answer_area .perseus-widget-expression-old > .output {
|
|
98
|
-
background: #f7f7f7;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.perseus-widget-expression-old > .output > .tex {
|
|
102
|
-
overflow-x: auto;
|
|
103
|
-
padding: 5px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.perseus-widget-expression-old > .output > .placeholder {
|
|
107
|
-
position: relative;
|
|
108
|
-
height: 40px;
|
|
109
|
-
overflow-y: hidden;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.perseus-widget-expression-old > .output > .placeholder > .error {
|
|
113
|
-
border-radius: 10px;
|
|
114
|
-
background: #f7f7f7;
|
|
115
|
-
border: 1px solid #ddd;
|
|
116
|
-
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);
|
|
117
|
-
color: #000;
|
|
118
|
-
display: none;
|
|
119
|
-
font-weight: normal;
|
|
120
|
-
min-height: 22px;
|
|
121
|
-
width: 168px;
|
|
122
|
-
position: absolute;
|
|
123
|
-
top: 0px;
|
|
124
|
-
left: 40px;
|
|
125
|
-
margin: auto;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.perseus-widget-expression-old > .output > .placeholder > .error > .buddy {
|
|
129
|
-
background-image: url(/images/perseus/error-buddy.png);
|
|
130
|
-
background-size: 100%;
|
|
131
|
-
height: 36px;
|
|
132
|
-
width: 40px;
|
|
133
|
-
position: absolute;
|
|
134
|
-
top: -3px;
|
|
135
|
-
left: -42px;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.perseus-widget-expression-old > .output > .placeholder > .error > .message {
|
|
139
|
-
font: 12px @baseFontFamily;
|
|
140
|
-
line-height: 1.4em;
|
|
141
|
-
margin: 0px 4px;
|
|
142
|
-
}
|
|
@@ -17,26 +17,8 @@
|
|
|
17
17
|
margin: 22px;
|
|
18
18
|
position: relative;
|
|
19
19
|
|
|
20
|
-
// Use the "Minion Pro" font, if available. If not, fall back to
|
|
21
|
-
// KaTeX_Main or another serif font.
|
|
22
|
-
//
|
|
23
|
-
// NOTE(mdr): Minion Pro is only loaded on SAT and GTP pages, because
|
|
24
|
-
// those are the main call sites for the passage widget. We
|
|
25
|
-
// considered inserting the relevant stylesheet whenever the
|
|
26
|
-
// passage widget loads, but this approach produced a "flash of
|
|
27
|
-
// unstyled text", so we decided to embed the stylesheet into the
|
|
28
|
-
// relevant page templates instead.
|
|
29
|
-
//
|
|
30
|
-
// NOTE(mdr): Minion Pro will not be available on ZNDs, due to
|
|
31
|
-
// restrictions in our Adobe TypeKit configuration.
|
|
32
|
-
//
|
|
33
|
-
// TODO(mdr): The BodySerif component will hopefully soon use Minion
|
|
34
|
-
// Pro by default (WB-399). Once that happens, we should be able to
|
|
35
|
-
// switch to the BodySerif component (TP-3112), and remove this
|
|
36
|
-
// special font-family rule.
|
|
37
20
|
div.paragraph {
|
|
38
|
-
font-family:
|
|
39
|
-
serif;
|
|
21
|
+
font-family: KaTeX_Main, Times, "Times New Roman", serif;
|
|
40
22
|
}
|
|
41
23
|
|
|
42
24
|
.passage-title div.paragraph {
|