@khanacademy/perseus 12.2.0 → 12.4.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 (40) hide show
  1. package/CHANGELOG.md +42 -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 +4 -69
  5. package/dist/es/index.js +135 -78
  6. package/dist/es/index.js.map +1 -1
  7. package/dist/index.css +4 -69
  8. package/dist/index.js +130 -67
  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/__tests__/widget-container.test.tsx +126 -0
  17. package/src/article-renderer.tsx +15 -13
  18. package/src/error-boundary.tsx +4 -0
  19. package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +5 -5
  20. package/src/renderer.tsx +29 -27
  21. package/src/server-item-renderer.tsx +1 -0
  22. package/src/styles/articles.less +3 -1
  23. package/src/styles/constants.ts +4 -4
  24. package/src/styles/perseus-renderer.less +1 -11
  25. package/src/styles/variables.less +2 -2
  26. package/src/styles/variables.shared.less +2 -2
  27. package/src/styles/widgets/expression.less +0 -77
  28. package/src/styles/widgets/passage.less +1 -19
  29. package/src/widget-container.tsx +33 -10
  30. package/src/widgets/__stories__/label-image.stories.tsx +30 -0
  31. package/src/widgets/__testdata__/label-image.testdata.ts +63 -0
  32. package/src/widgets/__tests__/__snapshots__/graded-group-set.test.ts.snap +50 -19
  33. package/src/widgets/__tests__/__snapshots__/group.test.tsx.snap +5 -5
  34. package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +28 -28
  35. package/src/widgets/__tests__/expression.test.tsx +12 -9
  36. package/src/widgets/__tests__/renderQuestion.tsx +30 -13
  37. package/src/widgets/expression.tsx +19 -25
  38. package/src/widgets/graded-group-set.tsx +70 -25
  39. package/src/widgets/transformer.tsx +1 -2
  40. package/tsconfig-build.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @khanacademy/perseus
2
2
 
