@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.
- package/CHANGELOG.md +21 -0
- package/dist/article-renderer.d.ts +2 -1
- package/dist/dependencies.d.ts +7 -1
- package/dist/es/index.js +66 -29
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +66 -29
- package/dist/index.js.map +1 -1
- package/dist/interaction-tracker.d.ts +3 -3
- package/dist/item-renderer.d.ts +2 -1
- package/dist/multi-items/multi-renderer.d.ts +2 -1
- package/dist/perseus-api.d.ts +1 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/server-item-renderer.d.ts +3 -1
- package/dist/types.d.ts +29 -9
- package/dist/widgets/expression.d.ts +409 -12
- package/dist/widgets/graded-group.d.ts +2 -2
- 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__/server-item-renderer.test.tsx +9 -1
- package/src/article-renderer.tsx +38 -30
- package/src/dependencies.ts +16 -1
- package/src/interaction-tracker.ts +3 -3
- package/src/item-renderer.tsx +62 -44
- package/src/multi-items/__tests__/multi-renderer.test.tsx +6 -1
- package/src/multi-items/multi-renderer.tsx +16 -4
- package/src/renderer.tsx +1 -1
- package/src/server-item-renderer.tsx +21 -16
- package/src/types.ts +49 -13
- 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 +26 -18
- package/src/widgets/expression.tsx +82 -48
- package/src/widgets/graded-group.tsx +11 -2
- package/src/widgets/sequence.tsx +10 -2
- package/tsconfig-build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @khanacademy/perseus
|
|
2
2
|
|
|
3
|
+
## 9.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- dd800c22: Rename analytics prop from onEvent to onAnalyticsEvent
|
|
8
|
+
- 2b99fbda: Remove 'analytics' key from PerseusDependencies
|
|
9
|
+
|
|
10
|
+
### Minor Changes
|
|
11
|
+
|
|
12
|
+
- ec50a510: Switch `virtualKeypadVersion` on `perseus:expression-evaluated` event to be non-optional
|
|
13
|
+
- 673f61b3: Introduce `dependencies` on ArticleRenderer, ItemRenderer, MultiRenderer, and ServerItemRenderer.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 2c69b0dc: Fix APIOptions `trackInteraction` type for better Flow type generation
|
|
18
|
+
- Updated dependencies [dd800c22]
|
|
19
|
+
- Updated dependencies [5352d512]
|
|
20
|
+
- Updated dependencies [673f61b3]
|
|
21
|
+
- @khanacademy/math-input@10.0.0
|
|
22
|
+
- @khanacademy/perseus-core@0.2.0
|
|
23
|
+
|
|
3
24
|
## 8.0.0
|
|
4
25
|
|
|
5
26
|
### Major Changes
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import type { KeypadProps } from "./mixins/provide-keypad";
|
|
7
7
|
import type { PerseusRenderer } from "./perseus-types";
|
|
8
|
-
import type { APIOptions } from "./types";
|
|
8
|
+
import type { APIOptions, PerseusDependenciesV2 } from "./types";
|
|
9
9
|
import type { LinterContextProps } from "@khanacademy/perseus-linter";
|
|
10
10
|
type Props = {
|
|
11
11
|
apiOptions: APIOptions;
|
|
@@ -16,6 +16,7 @@ type Props = {
|
|
|
16
16
|
useNewStyles: boolean;
|
|
17
17
|
linterContext: LinterContextProps;
|
|
18
18
|
legacyPerseusLint?: ReadonlyArray<string>;
|
|
19
|
+
dependencies: PerseusDependenciesV2;
|
|
19
20
|
} & KeypadProps;
|
|
20
21
|
type DefaultProps = {
|
|
21
22
|
apiOptions: Props["apiOptions"];
|
package/dist/dependencies.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { PerseusDependencies, PerseusDependenciesV2 } from "./types";
|
|
2
3
|
export declare const setDependencies: (dependencies: PerseusDependencies) => void;
|
|
3
4
|
export declare const getDependencies: () => PerseusDependencies;
|
|
5
|
+
export declare const DependenciesContext: React.Context<PerseusDependenciesV2>;
|
|
6
|
+
/**
|
|
7
|
+
* useDependencies provides easy access to the current Perseus dependencies.
|
|
8
|
+
*/
|
|
9
|
+
export declare const useDependencies: () => PerseusDependenciesV2;
|
package/dist/es/index.js
CHANGED
|
@@ -113,11 +113,26 @@ const getDependencies = () => {
|
|
|
113
113
|
}
|
|
114
114
|
throw new Error(["Perseus has not been provided required dependencies.", "setDependencies(dependencies) must be called first.", "Make sure Perseus is being imported from javascript/perseus/perseus.js."].join("\n"));
|
|
115
115
|
};
|
|
116
|
+
const DependenciesContext = /*#__PURE__*/React.createContext({
|
|
117
|
+
analytics: {
|
|
118
|
+
onAnalyticsEvent: async () => {}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* useDependencies provides easy access to the current Perseus dependencies.
|
|
124
|
+
*/
|
|
125
|
+
const useDependencies = () => {
|
|
126
|
+
const deps = React.useContext(DependenciesContext);
|
|
127
|
+
return deps;
|
|
128
|
+
};
|
|
116
129
|
|
|
117
130
|
var dependencies = /*#__PURE__*/Object.freeze({
|
|
118
131
|
__proto__: null,
|
|
119
132
|
setDependencies: setDependencies,
|
|
120
|
-
getDependencies: getDependencies
|
|
133
|
+
getDependencies: getDependencies,
|
|
134
|
+
DependenciesContext: DependenciesContext,
|
|
135
|
+
useDependencies: useDependencies
|
|
121
136
|
});
|
|
122
137
|
|
|
123
138
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -2430,14 +2445,6 @@ const KhanAnswerTypes = {
|
|
|
2430
2445
|
}
|
|
2431
2446
|
};
|
|
2432
2447
|
|
|
2433
|
-
const sendExpressionEvaluatedEvent = result => {
|
|
2434
|
-
getDependencies().analytics({
|
|
2435
|
-
type: "perseus:expression-evaluated",
|
|
2436
|
-
payload: {
|
|
2437
|
-
result
|
|
2438
|
-
}
|
|
2439
|
-
});
|
|
2440
|
-
};
|
|
2441
2448
|
const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
|
|
2442
2449
|
const insertBraces = value => {
|
|
2443
2450
|
// HACK(alex): Make sure that all LaTeX super/subscripts are wrapped
|
|
@@ -2460,6 +2467,13 @@ const insertBraces = value => {
|
|
|
2460
2467
|
// TODO(alex): Properly hack MathQuill to always use explicit braces.
|
|
2461
2468
|
return value.replace(/([_^])([^{])/g, "$1{$2}");
|
|
2462
2469
|
};
|
|
2470
|
+
const deriveKeypadVersion = apiOptions => {
|
|
2471
|
+
// We can derive which version of the keypad is in use. This is
|
|
2472
|
+
// a bit tricky, but this code will be relatively short-lived
|
|
2473
|
+
// as we coalesce onto the new, v2 Keypad, at which point we
|
|
2474
|
+
// can remove this `virtualKeypadVersion` field entirely.
|
|
2475
|
+
return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : apiOptions.customKeypad === true ? apiOptions.useV2Keypad === true ? "MATH_INPUT_KEYPAD_V2" : "MATH_INPUT_KEYPAD_V1" : "PERSEUS_MATH_INPUT";
|
|
2476
|
+
};
|
|
2463
2477
|
// The new, MathQuill input expression widget
|
|
2464
2478
|
class Expression extends React.Component {
|
|
2465
2479
|
constructor(..._args) {
|
|
@@ -2514,7 +2528,9 @@ class Expression extends React.Component {
|
|
|
2514
2528
|
};
|
|
2515
2529
|
this.simpleValidate = (rubric, onInputError) => {
|
|
2516
2530
|
onInputError = onInputError || function () {};
|
|
2517
|
-
|
|
2531
|
+
const result = Expression.validate(this.getUserInput(), rubric, onInputError);
|
|
2532
|
+
this.sendExpressionEvaluatedEvent(result.type === "invalid" ? "invalid" : result.earned === result.total ? "correct" : "incorrect");
|
|
2533
|
+
return result;
|
|
2518
2534
|
};
|
|
2519
2535
|
this.getUserInput = () => {
|
|
2520
2536
|
return Expression.getUserInputFromProps(this.props);
|
|
@@ -2670,8 +2686,6 @@ class Expression extends React.Component {
|
|
|
2670
2686
|
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2671
2687
|
if (!matchingAnswerForm) {
|
|
2672
2688
|
if (firstUngradedResult) {
|
|
2673
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
2674
|
-
|
|
2675
2689
|
// While we didn't directly match with any answer form, we
|
|
2676
2690
|
// did at some point get an "ungraded" validation result,
|
|
2677
2691
|
// which might indicate e.g. a mismatch in variable casing.
|
|
@@ -2685,8 +2699,6 @@ class Expression extends React.Component {
|
|
|
2685
2699
|
};
|
|
2686
2700
|
}
|
|
2687
2701
|
if (allEmpty) {
|
|
2688
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
2689
|
-
|
|
2690
2702
|
// If everything graded as empty, it's invalid.
|
|
2691
2703
|
return {
|
|
2692
2704
|
type: "invalid",
|
|
@@ -2695,7 +2707,6 @@ class Expression extends React.Component {
|
|
|
2695
2707
|
}
|
|
2696
2708
|
// We fell through all the possibilities and we're not empty,
|
|
2697
2709
|
// so the answer is considered incorrect.
|
|
2698
|
-
sendExpressionEvaluatedEvent("incorrect");
|
|
2699
2710
|
return {
|
|
2700
2711
|
type: "points",
|
|
2701
2712
|
earned: 0,
|
|
@@ -2708,7 +2719,6 @@ class Expression extends React.Component {
|
|
|
2708
2719
|
const apiResult = onInputError(null,
|
|
2709
2720
|
// Reserved for some widget identifier
|
|
2710
2721
|
userInput, matchMessage);
|
|
2711
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
2712
2722
|
return {
|
|
2713
2723
|
type: "invalid",
|
|
2714
2724
|
message: apiResult === false ? null : matchMessage
|
|
@@ -2720,7 +2730,6 @@ class Expression extends React.Component {
|
|
|
2720
2730
|
// TODO(eater): Seems silly to translate result to this
|
|
2721
2731
|
// invalid/points thing and immediately translate it back in
|
|
2722
2732
|
// ItemRenderer.scoreInput()
|
|
2723
|
-
sendExpressionEvaluatedEvent(matchingAnswerForm.considered === "correct" ? "correct" : "incorrect");
|
|
2724
2733
|
return {
|
|
2725
2734
|
type: "points",
|
|
2726
2735
|
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
@@ -2746,6 +2755,15 @@ class Expression extends React.Component {
|
|
|
2746
2755
|
// @ts-expect-error - TS2339 - Property 'insert' does not exist on type 'ReactInstance'.
|
|
2747
2756
|
this.refs.input.insert(keyPressed);
|
|
2748
2757
|
}
|
|
2758
|
+
sendExpressionEvaluatedEvent(result) {
|
|
2759
|
+
this.props.analytics.onAnalyticsEvent({
|
|
2760
|
+
type: "perseus:expression-evaluated",
|
|
2761
|
+
payload: {
|
|
2762
|
+
result,
|
|
2763
|
+
virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2749
2767
|
render() {
|
|
2750
2768
|
if (this.props.apiOptions.customKeypad) {
|
|
2751
2769
|
return /*#__PURE__*/React.createElement(KeypadInput
|
|
@@ -2906,11 +2924,18 @@ const propUpgrades$2 = {
|
|
|
2906
2924
|
}]
|
|
2907
2925
|
})
|
|
2908
2926
|
};
|
|
2927
|
+
const ExpressionWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
2928
|
+
const deps = useDependencies();
|
|
2929
|
+
return /*#__PURE__*/React.createElement(Expression, _extends({
|
|
2930
|
+
ref: ref,
|
|
2931
|
+
analytics: deps.analytics
|
|
2932
|
+
}, props));
|
|
2933
|
+
});
|
|
2909
2934
|
var Expression$1 = {
|
|
2910
2935
|
name: "expression",
|
|
2911
2936
|
displayName: "Expression / Equation",
|
|
2912
2937
|
defaultAlignment: "inline-block",
|
|
2913
|
-
widget:
|
|
2938
|
+
widget: ExpressionWithDependencies,
|
|
2914
2939
|
transform: widgetOptions => {
|
|
2915
2940
|
const {
|
|
2916
2941
|
times,
|
|
@@ -44033,7 +44058,9 @@ class ArticleRenderer extends React.Component {
|
|
|
44033
44058
|
// TODO(alex): Add mobile api functions and pass them down here
|
|
44034
44059
|
const sections = this._sections().map((section, i) => {
|
|
44035
44060
|
const refForSection = `section-${i}`;
|
|
44036
|
-
return /*#__PURE__*/React.createElement(
|
|
44061
|
+
return /*#__PURE__*/React.createElement(DependenciesContext.Provider, {
|
|
44062
|
+
value: this.props.dependencies
|
|
44063
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
44037
44064
|
key: i,
|
|
44038
44065
|
className: "clearfix"
|
|
44039
44066
|
}, /*#__PURE__*/React.createElement(Renderer, _extends({}, section, {
|
|
@@ -44051,7 +44078,7 @@ class ArticleRenderer extends React.Component {
|
|
|
44051
44078
|
}),
|
|
44052
44079
|
linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "article"),
|
|
44053
44080
|
legacyPerseusLint: this.props.legacyPerseusLint
|
|
44054
|
-
})));
|
|
44081
|
+
}))));
|
|
44055
44082
|
});
|
|
44056
44083
|
return /*#__PURE__*/React.createElement("div", {
|
|
44057
44084
|
className: classes
|
|
@@ -44508,7 +44535,9 @@ class ItemRenderer extends React.Component {
|
|
|
44508
44535
|
// that have completely different places in the DOM, we have to do this
|
|
44509
44536
|
// strangeness instead of relying on React's normal render() method.
|
|
44510
44537
|
// TODO(alpert): Figure out how to clean this up somehow
|
|
44511
|
-
render( /*#__PURE__*/React.createElement(
|
|
44538
|
+
render( /*#__PURE__*/React.createElement(DependenciesContext.Provider, {
|
|
44539
|
+
value: this.props.dependencies
|
|
44540
|
+
}, /*#__PURE__*/React.createElement(Renderer, _extends({
|
|
44512
44541
|
ref: node => {
|
|
44513
44542
|
if (!node) {
|
|
44514
44543
|
return;
|
|
@@ -44536,10 +44565,12 @@ class ItemRenderer extends React.Component {
|
|
|
44536
44565
|
linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "question")
|
|
44537
44566
|
}, this.props.item.question, {
|
|
44538
44567
|
legacyPerseusLint: this.props.legacyPerseusLint
|
|
44539
|
-
})),
|
|
44568
|
+
}))),
|
|
44540
44569
|
// @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
|
|
44541
44570
|
workArea);
|
|
44542
|
-
render( /*#__PURE__*/React.createElement(
|
|
44571
|
+
render( /*#__PURE__*/React.createElement(DependenciesContext.Provider, {
|
|
44572
|
+
value: this.props.dependencies
|
|
44573
|
+
}, /*#__PURE__*/React.createElement(HintsRenderer, {
|
|
44543
44574
|
ref: node => this.hintsRenderer = node,
|
|
44544
44575
|
hints: this.props.item.hints,
|
|
44545
44576
|
hintsVisible: this.state.hintsVisible
|
|
@@ -44547,7 +44578,7 @@ class ItemRenderer extends React.Component {
|
|
|
44547
44578
|
,
|
|
44548
44579
|
apiOptions: apiOptions,
|
|
44549
44580
|
linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "hints")
|
|
44550
|
-
}),
|
|
44581
|
+
})),
|
|
44551
44582
|
// @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
|
|
44552
44583
|
hintsArea);
|
|
44553
44584
|
const answerArea = this.props.item.answerArea || {};
|
|
@@ -44764,7 +44795,9 @@ class ItemRenderer extends React.Component {
|
|
|
44764
44795
|
this.questionRenderer.deselectIncorrectSelectedChoices();
|
|
44765
44796
|
}
|
|
44766
44797
|
render() {
|
|
44767
|
-
return /*#__PURE__*/React.createElement(
|
|
44798
|
+
return /*#__PURE__*/React.createElement(DependenciesContext.Provider, {
|
|
44799
|
+
value: this.props.dependencies
|
|
44800
|
+
}, /*#__PURE__*/React.createElement("div", null));
|
|
44768
44801
|
}
|
|
44769
44802
|
}
|
|
44770
44803
|
ItemRenderer.defaultProps = {
|
|
@@ -45084,14 +45117,16 @@ class ServerItemRenderer extends React.Component {
|
|
|
45084
45117
|
apiOptions: apiOptions,
|
|
45085
45118
|
ref: elem => this.hintsRenderer = elem
|
|
45086
45119
|
});
|
|
45087
|
-
return /*#__PURE__*/React.createElement(
|
|
45120
|
+
return /*#__PURE__*/React.createElement(DependenciesContext.Provider, {
|
|
45121
|
+
value: this.props.dependencies
|
|
45122
|
+
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", null, questionRenderer), /*#__PURE__*/React.createElement("div", {
|
|
45088
45123
|
className:
|
|
45089
45124
|
// Avoid adding any horizontal padding when applying the
|
|
45090
45125
|
// mobile hint styles, which are flush to the left.
|
|
45091
45126
|
// NOTE(charlie): We may still want to apply this
|
|
45092
45127
|
// padding for desktop exercises.
|
|
45093
45128
|
apiOptions.isMobile ? undefined : css(styles$3.hintsContainer)
|
|
45094
|
-
}, hintsRenderer));
|
|
45129
|
+
}, hintsRenderer)));
|
|
45095
45130
|
}
|
|
45096
45131
|
}
|
|
45097
45132
|
ServerItemRenderer.defaultProps = {
|
|
@@ -46492,9 +46527,11 @@ class MultiRenderer extends React.Component {
|
|
|
46492
46527
|
|
|
46493
46528
|
// Pass the renderer tree to the `children` function, which will
|
|
46494
46529
|
// determine the actual content of this component.
|
|
46495
|
-
return
|
|
46530
|
+
return /*#__PURE__*/React.createElement(DependenciesContext.Provider, {
|
|
46531
|
+
value: this.props.dependencies
|
|
46532
|
+
}, this.props.children({
|
|
46496
46533
|
renderers: this._getRenderers()
|
|
46497
|
-
});
|
|
46534
|
+
}));
|
|
46498
46535
|
}
|
|
46499
46536
|
}
|
|
46500
46537
|
const styles = StyleSheet.create({
|