@khanacademy/perseus 13.2.0 → 15.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 (72) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/article-renderer.d.ts +1 -2
  3. package/dist/components/svg-image.d.ts +10 -9
  4. package/dist/es/index.js +51 -498
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.d.ts +0 -1
  7. package/dist/index.js +72 -511
  8. package/dist/index.js.map +1 -1
  9. package/dist/multi-items/item-types.d.ts +1 -1
  10. package/dist/multi-items/multi-renderer.d.ts +2 -2
  11. package/dist/perseus-api.d.ts +0 -3
  12. package/dist/server-item-renderer.d.ts +2 -15
  13. package/dist/strings.js +1 -0
  14. package/dist/styles/shared.d.ts +1 -0
  15. package/dist/types.d.ts +0 -2
  16. package/dist/util/a11y.d.ts +1 -0
  17. package/dist/widgets/expression.d.ts +0 -12
  18. package/dist/widgets/graded-group.d.ts +1 -1
  19. package/dist/widgets/label-image.d.ts +1 -1
  20. package/dist/widgets/matcher.d.ts +0 -1
  21. package/dist/widgets/passage.d.ts +1 -1
  22. package/dist/widgets/radio/widget.d.ts +1 -1
  23. package/dist/widgets/simulator.d.ts +0 -1
  24. package/dist/widgets/sorter.d.ts +0 -1
  25. package/package.json +8 -8
  26. package/src/__stories__/server-item-renderer.stories.tsx +1 -1
  27. package/src/__testdata__/server-item-renderer.testdata.ts +160 -0
  28. package/src/__tests__/article-renderer.test.tsx +2 -3
  29. package/src/__tests__/server-item-renderer.test.tsx +2 -2
  30. package/src/article-renderer.tsx +13 -15
  31. package/src/components/graph.tsx +14 -8
  32. package/src/components/svg-image.tsx +14 -12
  33. package/src/hints-renderer.tsx +1 -0
  34. package/src/index.ts +0 -1
  35. package/src/multi-items/item-types.ts +1 -1
  36. package/src/multi-items/multi-renderer.tsx +4 -4
  37. package/src/perseus-api.tsx +0 -6
  38. package/src/perseus-types.ts +8 -3
  39. package/src/server-item-renderer.tsx +12 -13
  40. package/src/types.ts +0 -5
  41. package/src/util.ts +1 -1
  42. package/src/widgets/__stories__/expression.stories.tsx +0 -1
  43. package/src/widgets/__stories__/random-widgets.stories.tsx +59 -0
  44. package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +0 -1
  45. package/src/widgets/__testdata__/categorizer.testdata.ts +40 -0
  46. package/src/widgets/__testdata__/dropdown.testdata.ts +38 -0
  47. package/src/widgets/__testdata__/explanation.testdata.ts +30 -0
  48. package/src/widgets/__testdata__/expression.testdata.ts +82 -0
  49. package/src/widgets/__testdata__/interactive-graph-random.testdata.ts +269 -0
  50. package/src/widgets/__testdata__/radio.testdata.ts +63 -0
  51. package/src/widgets/__testdata__/randomizers.ts +51 -0
  52. package/src/widgets/__tests__/expression-mobile.test.tsx +0 -2
  53. package/src/widgets/__tests__/expression.test.tsx +4 -15
  54. package/src/widgets/__tests__/randomizers.test.ts +53 -0
  55. package/src/widgets/expression.tsx +1 -5
  56. package/src/widgets/graded-group.tsx +1 -1
  57. package/src/widgets/image.tsx +27 -20
  58. package/src/widgets/label-image/strings.ts +3 -1
  59. package/src/widgets/label-image.tsx +2 -2
  60. package/src/widgets/passage-ref.tsx +1 -1
  61. package/src/widgets/passage.tsx +1 -1
  62. package/src/widgets/radio/widget.tsx +1 -1
  63. package/src/widgets/radio.ts +1 -1
  64. package/tsconfig-build.tsbuildinfo +1 -1
  65. package/dist/item-renderer.d.ts +0 -125
  66. package/dist/mixins/provide-keypad.d.ts +0 -39
  67. package/src/__stories__/item-renderer.stories.tsx +0 -30
  68. package/src/__testdata__/item-renderer.testdata.ts +0 -299
  69. package/src/__tests__/__snapshots__/item-renderer.test.tsx.snap +0 -224
  70. package/src/__tests__/item-renderer.test.tsx +0 -930
  71. package/src/item-renderer.tsx +0 -529
  72. package/src/mixins/provide-keypad.tsx +0 -138
