@khanacademy/perseus 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/article-renderer.d.ts +2 -1
  3. package/dist/dependencies.d.ts +7 -1
  4. package/dist/es/index.js +66 -29
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.js +66 -29
  7. package/dist/index.js.map +1 -1
  8. package/dist/interaction-tracker.d.ts +3 -3
  9. package/dist/item-renderer.d.ts +2 -1
  10. package/dist/multi-items/multi-renderer.d.ts +2 -1
  11. package/dist/perseus-api.d.ts +1 -1
  12. package/dist/renderer.d.ts +1 -1
  13. package/dist/server-item-renderer.d.ts +3 -1
  14. package/dist/types.d.ts +29 -9
  15. package/dist/widgets/expression.d.ts +409 -12
  16. package/dist/widgets/graded-group.d.ts +2 -2
  17. package/dist/widgets/sequence.d.ts +2 -2
  18. package/package.json +3 -3
  19. package/src/__stories__/article-renderer.stories.tsx +35 -2
  20. package/src/__testdata__/article-renderer.testdata.ts +71 -0
  21. package/src/__tests__/item-renderer.test.tsx +12 -1
  22. package/src/__tests__/server-item-renderer.test.tsx +9 -1
  23. package/src/article-renderer.tsx +38 -30
  24. package/src/dependencies.ts +16 -1
  25. package/src/interaction-tracker.ts +3 -3
  26. package/src/item-renderer.tsx +62 -44
  27. package/src/multi-items/__tests__/multi-renderer.test.tsx +6 -1
  28. package/src/multi-items/multi-renderer.tsx +16 -4
  29. package/src/renderer.tsx +1 -1
  30. package/src/server-item-renderer.tsx +21 -16
  31. package/src/types.ts +49 -13
  32. package/src/widgets/__stories__/definition.stories.tsx +8 -1
  33. package/src/widgets/__stories__/expression.stories.tsx +2 -2
  34. package/src/widgets/__tests__/{expression.test.ts → expression.test.tsx} +73 -8
  35. package/src/widgets/__tests__/renderQuestion.tsx +26 -18
  36. package/src/widgets/expression.tsx +82 -48
  37. package/src/widgets/graded-group.tsx +11 -2
  38. package/src/widgets/sequence.tsx +10 -2
  39. package/tsconfig-build.tsbuildinfo +1 -1
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
- return Expression.validate(this.getUserInput(), rubric, onInputError);
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: Expression,
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("div", {
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(Renderer, _extends({
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(HintsRenderer, {
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("div", null);
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("div", null, /*#__PURE__*/React__namespace.createElement("div", null, questionRenderer), /*#__PURE__*/React__namespace.createElement("div", {
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 this.props.children({
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({