3
+ ## 12.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#784](https://github.com/Khan/perseus/pull/784) [`cfff6ad1`](https://github.com/Khan/perseus/commit/cfff6ad16a3923418a3c75816af7c99689b94da4) Thanks [@nedredmond](https://github.com/nedredmond)! - Graded Group Set indicator dots now have a 24x24 clickable area as well as hover/focus states. Stretching issue on mobile resolved as well.
8
+
9
+ * [#783](https://github.com/Khan/perseus/pull/783) [`79403e06`](https://github.com/Khan/perseus/commit/79403e06eedb597d7818d6c858bbba6f51ff3fe1) Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Adds 'widgetId' to the 'perseus:widget-rendering-error' analytics event.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [[`79403e06`](https://github.com/Khan/perseus/commit/79403e06eedb597d7818d6c858bbba6f51ff3fe1), [`ed00ee59`](https://github.com/Khan/perseus/commit/ed00ee59dcdeb20a66709c6b7d3474da55d58e4d)]:
14
+ - @khanacademy/perseus-core@1.3.0
15
+ - @khanacademy/math-input@14.2.2
16
+ - @khanacademy/kas@0.3.5
17
+ - @khanacademy/kmath@0.1.6
18
+ - @khanacademy/perseus-linter@0.3.8
19
+ - @khanacademy/pure-markdown@0.2.10
20
+ - @khanacademy/simple-markdown@0.10.4
21
+
22
+ ## 12.3.0
23
+
24
+ ### Minor Changes
25
+
26
+ - [#780](https://github.com/Khan/perseus/pull/780) [`376eb0e4`](https://github.com/Khan/perseus/commit/376eb0e4aaaa4c7a90fd6107a84bb74d382b077c) Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Adds an `onError` callback prop to the ErrorBoundary component. Uses this new callback in the WidgetContainer component to report all widget rendering failures through the analytics API.
27
+
28
+ ### Patch Changes
29
+
30
+ - [#759](https://github.com/Khan/perseus/pull/759) [`c431c4b8`](https://github.com/Khan/perseus/commit/c431c4b8147ae0630df2d2b19b0f5a5b5f04d4bf) Thanks [@handeyeco](https://github.com/handeyeco)! - Remove references to unused font families
31
+
32
+ * [#775](https://github.com/Khan/perseus/pull/775) [`cb529549`](https://github.com/Khan/perseus/commit/cb5295497fe74e3f4cc00eef0f99da9f83f58e4d) Thanks [@nedredmond](https://github.com/nedredmond)! - Added flag to `simpleValidate` call from Renderer to ensure we call analytics on MathInputEvaluated only when the answer is submitted
33
+
34
+ - [#781](https://github.com/Khan/perseus/pull/781) [`9b9dfd4f`](https://github.com/Khan/perseus/commit/9b9dfd4f6779d129040d9afcf3205f1863a64c25) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Fix for broken dilation tool in the Transformer Widget
35
+
36
+ - Updated dependencies [[`c431c4b8`](https://github.com/Khan/perseus/commit/c431c4b8147ae0630df2d2b19b0f5a5b5f04d4bf), [`fb84640d`](https://github.com/Khan/perseus/commit/fb84640de911a8e8817829985fe9956e83a7f7d1), [`376eb0e4`](https://github.com/Khan/perseus/commit/376eb0e4aaaa4c7a90fd6107a84bb74d382b077c), [`33cc24c3`](https://github.com/Khan/perseus/commit/33cc24c33f62cd87de7594ba575d311fd465e2e0)]:
37
+ - @khanacademy/math-input@14.2.1
38
+ - @khanacademy/perseus-core@1.2.0
39
+ - @khanacademy/kas@0.3.4
40
+ - @khanacademy/kmath@0.1.5
41
+ - @khanacademy/perseus-linter@0.3.7
42
+ - @khanacademy/pure-markdown@0.2.9
43
+ - @khanacademy/simple-markdown@0.10.3
44
+
3
45
  ## 12.2.0
4
46
 
5
47
  ### Minor Changes
@@ -3,6 +3,7 @@
3
3
  * composed of multiple (Renderer) sections concatenated together.
4
4
  */
5
5
  import * as React from "react";
6
+ import { DependenciesContext } from "./dependencies";
6
7
  import Renderer from "./renderer";
7
8
  import type { KeypadProps } from "./mixins/provide-keypad";
8
9
  import type { PerseusRenderer } from "./perseus-types";
@@ -10,7 +11,7 @@ import type { APIOptions, PerseusDependenciesV2 } from "./types";
10
11
  import type { KeypadAPI } from "@khanacademy/math-input";
11
12
  import type { KeypadContextRendererInterface } from "@khanacademy/perseus-core";
12
13
  import type { LinterContextProps } from "@khanacademy/perseus-linter";
13
- type Props = {
14
+ type Props = Partial<React.ContextType<typeof DependenciesContext>> & KeypadProps & {
14
15
  apiOptions: APIOptions;
15
16
  json: PerseusRenderer | ReadonlyArray<PerseusRenderer>;
16
17
  /**
@@ -21,7 +22,7 @@ type Props = {
21
22
  legacyPerseusLint?: ReadonlyArray<string>;
22
23
  keypadElement?: KeypadAPI | null | undefined;
23
24
  dependencies: PerseusDependenciesV2;
24
- } & KeypadProps;
25
+ };
25
26
  type DefaultProps = {
26
27
  apiOptions: Props["apiOptions"];
27
28
  useNewStyles: Props["useNewStyles"];
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
  type Props = {
3
3
  children: React.ReactNode;
4
4
  metadata?: Record<string, string>;
5
+ onError?: (error: Error, info: any) => void;
5
6
  };
6
7
  type State = {
7
8
  error: string;
package/dist/es/index.css CHANGED
@@ -1742,11 +1742,11 @@ body.mobile ul.inequalities-one-line-radios > li {
1742
1742
  .framework-perseus.perseus-mobile .clearfix > .perseus-renderer {
1743
1743
  margin-bottom: 32px;
1744
1744
  }
1745
- .framework-perseus.perseus-mobile .perseus-renderer > .paragraph ul:not(.perseus-widget-radio) {
1745
+ .framework-perseus.perseus-mobile .perseus-renderer > .paragraph ul:not(.perseus-widget-radio, .indicatorContainer) {
1746
1746
  margin: 0 0 0 1em;
1747
1747
  padding: 0;
1748
1748
  }
1749
- .framework-perseus.perseus-mobile .perseus-renderer > .paragraph ul:not(.perseus-widget-radio) > li {
1749
+ .framework-perseus.perseus-mobile .perseus-renderer > .paragraph ul:not(.perseus-widget-radio, .indicatorContainer) > li {
1750
1750
  padding-left: 10px;
1751
1751
  margin-bottom: 24px;
1752
1752
  }
@@ -2394,7 +2394,7 @@ img.zoom-img {
2394
2394
  .framework-perseus .perseus-renderer > .paragraph > ol {
2395
2395
  margin: -11px 0px 22px 0px;
2396
2396
  }
2397
- .framework-perseus .paragraph ul:not(.perseus-widget-radio) {
2397
+ .framework-perseus .paragraph ul:not(.perseus-widget-radio, .indicatorContainer) {
2398
2398
  padding-left: 35px;
2399
2399
  list-style-type: disc;
2400
2400
  }
@@ -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/es/index.js CHANGED
@@ -49,6 +49,21 @@ function _extends() {
49
49
  return _extends.apply(this, arguments);
50
50
  }
51
51
 
52
+ function _objectWithoutPropertiesLoose(source, excluded) {
53
+ if (source == null) return {};
54
+ var target = {};
55
+ var sourceKeys = Object.keys(source);
56
+ var key, i;
57
+
58
+ for (i = 0; i < sourceKeys.length; i++) {
59
+ key = sourceKeys[i];
60
+ if (excluded.indexOf(key) >= 0) continue;
61
+ target[key] = source[key];
62
+ }
63
+
64
+ return target;
65
+ }
66
+
52
67
  const customKeyTranslator = _extends({}, keyTranslator, {
53
68
  // If there's something in the input that can become part of a
54
69
  // fraction, typing "/" puts it in the numerator. If not, typing
@@ -1870,6 +1885,7 @@ const KhanAnswerTypes = {
1870
1885
  }
1871
1886
  };
1872
1887
 
1888
+ const _excluded$4 = ["scoring"];
1873
1889
  const ERROR_TITLE = i18n._("Oops!");
1874
1890
  const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
1875
1891
  const insertBraces = value => {
@@ -1947,11 +1963,26 @@ class Expression extends React.Component {
1947
1963
  });
1948
1964
  }
1949
1965
  };
1950
- this.simpleValidate = (rubric, onInputError) => {
1951
- onInputError = onInputError || function () {};
1952
- const result = Expression.validate(this.getUserInput(), rubric, onInputError);
1953
- this.sendExpressionEvaluatedEvent(result.type === "invalid" ? "invalid" : result.earned === result.total ? "correct" : "incorrect");
1954
- return result;
1966
+ this.simpleValidate = (_ref, onInputError) => {
1967
+ let {
1968
+ scoring
1969
+ } = _ref,
1970
+ rubric = _objectWithoutPropertiesLoose(_ref, _excluded$4);
1971
+ const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
1972
+
1973
+ // "scoring" is a flag that indicates when we are checking answers.
1974
+ // otherwise, we may just be checking validity after changes.
1975
+ if (scoring && score.type !== "invalid") {
1976
+ var _this$props$analytics;
1977
+ (_this$props$analytics = this.props.analytics) == null ? void 0 : _this$props$analytics.onAnalyticsEvent({
1978
+ type: "perseus:expression-evaluated",
1979
+ payload: {
1980
+ result: score.earned === score.total ? "correct" : "incorrect",
1981
+ virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
1982
+ }
1983
+ });
1984
+ }
1985
+ return score;
1955
1986
  };
1956
1987
  this.getUserInput = () => {
1957
1988
  return Expression.getUserInputFromProps(this.props);
@@ -1973,8 +2004,8 @@ class Expression extends React.Component {
1973
2004
  this.props.trackInteraction();
1974
2005
  };
1975
2006
  this._handleFocus = () => {
1976
- var _this$props$analytics;
1977
- (_this$props$analytics = this.props.analytics) == null ? void 0 : _this$props$analytics.onAnalyticsEvent({
2007
+ var _this$props$analytics2;
2008
+ (_this$props$analytics2 = this.props.analytics) == null ? void 0 : _this$props$analytics2.onAnalyticsEvent({
1978
2009
  type: "perseus:expression-focused",
1979
2010
  payload: null
1980
2011
  });
@@ -2182,16 +2213,6 @@ class Expression extends React.Component {
2182
2213
  // @ts-expect-error - TS2339 - Property 'insert' does not exist on type 'ReactInstance'.
2183
2214
  this.refs.input.insert(keyPressed);
2184
2215
  }
2185
- sendExpressionEvaluatedEvent(result) {
2186
- var _this$props$analytics2;
2187
- (_this$props$analytics2 = this.props.analytics) == null ? void 0 : _this$props$analytics2.onAnalyticsEvent({
2188
- type: "perseus:expression-evaluated",
2189
- payload: {
2190
- result,
2191
- virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
2192
- }
2193
- });
2194
- }
2195
2216
  render() {
2196
2217
  var _this$props$keypadCon, _this$props$analytics3;
2197
2218
  if (this.props.apiOptions.customKeypad) {
@@ -3680,10 +3701,10 @@ Zoom.prototype.dispose = function () {
3680
3701
  };
3681
3702
  const ZoomService = new ZoomServiceClass();
3682
3703
 
3683
- // @baseFontFamily: "Proxima Nova", sans-serif;
3684
- const baseFontFamily$1 = "'Proxima Nova',sans-serif";
3685
- // @boldFontFamily: "Proxima Nova Semibold", sans-serif;
3686
- const boldFontFamily = "'Proxima Nova Semibold',sans-serif";
3704
+ // @baseFontFamily: "Lato", sans-serif;
3705
+ const baseFontFamily$1 = "'Lato', sans-serif";
3706
+ // @boldFontFamily: "Lato-Bold", "Lato", sans-serif;
3707
+ const boldFontFamily = "'Lato-Bold', 'Lato', sans-serif";
3687
3708
  // @green: #76a005;
3688
3709
  const green$1 = "#76A005";
3689
3710
  // @kaGreen: #71b307;
@@ -17328,9 +17349,11 @@ class ErrorBoundary extends React.Component {
17328
17349
  };
17329
17350
  }
17330
17351
  componentDidCatch(error, info) {
17352
+ var _this$props$onError, _this$props;
17331
17353
  this.setState({
17332
17354
  error: error.toString()
17333
17355
  });
17356
+ (_this$props$onError = (_this$props = this.props).onError) == null ? void 0 : _this$props$onError.call(_this$props, error, info);
17334
17357
  Log.error("Perseus error boundary caught error", Errors.Internal, {
17335
17358
  cause: error,
17336
17359
  loggedMetadata: _extends({
@@ -18675,6 +18698,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18675
18698
  class WidgetContainer extends React.Component {
18676
18699
  constructor(...args) {
18677
18700
  super(...args);
18701
+ this.widgetRef = /*#__PURE__*/React.createRef();
18678
18702
  this.state = {
18679
18703
  // TODO(benkomalo): before we're mounted, we don't know how big
18680
18704
  // we're going to be, so just default to MEDIUM for now. :/ In the
@@ -18684,8 +18708,7 @@ class WidgetContainer extends React.Component {
18684
18708
  widgetProps: this.props.initialProps
18685
18709
  };
18686
18710
  this.getWidget = () => {
18687
- // eslint-disable-next-line react/no-string-refs
18688
- return this.refs.widget;
18711
+ return this.widgetRef.current;
18689
18712
  };
18690
18713
  this.replaceWidgetProps = newWidgetProps => {
18691
18714
  this.setState({
@@ -18779,15 +18802,29 @@ class WidgetContainer extends React.Component {
18779
18802
  return /*#__PURE__*/React.createElement("div", {
18780
18803
  className: className,
18781
18804
  style: isStatic ? staticContainerStyles : {}
18805
+ }, /*#__PURE__*/React.createElement(DependenciesContext.Consumer, null, ({
18806
+ analytics
18807
+ }) => /*#__PURE__*/React.createElement(ErrorBoundary, {
18808
+ metadata: {
18809
+ widget_type: type,
18810
+ widget_id: this.props.id
18811
+ },
18812
+ onError: () => {
18813
+ analytics.onAnalyticsEvent({
18814
+ type: "perseus:widget-rendering-error",
18815
+ payload: {
18816
+ widgetType: type,
18817
+ widgetId: this.props.id
18818
+ }
18819
+ });
18820
+ }
18782
18821
  }, /*#__PURE__*/React.createElement(WidgetType, _extends({}, this.state.widgetProps, {
18783
18822
  linterContext: linterContext,
18784
- containerSizeClass: this.state.sizeClass
18785
- // eslint-disable-next-line react/no-string-refs
18786
- ,
18787
- ref: "widget"
18823
+ containerSizeClass: this.state.sizeClass,
18824
+ ref: this.widgetRef
18788
18825
  })), isStatic && /*#__PURE__*/React.createElement("div", {
18789
18826
  style: staticOverlayStyles
18790
- }));
18827
+ }))));
18791
18828
  }
18792
18829
  UNSAFE_componentWillReceiveProps(nextProps) {
18793
18830
  if (this.props.type !== nextProps.type) {
@@ -18810,6 +18847,7 @@ const rContainsNonWhitespace = /\S/;
18810
18847
  const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
18811
18848
  const noopOnRender = () => {};
18812
18849
  const SHOULD_CLEAR_WIDGETS_PROP_LIST = ["content", "problemNum", "widgets"];
18850
+ const makeContainerId = id => "container:" + id;
18813
18851
 
18814
18852
  // Check if one focus path / id path is a prefix of another
18815
18853
  // The focus path null will never be a prefix of any non-null
@@ -18854,6 +18892,7 @@ class Renderer extends React.Component {
18854
18892
  this._isMounted = void 0;
18855
18893
  this._isTwoColumn = void 0;
18856
18894
  this._translationLinter = void 0;
18895
+ this._widgetContainers = new Map();
18857
18896
  this.lastRenderedMarkdown = void 0;
18858
18897
  this.reuseMarkdown = void 0;
18859
18898
  this.translationIndex = void 0;
@@ -18924,19 +18963,22 @@ class Renderer extends React.Component {
18924
18963
  // By this point we should have no duplicates, which are
18925
18964
  // filtered out in this.render(), so we shouldn't have to
18926
18965
  // worry about using this widget key and ref:
18927
- return /*#__PURE__*/React.createElement(ErrorBoundary, {
18928
- key: "container:" + id,
18929
- metadata: {
18930
- widget_type: type,
18931
- widget_id: id
18932
- }
18933
- }, /*#__PURE__*/React.createElement(WidgetContainer, {
18934
- ref: "container:" + id,
18966
+ return /*#__PURE__*/React.createElement(WidgetContainer, {
18967
+ key: makeContainerId(id),
18968
+ id: id,
18969
+ ref: node => {
18970
+ const containerId = makeContainerId(id);
18971
+ if (node != null) {
18972
+ this._widgetContainers.set(containerId, node);
18973
+ } else {
18974
+ this._widgetContainers.delete(containerId);
18975
+ }
18976
+ },
18935
18977
  type: type,
18936
18978
  initialProps: this.getWidgetProps(id),
18937
18979
  shouldHighlight: shouldHighlight,
18938
18980
  linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "widget")
18939
- }));
18981
+ });
18940
18982
  }
18941
18983
  return null;
18942
18984
  };
@@ -18963,7 +19005,6 @@ class Renderer extends React.Component {
18963
19005
  getTracking(widgetInfo && widgetInfo.type));
18964
19006
  }
18965
19007
  return _extends({}, widgetProps, {
18966
- ref: id,
18967
19008
  widgetId: id,
18968
19009
  alignment: widgetInfo && widgetInfo.alignment,
18969
19010
  // When determining if a widget is static, we verify that the widget is not an
@@ -19095,12 +19136,10 @@ class Renderer extends React.Component {
19095
19136
  return [...this.findInternalWidgets(filterCriterion), ...this.props.findExternalWidgets(filterCriterion)];
19096
19137
  };
19097
19138
  this.getWidgetInstance = id => {
19098
- // eslint-disable-next-line react/no-string-refs
19099
- const ref = this.refs["container:" + id];
19139
+ const ref = this._widgetContainers.get(makeContainerId(id));
19100
19140
  if (!ref) {
19101
19141
  return null;
19102
19142
  }
19103
- // @ts-expect-error - TS2339 - Property 'getWidget' does not exist on type 'ReactInstance'.
19104
19143
  return ref.getWidget();
19105
19144
  };
19106
19145
  this._onWidgetFocus = (id, focusPath = []) => {
@@ -19788,7 +19827,9 @@ class Renderer extends React.Component {
19788
19827
  const widget = this.getWidgetInstance(id);
19789
19828
  // widget can be undefined if it hasn't yet been rendered
19790
19829
  if (widget && widget.simpleValidate) {
19791
- widgetScores[id] = widget.simpleValidate(props == null ? void 0 : props.options, onInputError);
19830
+ widgetScores[id] = widget.simpleValidate(_extends({}, props == null ? void 0 : props.options, {
19831
+ scoring: true
19832
+ }), onInputError);
19792
19833
  }
19793
19834
  });
19794
19835
  return widgetScores;
@@ -19938,9 +19979,7 @@ class Renderer extends React.Component {
19938
19979
  // they are re-rendered, so even if they've been
19939
19980
  // re-rendered we need to call these methods on them.
19940
19981
  _.each(this.widgetIds, id => {
19941
- // eslint-disable-next-line react/no-string-refs
19942
- const container = this.refs["container:" + id];
19943
- // @ts-expect-error - TS2339 - Property 'replaceWidgetProps' does not exist on type 'ReactInstance'.
19982
+ const container = this._widgetContainers.get(makeContainerId(id));
19944
19983
  container && container.replaceWidgetProps(this.getWidgetProps(id));
19945
19984
  });
19946
19985
  if (this.props.serializedState && !_.isEqual(this.props.serializedState, this.getSerializedState())) {
@@ -20858,21 +20897,6 @@ InputWithExamples.defaultProps = {
20858
20897
  className: ""
20859
20898
  };
20860
20899
 
20861
- function _objectWithoutPropertiesLoose(source, excluded) {
20862
- if (source == null) return {};
20863
- var target = {};
20864
- var sourceKeys = Object.keys(source);
20865
- var key, i;
20866
-
20867
- for (i = 0; i < sourceKeys.length; i++) {
20868
- key = sourceKeys[i];
20869
- if (excluded.indexOf(key) >= 0) continue;
20870
- target[key] = source[key];
20871
- }
20872
-
20873
- return target;
20874
- }
20875
-
20876
20900
  const _excluded$3 = ["keypadElement", "onFocus", "value"];
20877
20901
  class SimpleKeypadInput extends React.Component {
20878
20902
  constructor(...args) {
@@ -24884,22 +24908,33 @@ class Indicators extends React.Component {
24884
24908
  }
24885
24909
  render() {
24886
24910
  return /*#__PURE__*/React.createElement("ul", {
24887
- className: css(styles$j.indicatorContainer)
24911
+ // reduntantly add class name for use in .less files--
24912
+ // the styles object key gets hashed
24913
+ className: classNames(css(styles$j.indicatorContainer), "indicatorContainer")
24888
24914
  }, this.props.gradedGroups.map(({
24889
24915
  title
24890
24916
  }, i) => /*#__PURE__*/React.createElement("li", {
24917
+ className: css(styles$j.indicator),
24918
+ key: title
24919
+ }, /*#__PURE__*/React.createElement(Clickable, {
24891
24920
  role: "button",
24892
24921
  "aria-label": i18n._("Skip to %(title)s", {
24893
24922
  title
24894
24923
  }),
24895
- key: title,
24896
- className: css(styles$j.indicator, i === this.props.currentGroup && styles$j.selectedIndicator),
24897
- tabIndex: 0,
24924
+ style: styles$j.indicatorButton,
24898
24925
  onClick: () => this.props.onChangeCurrentGroup(i),
24899
24926
  onKeyDown: e => this.handleKeyDown(e, i)
24900
- }, i === this.props.currentGroup && /*#__PURE__*/React.createElement("span", {
24927
+ }, ({
24928
+ hovered,
24929
+ focused,
24930
+ pressed
24931
+ }) => /*#__PURE__*/React.createElement(View, {
24932
+ style: [styles$j.indicatorDot, (hovered || focused || pressed) && styles$j.indicatorDotFocused]
24933
+ }, i === this.props.currentGroup && /*#__PURE__*/React.createElement(View, {
24934
+ style: styles$j.indicatorDotActive
24935
+ }, /*#__PURE__*/React.createElement("span", {
24901
24936
  className: css(a11y.srOnly)
24902
- }, i18n._("Current")))));
24937
+ }, i18n._("Current"))))))));
24903
24938
  }
24904
24939
  }
24905
24940
  // TODO(jared): find a better name for this :) and for GradedGroup; the names
@@ -25047,19 +25082,42 @@ const styles$j = StyleSheet.create({
25047
25082
  display: "flex",
25048
25083
  flexDirection: "row",
25049
25084
  listStyle: "none",
25050
- margin: "unset"
25085
+ margin: "unset",
25086
+ paddingInlineStart: "unset",
25087
+ flexWrap: "wrap"
25051
25088
  },
25052
25089
  indicator: {
25090
+ width: 24,
25091
+ height: 24
25092
+ },
25093
+ indicatorButton: {
25094
+ width: "100%",
25095
+ height: "100%",
25096
+ display: "flex",
25097
+ flexWrap: "wrap",
25098
+ placeContent: "center",
25099
+ cursor: "pointer",
25100
+ ":focus": {
25101
+ outline: "none"
25102
+ }
25103
+ },
25104
+ indicatorDot: {
25105
+ boxSizing: "content-box",
25053
25106
  width: 10,
25054
25107
  height: 10,
25055
25108
  borderRadius: "100%",
25056
- border: "3px solid",
25109
+ borderWidth: 2,
25057
25110
  borderColor: Color.blue,
25058
- marginLeft: 5,
25059
- cursor: "pointer"
25111
+ borderStyle: "solid"
25060
25112
  },
25061
- selectedIndicator: {
25062
- backgroundColor: Color.blue
25113
+ indicatorDotFocused: {
25114
+ borderWidth: 5,
25115
+ borderStyle: "double"
25116
+ },
25117
+ indicatorDotActive: {
25118
+ backgroundColor: Color.blue,
25119
+ width: "100%",
25120
+ height: "100%"
25063
25121
  },
25064
25122
  container: {
25065
25123
  borderTop: `1px solid ${gray76}`,
@@ -42117,8 +42175,7 @@ class Transformer extends React.Component {
42117
42175
  // @ts-expect-error - TS2345 - Argument of type '{ type: "dilation"; center: any; scale: number; }' is not assignable to parameter of type 'PerseusTransformerTransformation'.
42118
42176
  self.doTransform({
42119
42177
  type: "dilation",
42120
- // NOTE(kevinb): It's hard to convince that this.dilationCircle
42121
- // will still be defined here. center: self.dilationCircle.centerPoint.coord,
42178
+ center: self.dilationCircle.centerPoint.coord,
42122
42179
  scale: newRadius / oldRadius
42123
42180
  });
42124
42181
  },
@@ -43748,7 +43805,7 @@ const init = function init(options) {
43748
43805
 
43749
43806
  // This file is processed by a Rollup plugin (replace) to inject the production
43750
43807
  const libName = "@khanacademy/perseus";
43751
- const libVersion = "12.2.0";
43808
+ const libVersion = "12.4.0";
43752
43809
  addLibraryVersionToPerseusDebug(libName, libVersion);
43753
43810
 
43754
43811
  const apiVersion = {
@@ -43890,7 +43947,7 @@ class ArticleRenderer extends React.Component {
43890
43947
  }),
43891
43948
  linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "article"),
43892
43949
  legacyPerseusLint: this.props.legacyPerseusLint
43893
- })));
43950
+ }, this.props.dependencies)));
43894
43951
  });
43895
43952
  return /*#__PURE__*/React.createElement("div", {
43896
43953
  className: classes
@@ -44975,7 +45032,7 @@ class ServerItemRenderer extends React.Component {
44975
45032
  };
44976
45033
  const questionRenderer = /*#__PURE__*/React.createElement(context$1.Provider, {
44977
45034
  value: contextValue
44978
- }, /*#__PURE__*/React.createElement(Renderer, {
45035
+ }, /*#__PURE__*/React.createElement(Renderer, _extends({
44979
45036
  keypadElement: this.props.keypadElement,
44980
45037
  problemNum: this.props.problemNum,
44981
45038
  onInteractWithWidget: this.handleInteractWithWidget,
@@ -44991,7 +45048,7 @@ class ServerItemRenderer extends React.Component {
44991
45048
  content: this.props.item.question.content,
44992
45049
  widgets: this.props.item.question.widgets,
44993
45050
  images: this.props.item.question.images
44994
- }));
45051
+ }, this.props.dependencies)));
44995
45052
  const hintsRenderer = /*#__PURE__*/React.createElement(HintsRenderer, {
44996
45053
  hints: this.props.item.hints,
44997
45054
  hintsVisible: this.props.hintsVisible,