package/dist/index.js CHANGED
@@ -891,10 +891,6 @@ const ApiOptions = {
891
891
  nativeKeypadProxy: PropTypes__default["default"].func,
892
892
  // Indicates whether or not to use mobile styling.
893
893
  isMobile: PropTypes__default["default"].bool,
894
- // Whether to use v1 (Legacy) Keypad for MathInput mobile or
895
- // use the new v2 Keypad
896
- // TODO [LC-1088]: remove after v2 release
897
- useV2Keypad: PropTypes__default["default"].bool,
898
894
  // A function, called with a bool indicating whether use of the
899
895
  // drawing area (scratchpad) should be allowed/disallowed.
900
896
  // Previously handled by `Khan.scratchpad.enable/disable`
@@ -933,7 +929,6 @@ const ApiOptions = {
933
929
  defaults: {
934
930
  isArticle: false,
935
931
  isMobile: false,
936
- useV2Keypad: false,
937
932
  onInputError: function () {},
938
933
  onFocusChange: function () {},
939
934
  GroupMetadataEditor: StubTagEditor,
@@ -1940,7 +1935,7 @@ const deriveKeypadVersion = apiOptions => {
1940
1935
  // a bit tricky, but this code will be relatively short-lived
1941
1936
  // as we coalesce onto the new, v2 Keypad, at which point we
1942
1937
  // can remove this `virtualKeypadVersion` field entirely.
1943
- return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : apiOptions.customKeypad === true ? apiOptions.useV2Keypad === true ? "MATH_INPUT_KEYPAD_V2" : "MATH_INPUT_KEYPAD_V1" : "PERSEUS_MATH_INPUT";
1938
+ return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
1944
1939
  };
1945
1940
  // The new, MathQuill input expression widget
1946
1941
  class Expression extends React__namespace.Component {
@@ -10265,7 +10260,7 @@ const pluck = function (table, subKey) {
10265
10260
  * });
10266
10261
  * {a: 2, b: 3}
10267
10262
  */
10268
- const mapObject$3 = function (obj, lambda) {
10263
+ const mapObject$2 = function (obj, lambda) {
10269
10264
  const result = {};
10270
10265
  ___default["default"].each(___default["default"].keys(obj), function (key) {
10271
10266
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
@@ -10291,7 +10286,7 @@ const mapObjectFromArray$1 = function (arr, lambda) {
10291
10286
  };
10292
10287
  var objective_ = {
10293
10288
  pluck: pluck,
10294
- mapObject: mapObject$3,
10289
+ mapObject: mapObject$2,
10295
10290
  mapObjectFromArray: mapObjectFromArray$1
10296
10291
  };
10297
10292
 
@@ -16561,7 +16556,7 @@ const doJSONP = function (url, options) {
16561
16556
  // system (with file://). We replace urls that start with `web+graphie`
16562
16557
  // in the perseus json with this `file+graphie` prefix to indicate that
16563
16558
  // they should have the `file://` protocol instead of `https://`.
16564
- const svgLocalLabelsRegex = /^file\+graphie\:/;
16559
+ const svgLocalLabelsRegex = /^file\+graphie:/;
16565
16560
  const hashRegex = /\/([^/]+)$/;
16566
16561
  function isImageProbablyPhotograph(imageUrl) {
16567
16562
  // TODO(david): Do an inventory to refine this heuristic. For example, what
@@ -18937,7 +18932,7 @@ class WidgetContainer extends React__namespace.Component {
18937
18932
  }
18938
18933
 
18939
18934
  const {
18940
- mapObject: mapObject$2,
18935
+ mapObject: mapObject$1,
18941
18936
  mapObjectFromArray
18942
18937
  } = objective_;
18943
18938
  const rContainsNonWhitespace = /\S/;
@@ -19164,7 +19159,7 @@ class Renderer extends React__namespace.Component {
19164
19159
  // @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
19165
19160
  _getAllWidgetsInfo = props => {
19166
19161
  // @ts-expect-error - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
19167
- return mapObject$2(props.widgets, (widgetInfo, widgetId) => {
19162
+ return mapObject$1(props.widgets, (widgetInfo, widgetId) => {
19168
19163
  if (!widgetInfo.type || !widgetInfo.alignment) {
19169
19164
  const newValues = {};
19170
19165
  if (!widgetInfo.type) {
@@ -19180,7 +19175,7 @@ class Renderer extends React__namespace.Component {
19180
19175
  };
19181
19176
  _getAllWidgetsStartProps = (allWidgetInfo, props) => {
19182
19177
  // @ts-expect-error - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
19183
- return mapObject$2(allWidgetInfo, widgetInfo => {
19178
+ return mapObject$1(allWidgetInfo, widgetInfo => {
19184
19179
  return getRendererPropsForWidgetInfo(widgetInfo, props.problemNum);
19185
19180
  });
19186
19181
  };
@@ -19300,7 +19295,7 @@ class Renderer extends React__namespace.Component {
19300
19295
  * state from that instead of the current widget props.
19301
19296
  */
19302
19297
  getSerializedState = widgetProps => {
19303
- return mapObject$2(widgetProps || this.state.widgetProps, (props, widgetId) => {
19298
+ return mapObject$1(widgetProps || this.state.widgetProps, (props, widgetId) => {
19304
19299
  const widget = this.getWidgetInstance(widgetId);
19305
19300
  if (widget && widget.getSerializedState) {
19306
19301
  return widget.getSerializedState();
@@ -19336,7 +19331,7 @@ class Renderer extends React__namespace.Component {
19336
19331
  }
19337
19332
  };
19338
19333
  this.setState({
19339
- widgetProps: mapObject$2(serializedState, (props, widgetId) => {
19334
+ widgetProps: mapObject$1(serializedState, (props, widgetId) => {
19340
19335
  const widget = this.getWidgetInstance(widgetId);
19341
19336
  if (widget && widget.restoreSerializedState) {
19342
19337
  // Note that we probably can't call
@@ -26857,43 +26852,50 @@ class ImageWidget extends React__namespace.Component {
26857
26852
  } = this.props;
26858
26853
  const backgroundImage = this.props.backgroundImage;
26859
26854
  if (backgroundImage.url) {
26860
- image = /*#__PURE__*/React__namespace.createElement(SvgImage, {
26861
- src: backgroundImage.url,
26862
- alt:
26863
- /* alt text is formatted in a sr-only
26864
- div next to the image in addition to
26865
- the alt attribute.
26866
- If there is no alt text at all,
26867
- we don't put an alt attribute on
26868
- the image, so that screen readers
26869
- know there's something they can't
26870
- read there :(.
26871
- NOTE: React <=0.13 (maybe later)
26872
- has a bug where it won't ever
26873
- remove an attribute, so if this
26874
- alt node is ever defined it's
26875
- not removed. This is sort of
26876
- dangerous, but we usually re-key
26877
- new renderers so that they're
26878
- rendered from scratch anyways,
26879
- so this shouldn't be a problem
26880
- in practice right now, although
26881
- it will exhibit weird behaviour
26882
- while editing. */
26883
- this.props.alt,
26884
- overrideAriaHidden: true,
26885
- width: backgroundImage.width,
26886
- height: backgroundImage.height,
26887
- preloader: apiOptions.imagePreloader,
26888
- extraGraphie: {
26889
- box: this.props.box,
26890
- range: this.props.range,
26891
- labels: this.props.labels
26892
- },
26893
- trackInteraction: this.props.trackInteraction,
26894
- zoomToFullSizeOnMobile: apiOptions.isMobile,
26895
- constrainHeight: apiOptions.isMobile,
26896
- allowFullBleed: apiOptions.isMobile
26855
+ const url = backgroundImage.url;
26856
+ image = /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref => {
26857
+ let {
26858
+ setAssetStatus
26859
+ } = _ref;
26860
+ return /*#__PURE__*/React__namespace.createElement(SvgImage, {
26861
+ src: url,
26862
+ alt:
26863
+ /* alt text is formatted in a sr-only
26864
+ div next to the image in addition to
26865
+ the alt attribute.
26866
+ If there is no alt text at all,
26867
+ we don't put an alt attribute on
26868
+ the image, so that screen readers
26869
+ know there's something they can't
26870
+ read there :(.
26871
+ NOTE: React <=0.13 (maybe later)
26872
+ has a bug where it won't ever
26873
+ remove an attribute, so if this
26874
+ alt node is ever defined it's
26875
+ not removed. This is sort of
26876
+ dangerous, but we usually re-key
26877
+ new renderers so that they're
26878
+ rendered from scratch anyways,
26879
+ so this shouldn't be a problem
26880
+ in practice right now, although
26881
+ it will exhibit weird behaviour
26882
+ while editing. */
26883
+ this.props.alt,
26884
+ overrideAriaHidden: true,
26885
+ width: backgroundImage.width,
26886
+ height: backgroundImage.height,
26887
+ preloader: apiOptions.imagePreloader,
26888
+ extraGraphie: {
26889
+ box: this.props.box,
26890
+ range: this.props.range,
26891
+ labels: this.props.labels
26892
+ },
26893
+ trackInteraction: this.props.trackInteraction,
26894
+ zoomToFullSizeOnMobile: apiOptions.isMobile,
26895
+ constrainHeight: apiOptions.isMobile,
26896
+ allowFullBleed: apiOptions.isMobile,
26897
+ setAssetStatus: setAssetStatus
26898
+ });
26897
26899
  });
26898
26900
  }
26899
26901
  if (this.props.alt) {
@@ -27896,15 +27898,20 @@ class Graph extends React__namespace.Component {
27896
27898
  const imageData = this.props.backgroundImage;
27897
27899
  if (imageData.url) {
27898
27900
  const scale = this.props.box[0] / interactiveSizes$1.defaultBoxSize;
27899
- image =
27900
- /*#__PURE__*/
27901
- // @ts-expect-error - TS2741 - Property 'alt' is missing in type '{ src: any; width: any; height: any; scale: number; responsive: false; }' but required in type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | "height" | "width" | "title" | "alt" | "trackInteraction" | "preloader" | "allowFullBleed" | "extraGraphie" | "overrideAriaHidden">'.
27902
- React__namespace.createElement(SvgImage, {
27903
- src: imageData.url,
27904
- width: imageData.width,
27905
- height: imageData.height,
27906
- scale: scale,
27907
- responsive: false
27901
+ image = /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref => {
27902
+ let {
27903
+ setAssetStatus
27904
+ } = _ref;
27905
+ return /*#__PURE__*/React__namespace.createElement(SvgImage
27906
+ // @ts-expect-error - TS2741 - Property 'alt' is missing in type '{ src: any; width: any; height: any; scale: number; responsive: false; }' but required in type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | "height" | "width" | "title" | "alt" | "trackInteraction" | "preloader" | "allowFullBleed" | "extraGraphie" | "overrideAriaHidden">'.
27907
+ , {
27908
+ src: imageData.url,
27909
+ width: imageData.width,
27910
+ height: imageData.height,
27911
+ scale: scale,
27912
+ responsive: false,
27913
+ setAssetStatus: setAssetStatus
27914
+ });
27908
27915
  });
27909
27916
  } else {
27910
27917
  image = null;
@@ -44209,7 +44216,7 @@ const init = function (options) {
44209
44216
 
44210
44217
  // This file is processed by a Rollup plugin (replace) to inject the production
44211
44218
  const libName = "@khanacademy/perseus";
44212
- const libVersion = "13.2.0";
44219
+ const libVersion = "15.0.0";
44213
44220
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44214
44221
 
44215
44222
  const apiVersion = {
@@ -44483,6 +44490,8 @@ class HintsRenderer extends React__namespace.Component {
44483
44490
  componentDidMount() {
44484
44491
  this._cacheHintImages();
44485
44492
  }
44493
+
44494
+ // eslint-disable-next-line react/no-unsafe
44486
44495
  UNSAFE_componentWillReceiveProps(nextProps) {
44487
44496
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
44488
44497
  if (nextProps.hintsVisible - this.props.hintsVisible > 1) {
@@ -44717,455 +44726,6 @@ const styles$4 = aphrodite.StyleSheet.create({
44717
44726
  }
44718
44727
  });
44719
44728
 
44720
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
44721
-
44722
- // This represents the shape of any component that uses this ProvideKeypad
44723
- // mixin.
44724
- // NOTE: This is not a real component. It's a collection of methods used to
44725
- // create and manage a Keypad instances.
44726
- // TODO(LP-10789): replace this with a React Context object to pass information
44727
- // between Perseus and the Keypad.
44728
- const ProvideKeypad = {
44729
- getInitialState() {
44730
- const _this = this;
44731
- let keypadElement = null;
44732
- if (_this.props.apiOptions && _this.props.apiOptions.customKeypad && _this.props.apiOptions.nativeKeypadProxy) {
44733
- keypadElement = _this.props.apiOptions.nativeKeypadProxy(
44734
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'. | TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
44735
- () => _this.blur && _this.blur());
44736
- }
44737
- return {
44738
- keypadElement
44739
- };
44740
- },
44741
- componentDidMount() {
44742
- const _this = this;
44743
- const apiOptions = _this.props.apiOptions;
44744
- if (apiOptions && apiOptions.customKeypad && !apiOptions.nativeKeypadProxy) {
44745
- // TODO(charlie): Render this and the wrapped component in the same
44746
- // React tree. We may also want to add this keypad asynchronously
44747
- // or on-demand in the future.
44748
- _this._keypadContainer = document.createElement("div");
44749
- document.body?.appendChild(_this._keypadContainer);
44750
- render( /*#__PURE__*/React__namespace.createElement(mathInput.MobileKeypad, {
44751
- onElementMounted: element => {
44752
- // NOTE(kevinb): The reason why this setState works is
44753
- // b/c we're calling it manually from item-renderer.jsx
44754
- // and we're manually setting the 'this' by using 'call'
44755
- // @ts-expect-error - TS2339 - Property 'setState' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
44756
- _this.setState({
44757
- keypadElement: element
44758
- });
44759
- },
44760
- onDismiss: () => {
44761
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'. | TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
44762
- _this.blur && _this.blur();
44763
- }
44764
- // @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
44765
- ,
44766
- style: _this.props.keypadStyle,
44767
- useV2Keypad: apiOptions.useV2Keypad,
44768
- onAnalyticsEvent: async () => {
44769
- // Intentionally left empty. This component is
44770
- // deprecated and so we don't want/need to instrument
44771
- // it.
44772
- }
44773
- }), _this._keypadContainer);
44774
- }
44775
- },
44776
- componentWillUnmount() {
44777
- const _this = this;
44778
- if (_this._keypadContainer) {
44779
- ReactDOM__default["default"].unmountComponentAtNode(_this._keypadContainer);
44780
- if (_this._keypadContainer.parentNode) {
44781
- // Note ChildNode.remove() isn't available in older Android
44782
- // webviews.
44783
- _this._keypadContainer.parentNode.removeChild(_this._keypadContainer);
44784
- }
44785
- _this._keypadContainer = null;
44786
- }
44787
- },
44788
- keypadElement() {
44789
- const _this = this;
44790
- return _this.state.keypadElement;
44791
- }
44792
- };
44793
-
44794
- const {
44795
- mapObject: mapObject$1
44796
- } = objective_;
44797
- /**
44798
- * @depreciated use ServerItemRenderer
44799
- */
44800
- class ItemRenderer extends React__namespace.Component {
44801
- // @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
44802
-
44803
- static defaultProps = {
44804
- apiOptions: ApiOptions.defaults,
44805
- // defaults are set in `this.update()`
44806
- controlPeripherals: true,
44807
- hintsAreaSelector: "#hintsarea",
44808
- initialHintsVisible: 0,
44809
- workAreaSelector: "#workarea",
44810
- reviewMode: false,
44811
- linterContext: PerseusLinter__namespace.linterContextDefault
44812
- };
44813
- constructor(props) {
44814
- super(props);
44815
- this.state = {
44816
- // NOTE: This value is used in provide-keypad.jsx which contains a
44817
- // "mixin" that this file calls methods on.
44818
- ...ProvideKeypad.getInitialState.call(this),
44819
- hintsVisible: props.initialHintsVisible || 0,
44820
- questionCompleted: false,
44821
- questionHighlightedWidgets: []
44822
- };
44823
- }
44824
- componentDidMount() {
44825
- ProvideKeypad.componentDidMount.call(this);
44826
- if (this.props.controlPeripherals && this.props.apiOptions.setDrawingAreaAvailable) {
44827
- this.props.apiOptions.setDrawingAreaAvailable(true);
44828
- }
44829
- this._currentFocus = null;
44830
- this.update();
44831
- }
44832
- UNSAFE_componentWillReceiveProps(nextProps) {
44833
- this.setState({
44834
- questionHighlightedWidgets: []
44835
- });
44836
- }
44837
- componentDidUpdate() {
44838
- this.update();
44839
- }
44840
- componentWillUnmount() {
44841
- ProvideKeypad.componentWillUnmount.call(this);
44842
- const workArea = document.querySelector(this.props.workAreaSelector);
44843
- if (workArea != null) {
44844
- ReactDOM__namespace.unmountComponentAtNode(workArea);
44845
- }
44846
- const hintsArea = document.querySelector(this.props.hintsAreaSelector);
44847
- if (hintsArea != null) {
44848
- ReactDOM__namespace.unmountComponentAtNode(hintsArea);
44849
- }
44850
- if (this.props.controlPeripherals) {
44851
- const answerArea = this.props.item.answerArea || {};
44852
- if (answerArea.calculator) {
44853
- $__default["default"]("#calculator").hide();
44854
- }
44855
- if (answerArea.periodicTable) {
44856
- $__default["default"](".periodic-table-info-box").hide();
44857
- }
44858
- if (answerArea.zTable) {
44859
- $__default["default"](".z-table-info-box").hide();
44860
- }
44861
- if (answerArea.tTable) {
44862
- $__default["default"](".t-table-info-box").hide();
44863
- }
44864
- if (answerArea.chi2Table) {
44865
- $__default["default"](".chi2-table-info-box").hide();
44866
- }
44867
- }
44868
- }
44869
- keypadElement() {
44870
- return ProvideKeypad.keypadElement.call(this);
44871
- }
44872
- update() {
44873
- const apiOptions = {
44874
- ...ApiOptions.defaults,
44875
- ...this.props.apiOptions,
44876
- onFocusChange: this._handleFocusChange
44877
- };
44878
- const workArea = document.querySelector(this.props.workAreaSelector);
44879
- const hintsArea = document.querySelector(this.props.hintsAreaSelector);
44880
- if (!workArea || !hintsArea) {
44881
- return;
44882
- }
44883
-
44884
- // Since the item renderer works by rendering things into three divs
44885
- // that have completely different places in the DOM, we have to do this
44886
- // strangeness instead of relying on React's normal render() method.
44887
- // TODO(alpert): Figure out how to clean this up somehow
44888
- render( /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
44889
- value: this.props.dependencies
44890
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({
44891
- ref: node => {
44892
- if (!node) {
44893
- return;
44894
- }
44895
- this.questionRenderer = node;
44896
-
44897
- // NOTE(jeremy): Why don't we just pass this into the
44898
- // renderer as a prop?
44899
- const {
44900
- answerableCallback
44901
- } = apiOptions;
44902
- if (answerableCallback) {
44903
- const isAnswerable = this.questionRenderer.emptyWidgets().length === 0;
44904
- answerableCallback(isAnswerable);
44905
- }
44906
- },
44907
- keypadElement: this.keypadElement(),
44908
- problemNum: this.props.problemNum,
44909
- onInteractWithWidget: this.handleInteractWithWidget,
44910
- highlightedWidgets: this.state.questionHighlightedWidgets,
44911
- apiOptions: apiOptions,
44912
- questionCompleted: this.state.questionCompleted,
44913
- reviewMode: this.props.reviewMode,
44914
- savedState: this.props.savedState,
44915
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "question")
44916
- }, this.props.item.question, {
44917
- legacyPerseusLint: this.props.legacyPerseusLint
44918
- }))),
44919
- // @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
44920
- workArea);
44921
- render( /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
44922
- value: this.props.dependencies
44923
- }, /*#__PURE__*/React__namespace.createElement(HintsRenderer, {
44924
- ref: node => this.hintsRenderer = node,
44925
- hints: this.props.item.hints,
44926
- hintsVisible: this.state.hintsVisible,
44927
- apiOptions: apiOptions,
44928
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hints")
44929
- })),
44930
- // @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
44931
- hintsArea);
44932
- const answerArea = this.props.item.answerArea || {};
44933
- if (this.props.controlPeripherals) {
44934
- $__default["default"]("#calculator").toggle(answerArea.calculator || false);
44935
- $__default["default"](".periodic-table-info-box").toggle(answerArea.periodicTable || false);
44936
- $__default["default"](".z-table-info-box").toggle(answerArea.zTable || false);
44937
- $__default["default"](".t-table-info-box").toggle(answerArea.tTable || false);
44938
- $__default["default"](".chi2-table-info-box").toggle(answerArea.chi2Table || false);
44939
- } else {
44940
- if (answerArea.calculator) {
44941
- this.props.onShowCalculator && this.props.onShowCalculator();
44942
- }
44943
- if (answerArea.periodicTable) {
44944
- this.props.onShowPeriodicTable && this.props.onShowPeriodicTable({
44945
- showKey: answerArea.periodicTableWithKey
44946
- });
44947
- }
44948
- if (answerArea.zTable) {
44949
- this.props.onShowZTable && this.props.onShowZTable();
44950
- }
44951
- if (answerArea.tTable) {
44952
- this.props.onShowTTable && this.props.onShowTTable();
44953
- }
44954
- if (answerArea.chi2Table) {
44955
- this.props.onShowChi2Table && this.props.onShowChi2Table();
44956
- }
44957
- }
44958
- }
44959
- _handleFocusChange = (newFocus, oldFocus) => {
44960
- if (newFocus != null) {
44961
- this._setCurrentFocus(newFocus);
44962
- } else {
44963
- this._onRendererBlur(oldFocus);
44964
- }
44965
- };
44966
-
44967
- // Sets the current focus path and element and send an onChangeFocus event
44968
- // back to our parent.
44969
- _setCurrentFocus(newFocus) {
44970
- const keypadElement = this.keypadElement();
44971
- // By the time this happens, newFocus cannot be a prefix of
44972
- // prevFocused, since we must have either been called from
44973
- // an onFocusChange within a renderer, which is only called when
44974
- // this is not a prefix, or between the question and answer areas,
44975
- // which can never prefix each other.
44976
- const prevFocus = this._currentFocus;
44977
- this._currentFocus = newFocus;
44978
-
44979
- // Determine whether the newly focused path represents an input.
44980
- const inputPaths = this.getInputPaths();
44981
- const didFocusInput = this._currentFocus && inputPaths.some(inputPath => {
44982
- return Util.inputPathsEqual(inputPath, this._currentFocus);
44983
- });
44984
- if (this.props.apiOptions.onFocusChange != null) {
44985
- this.props.apiOptions.onFocusChange(this._currentFocus, prevFocus, didFocusInput && keypadElement && keypadElement.getDOMNode());
44986
- }
44987
- if (keypadElement) {
44988
- if (didFocusInput) {
44989
- keypadElement.activate();
44990
- } else {
44991
- keypadElement.dismiss();
44992
- }
44993
- }
44994
- }
44995
- _onRendererBlur(blurPath) {
44996
- const blurringFocusPath = this._currentFocus;
44997
-
44998
- // Failsafe: abort if ID is different, because focus probably happened
44999
- // before blur.
45000
- if (!Util.inputPathsEqual(blurPath, blurringFocusPath)) {
45001
- return;
45002
- }
45003
-
45004
- // Wait until after any new focus events fire this tick before
45005
- // declaring that nothing is focused, since if there were a focus change
45006
- // across Renderers (e.g., from the HintsRenderer to the
45007
- // QuestionRenderer), we could receive the blur before the focus.
45008
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API
45009
- // eslint-disable-next-line no-restricted-syntax
45010
- setTimeout(() => {
45011
- if (Util.inputPathsEqual(this._currentFocus, blurringFocusPath)) {
45012
- this._setCurrentFocus(null);
45013
- }
45014
- });
45015
- }
45016
-
45017
- /**
45018
- * Accepts a question area widgetId, or an answer area widgetId of
45019
- * the form "answer-input-number 1", or the string "answer-area"
45020
- * for the whole answer area (if the answer area is a single widget).
45021
- */
45022
- _setWidgetProps(widgetId, newProps, callback) {
45023
- this.questionRenderer._setWidgetProps(widgetId, newProps, callback);
45024
- }
45025
-
45026
- /**
45027
- * Sets the input value into the widget found at `path`. The `focus`
45028
- * function provided is called when all affected widgets have been
45029
- * re-rendered with the new value.
45030
- */
45031
- setInputValue(path, newValue, focus) {
45032
- return this.questionRenderer.setInputValue(path, newValue, focus);
45033
- }
45034
- focusPath(path) {
45035
- return this.questionRenderer.focusPath(path);
45036
- }
45037
- blurPath(path) {
45038
- return this.questionRenderer.blurPath(path);
45039
- }
45040
- getDOMNodeForPath(path) {
45041
- return this.questionRenderer.getDOMNodeForPath(path);
45042
- }
45043
- getGrammarTypeForPath(path) {
45044
- return this.questionRenderer.getGrammarTypeForPath(path);
45045
- }
45046
- getInputPaths() {
45047
- const questionAreaInputPaths = this.questionRenderer.getInputPaths();
45048
- return questionAreaInputPaths;
45049
- }
45050
-
45051
- // NOTE: this must remain as an arrow function because we pass it Renderer
45052
- handleInteractWithWidget = widgetId => {
45053
- const withRemoved = ___default["default"].difference(this.state.questionHighlightedWidgets, [widgetId]);
45054
- this.setState({
45055
- questionCompleted: false,
45056
- questionHighlightedWidgets: withRemoved
45057
- });
45058
- if (this.props.apiOptions.interactionCallback) {
45059
- this.props.apiOptions.interactionCallback();
45060
- }
45061
- };
45062
- focus() {
45063
- return this.questionRenderer.focus();
45064
- }
45065
- blur() {
45066
- if (this._currentFocus) {
45067
- this.blurPath(this._currentFocus);
45068
- }
45069
- }
45070
- showHint() {
45071
- if (this.state.hintsVisible < this.getNumHints()) {
45072
- this.setState({
45073
- hintsVisible: this.state.hintsVisible + 1
45074
- });
45075
- }
45076
- }
45077
- getNumHints() {
45078
- return this.props.item.hints.length;
45079
- }
45080
-
45081
- /**
45082
- * Grades the item.
45083
- *
45084
- * Returns a KE-style score of {
45085
- * empty: bool,
45086
- * correct: bool,
45087
- * message: string|null,
45088
- * guess: Array
45089
- * }
45090
- */
45091
- scoreInput() {
45092
- const guessAndScore = this.questionRenderer.guessAndScore();
45093
- const guess = guessAndScore[0];
45094
- const score = guessAndScore[1];
45095
-
45096
- // Continue to include an empty guess for the now defunct answer area.
45097
- // TODO(alex): Check whether we rely on the format here for
45098
- // analyzing ProblemLogs. If not, remove this layer.
45099
- const maxCompatGuess = [guess, []];
45100
- const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
45101
- const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
45102
- this.setState({
45103
- questionCompleted: keScore.correct,
45104
- questionHighlightedWidgets: emptyQuestionAreaWidgets
45105
- });
45106
- return keScore;
45107
- }
45108
-
45109
- /**
45110
- * Returns an array of all widget IDs in the order they occur in
45111
- * the question content.
45112
- */
45113
- getWidgetIds() {
45114
- return this.questionRenderer.getWidgetIds();
45115
- }
45116
-
45117
- /**
45118
- * Returns an object mapping from widget ID to KE-style score.
45119
- * The keys of this object are the values of the array returned
45120
- * from `getWidgetIds`.
45121
- */
45122
- scoreWidgets() {
45123
- const qScore = this.questionRenderer.scoreWidgets();
45124
- const qGuess = this.questionRenderer.getUserInputForWidgets();
45125
- const state = this.questionRenderer.getSerializedState();
45126
- // @ts-expect-error - TS2322 - Type 'Partial<Record<string, KEScore>>' is not assignable to type '{ [id: string]: KEScore; }'. | TS2345 - Argument of type '{ [widgetId: string]: PerseusScore; }' is not assignable to parameter of type 'Partial<Record<string, { type: "invalid"; message?: string | null | undefined; suppressAlmostThere?: boolean | null | undefined; }>>'.
45127
- return mapObject$1(qScore, (score, id) => {
45128
- return Util.keScoreFromPerseusScore(score, qGuess[id], state[id]);
45129
- });
45130
- }
45131
-
45132
- /**
45133
- * Get a representation of the current state of the item.
45134
- */
45135
- getSerializedState() {
45136
- return {
45137
- question: this.questionRenderer.getSerializedState(),
45138
- hints: this.hintsRenderer?.getSerializedState()
45139
- };
45140
- }
45141
-
45142
- // TODO(kevinb): make the type for this state better
45143
- restoreSerializedState(state, callback) {
45144
- // We need to wait for both the question renderer and the hints
45145
- // renderer to finish restoring their states.
45146
- let numCallbacks = 2;
45147
- const fireCallback = () => {
45148
- --numCallbacks;
45149
- if (callback && numCallbacks === 0) {
45150
- callback();
45151
- }
45152
- };
45153
- this.questionRenderer.restoreSerializedState(state.question, fireCallback);
45154
- this.hintsRenderer?.restoreSerializedState(state.hints, fireCallback);
45155
- }
45156
- showRationalesForCurrentlySelectedChoices() {
45157
- this.questionRenderer.showRationalesForCurrentlySelectedChoices();
45158
- }
45159
- deselectIncorrectSelectedChoices() {
45160
- this.questionRenderer.deselectIncorrectSelectedChoices();
45161
- }
45162
- render() {
45163
- return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
45164
- value: this.props.dependencies
45165
- }, /*#__PURE__*/React__namespace.createElement("div", null));
45166
- }
45167
- }
45168
-
45169
44729
  /**
45170
44730
  * A React context for signaling when all of the components in a Perseus
45171
44731
  * renderer is fully rendered. This means that all images and math have
@@ -45204,6 +44764,8 @@ class ServerItemRenderer extends React__namespace.Component {
45204
44764
  this._currentFocus = null;
45205
44765
  this._fullyRendered = false;
45206
44766
  }
44767
+
44768
+ // eslint-disable-next-line react/no-unsafe
45207
44769
  UNSAFE_componentWillReceiveProps(nextProps) {
45208
44770
  this.setState({
45209
44771
  questionHighlightedWidgets: []
@@ -47447,7 +47009,6 @@ exports.HintRenderer = HintRenderer;
47447
47009
  exports.HintsRenderer = HintsRenderer;
47448
47010
  exports.InputNumber = InputNumber$1;
47449
47011
  exports.InteractiveGraphWidget = InteractiveGraph$1;
47450
- exports.ItemRenderer = ItemRenderer;
47451
47012
  exports.JiptParagraphs = JiptParagraphs;
47452
47013
  exports.KhanColors = KhanColors;
47453
47014
  exports.KhanMath = KhanMath;