@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/article-renderer.d.ts +3 -2
  3. package/dist/error-boundary.d.ts +1 -0
  4. package/dist/es/index.css +1 -66
  5. package/dist/es/index.js +86 -66
  6. package/dist/es/index.js.map +1 -1
  7. package/dist/index.css +1 -66
  8. package/dist/index.js +77 -54
  9. package/dist/index.js.map +1 -1
  10. package/dist/renderer.d.ts +4 -1
  11. package/dist/styles/constants.d.ts +2 -2
  12. package/dist/widget-container.d.ts +2 -0
  13. package/dist/widgets/expression.d.ts +5 -4
  14. package/package.json +8 -8
  15. package/src/__tests__/renderer-api.test.tsx +18 -23
  16. package/src/article-renderer.tsx +15 -13
  17. package/src/error-boundary.tsx +3 -0
  18. package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +5 -5
  19. package/src/renderer.tsx +29 -27
  20. package/src/server-item-renderer.tsx +1 -0
  21. package/src/styles/constants.ts +4 -4
  22. package/src/styles/perseus-renderer.less +0 -10
  23. package/src/styles/variables.less +2 -2
  24. package/src/styles/variables.shared.less +2 -2
  25. package/src/styles/widgets/expression.less +0 -77
  26. package/src/styles/widgets/passage.less +1 -19
  27. package/src/widget-container.tsx +32 -10
  28. package/src/widgets/__stories__/label-image.stories.tsx +30 -0
  29. package/src/widgets/__testdata__/label-image.testdata.ts +63 -0
  30. package/src/widgets/__tests__/__snapshots__/group.test.tsx.snap +5 -5
  31. package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +28 -28
  32. package/src/widgets/__tests__/expression.test.tsx +12 -9
  33. package/src/widgets/__tests__/renderQuestion.tsx +30 -13
  34. package/src/widgets/expression.tsx +19 -25
  35. package/src/widgets/transformer.tsx +1 -2
  36. package/tsconfig-build.tsbuildinfo +1 -1
package/dist/index.css CHANGED
@@ -2684,71 +2684,6 @@ body.mobile .categorizer-container td.category input[type="radio"] + span:active
2684
2684
  #answer_area .perseus-widget-expression .error-tooltip .tooltipContainer > div:first-child {
2685
2685
  visibility: hidden !important;
2686
2686
  }
2687
- .perseus-widget-expression-old,
2688
- .perseus-widget-expression-old > .output,
2689
- .perseus-widget-expression-old > .output > .tex,
2690
- .perseus-widget-expression-old > .output > .placeholder {
2691
- display: block;
2692
- }
2693
- .perseus-widget-expression-old input,
2694
- #answer_area .perseus-widget-expression-old input {
2695
- direction: ltr;
2696
- border: 1px solid #a4a4a4;
2697
- border-radius: 5px;
2698
- box-sizing: border-box;
2699
- font-size: 14px;
2700
- margin-bottom: 5px;
2701
- max-width: 240px;
2702
- padding: 6px;
2703
- width: 100%;
2704
- }
2705
- .perseus-widget-expression-old > .output {
2706
- background: #f2f2f2;
2707
- border-radius: 5px;
2708
- padding: 10px;
2709
- margin: 10px 0;
2710
- }
2711
- #answer_area .perseus-widget-expression-old > .output {
2712
- background: #f7f7f7;
2713
- }
2714
- .perseus-widget-expression-old > .output > .tex {
2715
- overflow-x: auto;
2716
- padding: 5px;
2717
- }
2718
- .perseus-widget-expression-old > .output > .placeholder {
2719
- position: relative;
2720
- height: 40px;
2721
- overflow-y: hidden;
2722
- }
2723
- .perseus-widget-expression-old > .output > .placeholder > .error {
2724
- border-radius: 10px;
2725
- background: #f7f7f7;
2726
- border: 1px solid #ddd;
2727
- box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);
2728
- color: #000;
2729
- display: none;
2730
- font-weight: normal;
2731
- min-height: 22px;
2732
- width: 168px;
2733
- position: absolute;
2734
- top: 0px;
2735
- left: 40px;
2736
- margin: auto;
2737
- }
2738
- .perseus-widget-expression-old > .output > .placeholder > .error > .buddy {
2739
- background-image: url(/images/perseus/error-buddy.png);
2740
- background-size: 100%;
2741
- height: 36px;
2742
- width: 40px;
2743
- position: absolute;
2744
- top: -3px;
2745
- left: -42px;
2746
- }
2747
- .perseus-widget-expression-old > .output > .placeholder > .error > .message {
2748
- font: 12px inherit;
2749
- line-height: 1.4em;
2750
- margin: 0px 4px;
2751
- }
2752
2687
  .perseus-widget-grapher {
2753
2688
  box-sizing: content-box;
2754
2689
  padding: 25px 25px 0 0;
@@ -3121,7 +3056,7 @@ body.mobile .perseus-matrix .math-output {
3121
3056
  }
3122
3057
  .framework-perseus.perseus-article .perseus-renderer .perseus-widget-passage-container .perseus-widget-passage div.paragraph,
3123
3058
  .perseus-widget-passage-container .perseus-widget-passage div.paragraph {
3124
- font-family: "minion-pro", KaTeX_Main, Times, "Times New Roman", serif;
3059
+ font-family: KaTeX_Main, Times, "Times New Roman", serif;
3125
3060
  }
3126
3061
  .framework-perseus.perseus-article .perseus-renderer .perseus-widget-passage-container .perseus-widget-passage .passage-title div.paragraph,
3127
3062
  .perseus-widget-passage-container .perseus-widget-passage .passage-title div.paragraph {
package/dist/index.js CHANGED
@@ -2007,11 +2007,26 @@ class Expression extends React__namespace.Component {
2007
2007
  });
2008
2008
  }
