@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/dist/index.js
CHANGED
|
@@ -183,11 +183,26 @@ const getDependencies = () => {
|
|
|
183
183
|
}
|
|
184
184
|
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"));
|
|
185
185
|
};
|
|
186
|
+
const DependenciesContext = /*#__PURE__*/React__namespace.createContext({
|
|
187
|
+
analytics: {
|
|
188
|
+
onAnalyticsEvent: async () => {}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* useDependencies provides easy access to the current Perseus dependencies.
|
|
194
|
+
*/
|
|
195
|
+
const useDependencies = () => {
|
|
196
|
+
const deps = React__namespace.useContext(DependenciesContext);
|
|
197
|
+
return deps;
|
|
198
|
+
};
|
|
186
199
|
|
|
187
200
|
var dependencies = /*#__PURE__*/Object.freeze({
|
|
188
201
|
__proto__: null,
|
|
189
202
|
setDependencies: setDependencies,
|
|
190
|
-
getDependencies: getDependencies
|
|
203
|
+
getDependencies: getDependencies,
|
|
204
|
+
DependenciesContext: DependenciesContext,
|
|
205
|
+
useDependencies: useDependencies
|
|
191
206
|
});
|
|
192
207
|
|
|
193
208
|
const prettyBig = {
|
|
@@ -2489,14 +2504,6 @@ const KhanAnswerTypes = {
|
|
|
2489
2504
|
}
|
|
2490
2505
|
};
|
|
2491
2506
|
|
|
2492
|
-
const sendExpressionEvaluatedEvent = result => {
|
|
2493
|
-
getDependencies().analytics({
|
|
2494
|
-
type: "perseus:expression-evaluated",
|
|
2495
|
-
payload: {
|
|
2496
|
-
result
|
|
2497
|
-
}
|
|
2498
|
-
});
|
|
2499
|
-
};
|
|
2500
2507
|
const ERROR_MESSAGE = i18n__namespace._("Sorry, I don't understand that!");
|
|
2501
2508
|
const insertBraces = value => {
|
|
2502
2509
|
// HACK(alex): Make sure that all LaTeX super/subscripts are wrapped
|
|
@@ -2519,6 +2526,13 @@ const insertBraces = value => {
|
|
|
2519
2526
|
// TODO(alex): Properly hack MathQuill to always use explicit braces.
|
|
2520
2527
|
return value.replace(/([_^])([^{])/g, "$1{$2}");
|
|
2521
2528
|
};
|
|
2529
|
+
const deriveKeypadVersion = apiOptions => {
|
|
2530
|
+
// We can derive which version of the keypad is in use. This is
|
|
2531
|
+
// a bit tricky, but this code will be relatively short-lived
|
|
2532
|
+
// as we coalesce onto the new, v2 Keypad, at which point we
|
|
2533
|
+
// can remove this `virtualKeypadVersion` field entirely.
|
|
2534
|
+
return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : apiOptions.customKeypad === true ? apiOptions.useV2Keypad === true ? "MATH_INPUT_KEYPAD_V2" : "MATH_INPUT_KEYPAD_V1" : "PERSEUS_MATH_INPUT";
|
|
2535
|
+
};
|
|
2522
2536
|
// The new, MathQuill input expression widget
|
|
2523
2537
|
class Expression extends React__namespace.Component {
|
|
2524
2538
|
constructor() {
|
|
@@ -2575,7 +2589,9 @@ class Expression extends React__namespace.Component {
|
|
|
2575
2589
|
});
|
|
2576
2590
|
_defineProperty(this, "simpleValidate", (rubric, onInputError) => {
|
|
2577
2591
|
onInputError = onInputError || function () {};
|
|
2578
|
-
|
|
2592
|
+
const result = Expression.validate(this.getUserInput(), rubric, onInputError);
|
|
2593
|
+
this.sendExpressionEvaluatedEvent(result.type === "invalid" ? "invalid" : result.earned === result.total ? "correct" : "incorrect");
|
|
2594
|
+
return result;
|
|
2579
2595
|
});
|
|
2580
2596
|
_defineProperty(this, "getUserInput", () => {
|
|
2581
2597
|
return Expression.getUserInputFromProps(this.props);
|
|
@@ -2733,8 +2749,6 @@ class Expression extends React__namespace.Component {
|
|
|
2733
2749
|
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2734
2750
|
if (!matchingAnswerForm) {
|
|
2735
2751
|
if (firstUngradedResult) {
|
|
2736
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
2737
|
-
|
|
2738
2752
|
// While we didn't directly match with any answer form, we
|
|
2739
2753
|
// did at some point get an "ungraded" validation result,
|
|
2740
2754
|
// which might indicate e.g. a mismatch in variable casing.
|
|
@@ -2748,8 +2762,6 @@ class Expression extends React__namespace.Component {
|
|
|
2748
2762
|
};
|
|
2749
2763
|
}
|
|
2750
2764
|
if (allEmpty) {
|
|
2751
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
2752
|
-
|
|
2753
2765
|
// If everything graded as empty, it's invalid.
|
|
2754
2766
|
return {
|
|
2755
2767
|
type: "invalid",
|
|
@@ -2758,7 +2770,6 @@ class Expression extends React__namespace.Component {
|
|
|
2758
2770
|
}
|
|
2759
2771
|
// We fell through all the possibilities and we're not empty,
|
|
2760
2772
|
// so the answer is considered incorrect.
|
|
2761
|
-
sendExpressionEvaluatedEvent("incorrect");
|
|
2762
2773
|
return {
|
|
2763
2774
|
type: "points",
|
|
2764
2775
|
earned: 0,
|
|
@@ -2771,7 +2782,6 @@ class Expression extends React__namespace.Component {
|
|
|
2771
2782
|
const apiResult = onInputError(null,
|
|
2772
2783
|
// Reserved for some widget identifier
|
|
2773
2784
|
userInput, matchMessage);
|
|
2774
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
2775
2785
|
return {
|
|
2776
2786
|
type: "invalid",
|
|
2777
2787
|
message: apiResult === false ? null : matchMessage
|
|
@@ -2783,7 +2793,6 @@ class Expression extends React__namespace.Component {
|
|
|
2783
2793
|
// TODO(eater): Seems silly to translate result to this
|
|
2784
2794
|
// invalid/points thing and immediately translate it back in
|
|
2785
2795
|
// ItemRenderer.scoreInput()
|
|
2786
|
-
sendExpressionEvaluatedEvent(matchingAnswerForm.considered === "correct" ? "correct" : "incorrect");
|
|
2787
2796
|
return {
|
|
2788
2797
|
type: "points",
|
|
2789
2798
|
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
@@ -2809,6 +2818,15 @@ class Expression extends React__namespace.Component {
|
|
|
2809
2818
|
// @ts-expect-error - TS2339 - Property 'insert' does not exist on type 'ReactInstance'.
|
|
2810
2819
|
this.refs.input.insert(keyPressed);
|
|
2811
2820
|
}
|
|
2821
|
+
sendExpressionEvaluatedEvent(result) {
|
|
2822
|
+
this.props.analytics.onAnalyticsEvent({
|
|
2823
|
+
type: "perseus:expression-evaluated",
|
|
2824
|
+
payload: {
|
|
2825
|
+
result,
|
|
2826
|
+
virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2812
2830
|
render() {
|
|
2813
2831
|
if (this.props.apiOptions.customKeypad) {
|
|
2814
2832
|
return /*#__PURE__*/React__namespace.createElement(mathInput.KeypadInput
|
|
@@ -2969,11 +2987,18 @@ const propUpgrades$2 = {
|
|
|
2969
2987
|
}]
|
|
2970
2988
|
})
|
|
2971
2989
|
};
|
|
2990
|
+
const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
2991
|
+
const deps = useDependencies();
|
|
2992
|
+
return /*#__PURE__*/React__namespace.createElement(Expression, _extends({
|
|
2993
|
+
ref: ref,
|
|
2994
|
+
analytics: deps.analytics
|
|
2995
|
+
}, props));
|
|
2996
|
+
});
|
|
2972
2997
|
var Expression$1 = {
|
|
2973
2998
|
name: "expression",
|
|
2974
2999
|
displayName: "Expression / Equation",
|
|
2975
3000
|
defaultAlignment: "inline-block",
|
|
2976
|
-
widget:
|
|
3001
|
+
widget: ExpressionWithDependencies,
|
|
2977
3002
|
transform: widgetOptions => {
|
|
2978
3003
|
const {
|
|
2979
3004
|
times,
|
|
@@ -44299,7 +44324,9 @@ class ArticleRenderer extends React__namespace.Component {
|
|
|
44299
44324
|
// TODO(alex): Add mobile api functions and pass them down here
|
|
44300
44325
|
const sections = this._sections().map((section, i) => {
|
|
44301
44326
|
const refForSection = "section-".concat(i);
|
|
44302
|
-
return /*#__PURE__*/React__namespace.createElement(
|
|
44327
|
+
return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
|
|
44328
|
+
value: this.props.dependencies
|
|
44329
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
44303
44330
|
key: i,
|
|
44304
44331
|
className: "clearfix"
|
|
44305
44332
|
}, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, section, {
|
|
@@ -44318,7 +44345,7 @@ class ArticleRenderer extends React__namespace.Component {
|
|
|
44318
44345
|
},
|
|
44319
44346
|
linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "article"),
|
|
44320
44347
|
legacyPerseusLint: this.props.legacyPerseusLint
|
|
44321
|
-
})));
|
|
44348
|
+
}))));
|
|
44322
44349
|
});
|
|
44323
44350
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
44324
44351
|
className: classes
|
|
@@ -44784,7 +44811,9 @@ class ItemRenderer extends React__namespace.Component {
|
|
|
44784
44811
|
// that have completely different places in the DOM, we have to do this
|
|
44785
44812
|
// strangeness instead of relying on React's normal render() method.
|
|
44786
44813
|
// TODO(alpert): Figure out how to clean this up somehow
|
|
44787
|
-
render( /*#__PURE__*/React__namespace.createElement(
|
|
44814
|
+
render( /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
|
|
44815
|
+
value: this.props.dependencies
|
|
44816
|
+
}, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({
|
|
44788
44817
|
ref: node => {
|
|
44789
44818
|
if (!node) {
|
|
44790
44819
|
return;
|
|
@@ -44812,10 +44841,12 @@ class ItemRenderer extends React__namespace.Component {
|
|
|
44812
44841
|
linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "question")
|
|
44813
44842
|
}, this.props.item.question, {
|
|
44814
44843
|
legacyPerseusLint: this.props.legacyPerseusLint
|
|
44815
|
-
})),
|
|
44844
|
+
}))),
|
|
44816
44845
|
// @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
|
|
44817
44846
|
workArea);
|
|
44818
|
-
render( /*#__PURE__*/React__namespace.createElement(
|
|
44847
|
+
render( /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
|
|
44848
|
+
value: this.props.dependencies
|
|
44849
|
+
}, /*#__PURE__*/React__namespace.createElement(HintsRenderer, {
|
|
44819
44850
|
ref: node => this.hintsRenderer = node,
|
|
44820
44851
|
hints: this.props.item.hints,
|
|
44821
44852
|
hintsVisible: this.state.hintsVisible
|
|
@@ -44823,7 +44854,7 @@ class ItemRenderer extends React__namespace.Component {
|
|
|
44823
44854
|
,
|
|
44824
44855
|
apiOptions: apiOptions,
|
|
44825
44856
|
linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hints")
|
|
44826
|
-
}),
|
|
44857
|
+
})),
|
|
44827
44858
|
// @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
|
|
44828
44859
|
hintsArea);
|
|
44829
44860
|
const answerArea = this.props.item.answerArea || {};
|
|
@@ -45040,7 +45071,9 @@ class ItemRenderer extends React__namespace.Component {
|
|
|
45040
45071
|
this.questionRenderer.deselectIncorrectSelectedChoices();
|
|
45041
45072
|
}
|
|
45042
45073
|
render() {
|
|
45043
|
-
return /*#__PURE__*/React__namespace.createElement(
|
|
45074
|
+
return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
|
|
45075
|
+
value: this.props.dependencies
|
|
45076
|
+
}, /*#__PURE__*/React__namespace.createElement("div", null));
|
|
45044
45077
|
}
|
|
45045
45078
|
}
|
|
45046
45079
|
_defineProperty(ItemRenderer, "defaultProps", {
|
|
@@ -45363,14 +45396,16 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
45363
45396
|
apiOptions: apiOptions,
|
|
45364
45397
|
ref: elem => this.hintsRenderer = elem
|
|
45365
45398
|
});
|
|
45366
|
-
return /*#__PURE__*/React__namespace.createElement(
|
|
45399
|
+
return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
|
|
45400
|
+
value: this.props.dependencies
|
|
45401
|
+
}, /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("div", null, questionRenderer), /*#__PURE__*/React__namespace.createElement("div", {
|
|
45367
45402
|
className:
|
|
45368
45403
|
// Avoid adding any horizontal padding when applying the
|
|
45369
45404
|
// mobile hint styles, which are flush to the left.
|
|
45370
45405
|
// NOTE(charlie): We may still want to apply this
|
|
45371
45406
|
// padding for desktop exercises.
|
|
45372
45407
|
apiOptions.isMobile ? undefined : aphrodite.css(styles$3.hintsContainer)
|
|
45373
|
-
}, hintsRenderer));
|
|
45408
|
+
}, hintsRenderer)));
|
|
45374
45409
|
}
|
|
45375
45410
|
}
|
|
45376
45411
|
_defineProperty(ServerItemRenderer, "defaultProps", {
|
|
@@ -46727,9 +46762,11 @@ class MultiRenderer extends React__namespace.Component {
|
|
|
46727
46762
|
|
|
46728
46763
|
// Pass the renderer tree to the `children` function, which will
|
|
46729
46764
|
// determine the actual content of this component.
|
|
46730
|
-
return
|
|
46765
|
+
return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
|
|
46766
|
+
value: this.props.dependencies
|
|
46767
|
+
}, this.props.children({
|
|
46731
46768
|
renderers: this._getRenderers()
|
|
46732
|
-
});
|
|
46769
|
+
}));
|
|
46733
46770
|
}
|
|
46734
46771
|
}
|
|
46735
46772
|
const styles = aphrodite.StyleSheet.create({
|