2009
2009
  });
2010
- _defineProperty(this, "simpleValidate", (rubric, onInputError) => {
2011
- onInputError = onInputError || function () {};
2012
- const result = Expression.validate(this.getUserInput(), rubric, onInputError);
2013
- this.sendExpressionEvaluatedEvent(result.type === "invalid" ? "invalid" : result.earned === result.total ? "correct" : "incorrect");
2014
- return result;
2010
+ _defineProperty(this, "simpleValidate", (_ref, onInputError) => {
2011
+ let {
2012
+ scoring,
2013
+ ...rubric
2014
+ } = _ref;
2015
+ const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
2016
+
2017
+ // "scoring" is a flag that indicates when we are checking answers.
2018
+ // otherwise, we may just be checking validity after changes.
2019
+ if (scoring && score.type !== "invalid") {
2020
+ var _this$props$analytics;
2021
+ (_this$props$analytics = this.props.analytics) === null || _this$props$analytics === void 0 ? void 0 : _this$props$analytics.onAnalyticsEvent({
2022
+ type: "perseus:expression-evaluated",
2023
+ payload: {
2024
+ result: score.earned === score.total ? "correct" : "incorrect",
2025
+ virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
2026
+ }
2027
+ });
2028
+ }
2029
+ return score;
2015
2030
  });
2016
2031
  _defineProperty(this, "getUserInput", () => {
2017
2032
  return Expression.getUserInputFromProps(this.props);
@@ -2036,8 +2051,8 @@ class Expression extends React__namespace.Component {
2036
2051
  this.props.trackInteraction();
2037
2052
  });
2038
2053
  _defineProperty(this, "_handleFocus", () => {
2039
- var _this$props$analytics;
2040
- (_this$props$analytics = this.props.analytics) === null || _this$props$analytics === void 0 ? void 0 : _this$props$analytics.onAnalyticsEvent({
2054
+ var _this$props$analytics2;
2055
+ (_this$props$analytics2 = this.props.analytics) === null || _this$props$analytics2 === void 0 ? void 0 : _this$props$analytics2.onAnalyticsEvent({
2041
2056
  type: "perseus:expression-focused",
2042
2057
  payload: null
2043
2058
  });
@@ -2244,16 +2259,6 @@ class Expression extends React__namespace.Component {
2244
2259
  // @ts-expect-error - TS2339 - Property 'insert' does not exist on type 'ReactInstance'.
2245
2260
  this.refs.input.insert(keyPressed);
2246
2261
  }
2247
- sendExpressionEvaluatedEvent(result) {
2248
- var _this$props$analytics2;
2249
- (_this$props$analytics2 = this.props.analytics) === null || _this$props$analytics2 === void 0 ? void 0 : _this$props$analytics2.onAnalyticsEvent({
2250
- type: "perseus:expression-evaluated",
2251
- payload: {
2252
- result,
2253
- virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
2254
- }
2255
- });
2256
- }
2257
2262
  render() {
2258
2263
  var _this$props$keypadCon, _this$props$analytics3;
2259
2264
  if (this.props.apiOptions.customKeypad) {
@@ -3739,10 +3744,10 @@ Zoom.prototype.dispose = function () {
3739
3744
  };
3740
3745
  const ZoomService = new ZoomServiceClass();
3741
3746
 
3742
- // @baseFontFamily: "Proxima Nova", sans-serif;
3743
- const baseFontFamily$1 = "'Proxima Nova',sans-serif";
3744
- // @boldFontFamily: "Proxima Nova Semibold", sans-serif;
3745
- const boldFontFamily = "'Proxima Nova Semibold',sans-serif";
3747
+ // @baseFontFamily: "Lato", sans-serif;
3748
+ const baseFontFamily$1 = "'Lato', sans-serif";
3749
+ // @boldFontFamily: "Lato-Bold", "Lato", sans-serif;
3750
+ const boldFontFamily = "'Lato-Bold', 'Lato', sans-serif";
3746
3751
  // @green: #76a005;
3747
3752
  const green$1 = "#76A005";
3748
3753
  // @kaGreen: #71b307;
@@ -18763,6 +18768,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18763
18768
  class WidgetContainer extends React__namespace.Component {
18764
18769
  constructor() {
18765
18770
  super(...arguments);
18771
+ _defineProperty(this, "widgetRef", /*#__PURE__*/React__namespace.createRef());
18766
18772
  _defineProperty(this, "state", {
18767
18773
  // TODO(benkomalo): before we're mounted, we don't know how big
18768
18774
  // we're going to be, so just default to MEDIUM for now. :/ In the
@@ -18772,8 +18778,7 @@ class WidgetContainer extends React__namespace.Component {
18772
18778
  widgetProps: this.props.initialProps
18773
18779
  });
18774
18780
  _defineProperty(this, "getWidget", () => {
18775
- // eslint-disable-next-line react/no-string-refs
18776
- return this.refs.widget;
18781
+ return this.widgetRef.current;
18777
18782
  });
18778
18783
  _defineProperty(this, "replaceWidgetProps", newWidgetProps => {
18779
18784
  this.setState({
@@ -18868,14 +18873,30 @@ class WidgetContainer extends React__namespace.Component {
18868
18873
  return /*#__PURE__*/React__namespace.createElement("div", {
18869
18874
  className: className,
18870
18875
  style: isStatic ? staticContainerStyles : {}
18871
- }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends({}, this.state.widgetProps, {
18872
- linterContext: linterContext,
18873
- containerSizeClass: this.state.sizeClass
18874
- // eslint-disable-next-line react/no-string-refs
18875
- ,
18876
- ref: "widget"
18877
- })), isStatic && /*#__PURE__*/React__namespace.createElement("div", {
18878
- style: staticOverlayStyles
18876
+ }, /*#__PURE__*/React__namespace.createElement(DependenciesContext.Consumer, null, _ref => {
18877
+ let {
18878
+ analytics
18879
+ } = _ref;
18880
+ return /*#__PURE__*/React__namespace.createElement(ErrorBoundary, {
18881
+ metadata: {
18882
+ widget_type: type,
18883
+ widget_id: this.props.id
18884
+ },
18885
+ onError: error => {
18886
+ analytics.onAnalyticsEvent({
18887
+ type: "perseus:widget-rendering-error",
18888
+ payload: {
18889
+ widgetType: type
18890
+ }
18891
+ });
18892
+ }
18893
+ }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends({}, this.state.widgetProps, {
18894
+ linterContext: linterContext,
18895
+ containerSizeClass: this.state.sizeClass,
18896
+ ref: this.widgetRef
18897
+ })), isStatic && /*#__PURE__*/React__namespace.createElement("div", {
18898
+ style: staticOverlayStyles
18899
+ }));
18879
18900
  }));
18880
18901
  }
18881
18902
  UNSAFE_componentWillReceiveProps(nextProps) {
@@ -18899,6 +18920,7 @@ const rContainsNonWhitespace = /\S/;
18899
18920
  const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
18900
18921
  const noopOnRender = () => {};
18901
18922
  const SHOULD_CLEAR_WIDGETS_PROP_LIST = ["content", "problemNum", "widgets"];
18923
+ const makeContainerId = id => "container:" + id;
18902
18924
 
18903
18925
  // Check if one focus path / id path is a prefix of another
18904
18926
  // The focus path null will never be a prefix of any non-null
@@ -18945,6 +18967,7 @@ class Renderer extends React__namespace.Component {
18945
18967
  _defineProperty(this, "_isMounted", void 0);
18946
18968
  _defineProperty(this, "_isTwoColumn", void 0);
18947
18969
  _defineProperty(this, "_translationLinter", void 0);
18970
+ _defineProperty(this, "_widgetContainers", new Map());
18948
18971
  _defineProperty(this, "lastRenderedMarkdown", void 0);
18949
18972
  _defineProperty(this, "reuseMarkdown", void 0);
18950
18973
  _defineProperty(this, "translationIndex", void 0);
@@ -19018,19 +19041,22 @@ class Renderer extends React__namespace.Component {
19018
19041
  // By this point we should have no duplicates, which are
19019
19042
  // filtered out in this.render(), so we shouldn't have to
19020
19043
  // worry about using this widget key and ref:
19021
- return /*#__PURE__*/React__namespace.createElement(ErrorBoundary, {
19022
- key: "container:" + id,
19023
- metadata: {
19024
- widget_type: type,
19025
- widget_id: id
19026
- }
19027
- }, /*#__PURE__*/React__namespace.createElement(WidgetContainer, {
19028
- ref: "container:" + id,
19044
+ return /*#__PURE__*/React__namespace.createElement(WidgetContainer, {
19045
+ key: makeContainerId(id),
19046
+ id: id,
19047
+ ref: node => {
19048
+ const containerId = makeContainerId(id);
19049
+ if (node != null) {
19050
+ this._widgetContainers.set(containerId, node);
19051
+ } else {
19052
+ this._widgetContainers.delete(containerId);
19053
+ }
19054
+ },
19029
19055
  type: type,
19030
19056
  initialProps: this.getWidgetProps(id),
19031
19057
  shouldHighlight: shouldHighlight,
19032
19058
  linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "widget")
19033
- }));
19059
+ });
19034
19060
  }
19035
19061
  return null;
19036
19062
  });
@@ -19058,7 +19084,6 @@ class Renderer extends React__namespace.Component {
19058
19084
  }
19059
19085
  return {
19060
19086
  ...widgetProps,
19061
- ref: id,
19062
19087
  widgetId: id,
19063
19088
  alignment: widgetInfo && widgetInfo.alignment,
19064
19089
  // When determining if a widget is static, we verify that the widget is not an
@@ -19191,12 +19216,10 @@ class Renderer extends React__namespace.Component {
19191
19216
  return [...this.findInternalWidgets(filterCriterion), ...this.props.findExternalWidgets(filterCriterion)];
19192
19217
  });
19193
19218
  _defineProperty(this, "getWidgetInstance", id => {
19194
- // eslint-disable-next-line react/no-string-refs
19195
- const ref = this.refs["container:" + id];
19219
+ const ref = this._widgetContainers.get(makeContainerId(id));
19196
19220
  if (!ref) {
19197
19221
  return null;
19198
19222
  }
19199
- // @ts-expect-error - TS2339 - Property 'getWidget' does not exist on type 'ReactInstance'.
19200
19223
  return ref.getWidget();
19201
19224
  });
19202
19225
  _defineProperty(this, "_onWidgetFocus", function (id) {
@@ -19910,7 +19933,10 @@ class Renderer extends React__namespace.Component {
19910
19933
  const widget = this.getWidgetInstance(id);
19911
19934
  // widget can be undefined if it hasn't yet been rendered
19912
19935
  if (widget && widget.simpleValidate) {
19913
- widgetScores[id] = widget.simpleValidate(props === null || props === void 0 ? void 0 : props.options, onInputError);
19936
+ widgetScores[id] = widget.simpleValidate({
19937
+ ...(props === null || props === void 0 ? void 0 : props.options),
19938
+ scoring: true
19939
+ }, onInputError);
19914
19940
  }
19915
19941
  });
19916
19942
  return widgetScores;
@@ -20061,9 +20087,7 @@ class Renderer extends React__namespace.Component {
20061
20087
  // they are re-rendered, so even if they've been
20062
20088
  // re-rendered we need to call these methods on them.
20063
20089
  ___default["default"].each(this.widgetIds, id => {
20064
- // eslint-disable-next-line react/no-string-refs
20065
- const container = this.refs["container:" + id];
20066
- // @ts-expect-error - TS2339 - Property 'replaceWidgetProps' does not exist on type 'ReactInstance'.
20090
+ const container = this._widgetContainers.get(makeContainerId(id));
20067
20091
  container && container.replaceWidgetProps(this.getWidgetProps(id));
20068
20092
  });
20069
20093
  if (this.props.serializedState && !___default["default"].isEqual(this.props.serializedState, this.getSerializedState())) {
@@ -42366,8 +42390,7 @@ class Transformer extends React__namespace.Component {
42366
42390
  // @ts-expect-error - TS2345 - Argument of type '{ type: "dilation"; center: any; scale: number; }' is not assignable to parameter of type 'PerseusTransformerTransformation'.
42367
42391
  self.doTransform({
42368
42392
  type: "dilation",
42369
- // NOTE(kevinb): It's hard to convince that this.dilationCircle
42370
- // will still be defined here. center: self.dilationCircle.centerPoint.coord,
42393
+ center: self.dilationCircle.centerPoint.coord,
42371
42394
  scale: newRadius / oldRadius
42372
42395
  });
42373
42396
  },
@@ -44015,7 +44038,7 @@ const init = function (options) {
44015
44038
 
44016
44039
  // This file is processed by a Rollup plugin (replace) to inject the production
44017
44040
  const libName = "@khanacademy/perseus";
44018
- const libVersion = "12.2.0";
44041
+ const libVersion = "12.3.0";
44019
44042
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44020
44043
 
44021
44044
  const apiVersion = {
@@ -44160,7 +44183,7 @@ class ArticleRenderer extends React__namespace.Component {
44160
44183
  },
44161
44184
  linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "article"),
44162
44185
  legacyPerseusLint: this.props.legacyPerseusLint
44163
- })));
44186
+ }, this.props.dependencies)));
44164
44187
  });
44165
44188
  return /*#__PURE__*/React__namespace.createElement("div", {
44166
44189
  className: classes
@@ -45256,7 +45279,7 @@ class ServerItemRenderer extends React__namespace.Component {
45256
45279
  };
45257
45280
  const questionRenderer = /*#__PURE__*/React__namespace.createElement(context$1.Provider, {
45258
45281
  value: contextValue
45259
- }, /*#__PURE__*/React__namespace.createElement(Renderer, {
45282
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({
45260
45283
  keypadElement: this.props.keypadElement,
45261
45284
  problemNum: this.props.problemNum,
45262
45285
  onInteractWithWidget: this.handleInteractWithWidget,
@@ -45272,7 +45295,7 @@ class ServerItemRenderer extends React__namespace.Component {
45272
45295
  content: this.props.item.question.content,
45273
45296
  widgets: this.props.item.question.widgets,
45274
45297
  images: this.props.item.question.images
45275
- }));
45298
+ }, this.props.dependencies)));
45276
45299
  const hintsRenderer = /*#__PURE__*/React__namespace.createElement(HintsRenderer, {
45277
45300
  hints: this.props.item.hints,
45278
45301
  hintsVisible: this.props.hintsVisible,