@khanacademy/perseus 19.7.3 → 19.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.js CHANGED
@@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
19
19
  import * as ReactDOM from 'react-dom';
20
20
  import ReactDOM__default from 'react-dom';
21
21
  import { number, point, vector as vector$1, line } from '@khanacademy/kmath';
22
- import { entries, keys } from '@khanacademy/wonder-stuff-core';
22
+ import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
23
23
  import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
24
24
  import SimpleMarkdown from '@khanacademy/simple-markdown';
25
25
  import { pureMarkdownRules } from '@khanacademy/pure-markdown';
@@ -29,7 +29,7 @@ import { Strut } from '@khanacademy/wonder-blocks-layout';
29
29
  import Banner from '@khanacademy/wonder-blocks-banner';
30
30
  import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-blocks-dropdown';
31
31
  import debounce$1 from 'lodash.debounce';
32
- import { useMovablePoint, Line as Line$4, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Line as Line$4, MovablePoint as MovablePoint$6, Coordinates, Mafs } from 'mafs';
33
33
  import createReactClass from 'create-react-class';
34
34
  import Switch from '@khanacademy/wonder-blocks-switch';
35
35
  import { LabelMedium } from '@khanacademy/wonder-blocks-typography';
@@ -1902,7 +1902,7 @@ const KhanAnswerTypes = {
1902
1902
  }
1903
1903
  };
1904
1904
 
1905
- const _excluded$5 = ["scoring"];
1905
+ const _excluded$4 = ["scoring"];
1906
1906
  const ERROR_TITLE = i18n._("Oops!");
1907
1907
  const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
1908
1908
 
@@ -1990,7 +1990,7 @@ class Expression extends React.Component {
1990
1990
  let {
1991
1991
  scoring
1992
1992
  } = _ref,
1993
- rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
1993
+ rubric = _objectWithoutPropertiesLoose(_ref, _excluded$4);
1994
1994
  const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
1995
1995
 
1996
1996
  // "scoring" is a flag that indicates when we are checking answers.
@@ -18909,7 +18909,7 @@ const getRendererPropsForWidgetInfo = (widgetInfo, problemNum) => {
18909
18909
  // It shouldn't matter what we return here, but for consistency
18910
18910
  // we return the untransformed options, as if the widget did
18911
18911
  // not have a transform defined.
18912
- return widgetInfo.options;
18912
+ return widgetInfo;
18913
18913
  }
18914
18914
  let transform;
18915
18915
  if (widgetInfo.static) {
@@ -19389,11 +19389,8 @@ class Renderer extends React.Component {
19389
19389
 
19390
19390
  // The widget needs access to its "rubric" at all times when in review
19391
19391
  // mode (which is really just part of its widget info).
19392
- let reviewModeRubric = null;
19393
19392
  const widgetInfo = this.state.widgetInfo[id];
19394
- if (this.props.reviewMode && widgetInfo) {
19395
- reviewModeRubric = widgetInfo.options;
19396
- }
19393
+ const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
19397
19394
  if (!this._interactionTrackers) {
19398
19395
  this._interactionTrackers = {};
19399
19396
  }
@@ -21393,7 +21390,7 @@ InputWithExamples.defaultProps = {
21393
21390
  className: ""
21394
21391
  };
21395
21392
 
21396
- const _excluded$4 = ["keypadElement", "onFocus", "value"];
21393
+ const _excluded$3 = ["keypadElement", "onFocus", "value"];
21397
21394
  class SimpleKeypadInput extends React.Component {
21398
21395
  constructor(...args) {
21399
21396
  super(...args);
@@ -21432,7 +21429,7 @@ class SimpleKeypadInput extends React.Component {
21432
21429
  onFocus,
21433
21430
  value
21434
21431
  } = _this$props,
21435
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
21432
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
21436
21433
  return (
21437
21434
  /*#__PURE__*/
21438
21435
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -23263,14 +23260,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
23263
23260
  forwardedRef: ref
23264
23261
  })));
23265
23262
 
23266
- const _excluded$3 = ["showContent", "content", "forwardedRef"];
23263
+ const _excluded$2 = ["showContent", "content", "forwardedRef"];
23267
23264
  const ChoiceNoneAbove = function ChoiceNoneAbove(props) {
23268
23265
  const {
23269
23266
  showContent,
23270
23267
  content,
23271
23268
  forwardedRef
23272
23269
  } = props,
23273
- rest = _objectWithoutPropertiesLoose(props, _excluded$3);
23270
+ rest = _objectWithoutPropertiesLoose(props, _excluded$2);
23274
23271
  const choiceProps = _extends({}, rest, {
23275
23272
  content: showContent ? content :
23276
23273
  /*#__PURE__*/
@@ -28174,39 +28171,167 @@ Graph.defaultProps = {
28174
28171
  isMobile: false
28175
28172
  };
28176
28173
 
28177
- const useInteractivePoint = (coords, snaps, range) => useMovablePoint(coords, {
28178
- constrain: coord => constrain(coord, snaps, range),
28179
- color: Color.blue
28180
- });
28174
+ const MOVE_CONTROL_POINT = "move-control-point";
28175
+ function moveControlPoint(objectIndex, pointIndex, destination) {
28176
+ return {
28177
+ type: MOVE_CONTROL_POINT,
28178
+ objectIndex,
28179
+ pointIndex,
28180
+ destination
28181
+ };
28182
+ }
28183
+ const MOVE_SEGMENT = "move-segment";
28184
+ function moveSegment(segmentIndex, delta) {
28185
+ return {
28186
+ type: MOVE_SEGMENT,
28187
+ segmentIndex,
28188
+ delta
28189
+ };
28190
+ }
28181
28191
 
28182
- /**
28183
- * We do not want to call `onGraphChange` until the user
28184
- * has interacted with the graph. See the test,
28185
- * "should reject no interaction"
28186
- */
28187
- const useEffectAfterFirstRender = (fn, deps) => {
28188
- const firstRender = useRef(true);
28189
- useEffect(() => {
28190
- if (firstRender.current) {
28191
- firstRender.current = false;
28192
- return;
28193
- }
28194
- fn();
28195
- // eslint-disable-next-line react-hooks/exhaustive-deps
28196
- }, [fn, ...deps]);
28197
- };
28198
- const snap = (val, step) => {
28199
- const inverse = 1 / step;
28200
- return Math.round(val * inverse) / inverse;
28192
+ const SegmentGraph = props => {
28193
+ const {
28194
+ dispatch
28195
+ } = props;
28196
+ const {
28197
+ segments,
28198
+ snapStep,
28199
+ range
28200
+ } = props.graphState;
28201
+ return /*#__PURE__*/React.createElement(React.Fragment, null, segments.map((segment, i) => /*#__PURE__*/React.createElement(SegmentView, {
28202
+ key: i,
28203
+ segment: segment,
28204
+ snaps: snapStep,
28205
+ range: range,
28206
+ onMoveSegment: delta => {
28207
+ dispatch(moveSegment(i, delta));
28208
+ },
28209
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(i, endpointIndex, destination)),
28210
+ "data-testid": "segment" + i
28211
+ })));
28201
28212
  };
28202
- const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
28203
- const constrain = (coord, snapStep, range) => {
28204
- const [x, y] = coord;
28205
- const [xSnap, ySnap] = snapStep;
28206
- const [[xMin, xMax], [yMin, yMax]] = range;
28207
- return [clamp(snap(x, xSnap), xMin, xMax), clamp(snap(y, ySnap), yMin, yMax)];
28213
+ const SegmentView = props => {
28214
+ const [pt1, pt2] = props.segment;
28215
+ const midpoint = vec.midpoint(pt1, pt2);
28216
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Line$4.Segment, {
28217
+ point1: pt1,
28218
+ point2: pt2
28219
+ }), /*#__PURE__*/React.createElement(MovablePoint$6, {
28220
+ point: midpoint,
28221
+ color: Color.blue,
28222
+ onMove: newPoint => {
28223
+ props.onMoveSegment(vec.sub(newPoint, midpoint));
28224
+ }
28225
+ }), /*#__PURE__*/React.createElement(MovablePoint$6, {
28226
+ point: pt1,
28227
+ color: Color.blue,
28228
+ onMove: newPoint => {
28229
+ props.onMovePoint(0, newPoint);
28230
+ }
28231
+ }), /*#__PURE__*/React.createElement(MovablePoint$6, {
28232
+ point: pt2,
28233
+ color: Color.blue,
28234
+ onMove: newPoint => {
28235
+ props.onMovePoint(1, newPoint);
28236
+ }
28237
+ }));
28208
28238
  };
28209
28239
 
28240
+ const renderLineLabel = (n, [min, max]) => n !== -1 && n !== min && n !== max;
28241
+ const axisOptions = (props, index) => ({
28242
+ axis: props.markings === "graph",
28243
+ lines: props.step[index],
28244
+ subdivisions: props.step[index] / props.gridStep[index],
28245
+ labels: n => renderLineLabel(n, props.range[index]) ? n : ""
28246
+ });
28247
+ const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
28248
+ xAxis: axisOptions(props, 0),
28249
+ yAxis: axisOptions(props, 1)
28250
+ });
28251
+
28252
+ function interactiveGraphReducer(state, action) {
28253
+ switch (action.type) {
28254
+ case "move-control-point":
28255
+ {
28256
+ const newSegments = updateAtIndex(state.segments, action.objectIndex, segment => setAtIndex(segment, action.pointIndex, snap(state, bound$1(state, action.destination))));
28257
+ if (!validSegments(newSegments)) {
28258
+ return state;
28259
+ }
28260
+ return _extends({}, state, {
28261
+ hasBeenInteractedWith: true,
28262
+ segments: newSegments
28263
+ });
28264
+ }
28265
+ case "move-segment":
28266
+ {
28267
+ const oldSegment = state.segments[action.segmentIndex];
28268
+ const maxMoves = oldSegment.map(point => maxMove(state, point));
28269
+ const minMoves = oldSegment.map(point => minMove(state, point));
28270
+ const maxXMove = Math.min(...maxMoves.map(move => move[0]));
28271
+ const maxYMove = Math.min(...maxMoves.map(move => move[1]));
28272
+ const minXMove = Math.max(...minMoves.map(move => move[0]));
28273
+ const minYMove = Math.max(...minMoves.map(move => move[1]));
28274
+ const dx = clamp(action.delta[0], minXMove, maxXMove);
28275
+ const dy = clamp(action.delta[1], minYMove, maxYMove);
28276
+ const newSegment = oldSegment.map(point => snap(state, vector$1.add(point, [dx, dy])));
28277
+ const newSegments = setAtIndex(state.segments, action.segmentIndex, newSegment);
28278
+ return _extends({}, state, {
28279
+ hasBeenInteractedWith: true,
28280
+ segments: newSegments
28281
+ });
28282
+ }
28283
+ default:
28284
+ throw new UnreachableCaseError(action);
28285
+ }
28286
+ }
28287
+ function snap(state, point) {
28288
+ const [requestedX, requestedY] = point;
28289
+ const [snapX, snapY] = state.snapStep;
28290
+ return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
28291
+ }
28292
+
28293
+ // Returns the closest point to the given `point` that is within the graph
28294
+ // bounds given in `state`.
28295
+ function bound$1(state, point) {
28296
+ const [requestedX, requestedY] = point;
28297
+ const [snapX, snapY] = state.snapStep;
28298
+ const [[minX, maxX], [minY, maxY]] = state.range;
28299
+ return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
28300
+ }
28301
+
28302
+ // Returns the vector from the given point to the top-right corner of the graph
28303
+ function maxMove(state, point) {
28304
+ const topRight = bound$1(state, [Infinity, Infinity]);
28305
+ return vec.sub(topRight, point);
28306
+ }
28307
+
28308
+ // Returns the vector from the given point to the bottom-left corner of the
28309
+ // graph
28310
+ function minMove(state, point) {
28311
+ const bottomLeft = bound$1(state, [-Infinity, -Infinity]);
28312
+ return vec.sub(bottomLeft, point);
28313
+ }
28314
+ function clamp(value, min, max) {
28315
+ if (value < min) {
28316
+ return min;
28317
+ }
28318
+ if (value > max) {
28319
+ return max;
28320
+ }
28321
+ return value;
28322
+ }
28323
+ function validSegments(segments) {
28324
+ return segments.every(([start, end]) => !vector$1.equal(start, end));
28325
+ }
28326
+ function updateAtIndex(array, index, update) {
28327
+ return setAtIndex(array, index, update(array[index]));
28328
+ }
28329
+ function setAtIndex(array, index, newValue) {
28330
+ const copy = [...array];
28331
+ copy[index] = newValue;
28332
+ return copy;
28333
+ }
28334
+
28210
28335
  // same as pointsFromNormalized in interactive-graph.tsx
28211
28336
  const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
28212
28337
  // maps over [x, y]
@@ -28227,11 +28352,30 @@ const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.
28227
28352
  return (coord + ranges[i][1]) / extent;
28228
28353
  }));
28229
28354
 
28230
- const getSegmentCoords = props => {
28231
- const coords = props.graph.coords;
28232
- if (coords) {
28233
- return coords;
28355
+ function initializeGraphState(params) {
28356
+ const {
28357
+ graph,
28358
+ step,
28359
+ snapStep,
28360
+ range
28361
+ } = params;
28362
+ switch (graph.type) {
28363
+ case "segment":
28364
+ return {
28365
+ type: "segment",
28366
+ hasBeenInteractedWith: false,
28367
+ range,
28368
+ snapStep,
28369
+ segments: getDefaultSegments({
28370
+ graph,
28371
+ step,
28372
+ range
28373
+ })
28374
+ };
28234
28375
  }
28376
+ throw new Error("Mafs not yet implemented for graph type: " + graph.type);
28377
+ }
28378
+ const getDefaultSegments = props => {
28235
28379
  const ys = n => {
28236
28380
  switch (n) {
28237
28381
  case 2:
@@ -28249,56 +28393,12 @@ const getSegmentCoords = props => {
28249
28393
  }
28250
28394
  };
28251
28395
  return ys(props.graph.numSegments).map(y => {
28252
- let segment = [[-5, y], [5, y]];
28253
- segment = normalizeCoords(segment, props.range);
28254
- segment = normalizePoints(props.range, props.step, segment);
28255
- return segment;
28396
+ let endpoints = [[-5, y], [5, y]];
28397
+ endpoints = normalizeCoords(endpoints, props.range);
28398
+ endpoints = normalizePoints(props.range, props.step, endpoints);
28399
+ return endpoints;
28256
28400
  });
28257
28401
  };
28258
- const updateSegmentsArray = (segments, index, segment) => segments.map((seg, i) => index === i ? segment : seg);
28259
- const SegmentGraph = props => {
28260
- const segments = getSegmentCoords(props);
28261
- const handleChange = (i, segment) => props.onGraphChange(current => _extends({}, current, {
28262
- coords: current.coords ? updateSegmentsArray(current.coords, i, segment) : updateSegmentsArray(segments, i, segment)
28263
- }));
28264
- return /*#__PURE__*/React.createElement(React.Fragment, null, segments.map((segment, i) => /*#__PURE__*/React.createElement(Segment, {
28265
- key: i,
28266
- i: i,
28267
- segment: segment,
28268
- snaps: props.snapStep,
28269
- range: props.range,
28270
- onChange: handleChange,
28271
- "data-testid": "segment" + i
28272
- })));
28273
- };
28274
- const Segment = props => {
28275
- const [start, end] = props.segment;
28276
- const {
28277
- point: pt1,
28278
- element: el1
28279
- } = useInteractivePoint(start, props.snaps, props.range);
28280
- const {
28281
- point: pt2,
28282
- element: el2
28283
- } = useInteractivePoint(end, props.snaps, props.range);
28284
- useEffectAfterFirstRender(() => props.onChange(props.i, [pt1, pt2]), [pt1, pt2]);
28285
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Line$4.Segment, {
28286
- point1: pt1,
28287
- point2: pt2
28288
- }), el1, el2);
28289
- };
28290
-
28291
- const renderLineLabel = (n, [min, max]) => n !== -1 && n !== min && n !== max;
28292
- const axisOptions = (props, index) => ({
28293
- axis: props.markings === "graph",
28294
- lines: props.step[index],
28295
- subdivisions: props.step[index] / props.gridStep[index],
28296
- labels: n => renderLineLabel(n, props.range[index]) ? n : ""
28297
- });
28298
- const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
28299
- xAxis: axisOptions(props, 0),
28300
- yAxis: axisOptions(props, 1)
28301
- });
28302
28402
 
28303
28403
  // Displays a stylized open eye: lint warnings are visible
28304
28404
  const VisibleIcon = () => /*#__PURE__*/React.createElement("svg", {
@@ -28646,7 +28746,7 @@ const styles$h = StyleSheet.create({
28646
28746
  }
28647
28747
  });
28648
28748
 
28649
- const _excluded$2 = ["onFormatChange", "checkValidity", "useArrowKeys"];
28749
+ const _excluded$1 = ["onFormatChange", "checkValidity", "useArrowKeys"];
28650
28750
  const {
28651
28751
  firstNumericalParse,
28652
28752
  captureScratchpadTouchStart
@@ -28793,7 +28893,7 @@ class NumberInput extends React.Component {
28793
28893
  classes = classes + " " + this.props.className;
28794
28894
  }
28795
28895
  const _this$props = this.props,
28796
- restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
28896
+ restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
28797
28897
  return /*#__PURE__*/React.createElement("input", _extends({}, restProps, {
28798
28898
  className: classes,
28799
28899
  type: "text"
@@ -28986,33 +29086,40 @@ const getLegacyGrid = (box, backgroundImage) => {
28986
29086
  return null;
28987
29087
  };
28988
29088
 
28989
- const _excluded$1 = ["graph"];
28990
29089
  const renderGraph = props => {
28991
29090
  const {
28992
- graph
28993
- } = props,
28994
- rest = _objectWithoutPropertiesLoose(props, _excluded$1);
28995
- switch (graph.type) {
29091
+ state,
29092
+ dispatch
29093
+ } = props;
29094
+ switch (state.type) {
28996
29095
  case "segment":
28997
- return /*#__PURE__*/React.createElement(SegmentGraph, _extends({}, rest, {
28998
- graph: graph,
28999
- onGraphChange: props.onGraphChange
29000
- }));
29096
+ return /*#__PURE__*/React.createElement(SegmentGraph, {
29097
+ graphState: state,
29098
+ dispatch: dispatch
29099
+ });
29001
29100
  }
29101
+ throw new Error("Mafs is not yet implemented for graph type: " + state.type);
29002
29102
  };
29103
+ function getGradableGraph(state, initialGraph) {
29104
+ if (!state.hasBeenInteractedWith) {
29105
+ return initialGraph;
29106
+ }
29107
+ switch (initialGraph.type) {
29108
+ case "segment":
29109
+ return _extends({}, initialGraph, {
29110
+ coords: state.segments
29111
+ });
29112
+ }
29113
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29114
+ }
29003
29115
  const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29004
29116
  var _props$backgroundImag, _props$backgroundImag2, _props$backgroundImag3, _props$backgroundImag4;
29005
- // Storing the gradable state in a ref so that it can be updated without
29006
- // causing a re-render.
29007
- const graphRef = React.useRef(props.graph);
29008
- const handleGraphUpdate = callback => {
29009
- graphRef.current = callback(graphRef.current);
29010
- };
29011
- React.useImperativeHandle(ref, () => ({
29012
- getUserInput: () => graphRef.current
29013
- }));
29014
29117
  const [width, height] = props.box;
29015
29118
  const legacyGrid = getLegacyGrid([width, height], props.backgroundImage);
29119
+ const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
29120
+ React.useImperativeHandle(ref, () => ({
29121
+ getUserInput: () => getGradableGraph(state, props.graph)
29122
+ }));
29016
29123
  return /*#__PURE__*/React.createElement(View, {
29017
29124
  style: {
29018
29125
  width: (_props$backgroundImag = (_props$backgroundImag2 = props.backgroundImage) == null ? void 0 : _props$backgroundImag2.width) != null ? _props$backgroundImag : width,
@@ -29035,9 +29142,10 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29035
29142
  zoom: false,
29036
29143
  width: width,
29037
29144
  height: height
29038
- }, !legacyGrid && /*#__PURE__*/React.createElement(Grid, props), renderGraph(_extends({}, props, {
29039
- onGraphChange: handleGraphUpdate
29040
- })))));
29145
+ }, !legacyGrid && /*#__PURE__*/React.createElement(Grid, props), renderGraph({
29146
+ state,
29147
+ dispatch
29148
+ }))));
29041
29149
  });
29042
29150
 
29043
29151
  const {
@@ -32782,6 +32890,8 @@ var LabelImage$1 = {
32782
32890
  isLintable: true
32783
32891
  };
32784
32892
 
32893
+ /* eslint-disable @typescript-eslint/no-unused-vars, react/sort-comp */
32894
+
32785
32895
  // Styling
32786
32896
  const CELL_PADDING = 5;
32787
32897
  const TABLE_STYLE = {
@@ -32821,7 +32931,11 @@ class Tile extends React.Component {
32821
32931
  constructor(...args) {
32822
32932
  super(...args);
32823
32933
  this._flip = () => {
32824
- this.props.onChange(!this.props.value);
32934
+ // On change requires a value, but we don't actually have access
32935
+ // to the coordinates here, so we just pass the inverse value as a workaround.
32936
+ this.props.onChange({
32937
+ value: !this.props.value
32938
+ });
32825
32939
  };
32826
32940
  }
32827
32941
  render() {
@@ -32848,7 +32962,7 @@ class TileGrid extends React.Component {
32848
32962
  return /*#__PURE__*/React.createElement("div", {
32849
32963
  style: TABLE_STYLE,
32850
32964
  className: "no-select"
32851
- }, _.map(this.props.cells, (row, y) => {
32965
+ }, this.props.cells && _.map(this.props.cells, (row, y) => {
32852
32966
  return /*#__PURE__*/React.createElement("div", {
32853
32967
  key: y,
32854
32968
  style: ROW_STYLE
@@ -32859,7 +32973,12 @@ class TileGrid extends React.Component {
32859
32973
  }, /*#__PURE__*/React.createElement(Tile, {
32860
32974
  value: cell,
32861
32975
  size: this.props.size,
32862
- onChange: _.partial(this.props.onChange, y, x)
32976
+ onChange: _.partial(this.props.onChange, {
32977
+ value: {
32978
+ y,
32979
+ x
32980
+ }
32981
+ })
32863
32982
  }));
32864
32983
  }));
32865
32984
  }));
@@ -32868,10 +32987,6 @@ class TileGrid extends React.Component {
32868
32987
 
32869
32988
  // Returns a copy of the tiles, with tiles flipped according to
32870
32989
  // whether or not their y, x position satisfies the predicate
32871
- TileGrid.propTypes = {
32872
- cells: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.bool)).isRequired,
32873
- size: PropTypes.number.isRequired
32874
- };
32875
32990
  const flipTilesPredicate = (oldCells, predicate) => {
32876
32991
  return _.map(oldCells, (row, y) => {
32877
32992
  return _.map(row, (cell, x) => {
@@ -32906,27 +33021,36 @@ class LightsPuzzle extends React.Component {
32906
33021
  return change.apply(this, args);
32907
33022
  };
32908
33023
  this._width = () => {
32909
- if (this.props.cells.length !== 0) {
33024
+ if (this.props.cells && this.props.cells.length !== 0) {
32910
33025
  return this.props.cells[0].length;
32911
33026
  }
32912
33027
  return 0; // default to 0
32913
33028
  };
32914
33029
  this._initNextPatterns = () => {
32915
- this._currPattern = PATTERNS[this.props.flipPattern](0);
32916
- this._nextPattern = PATTERNS[this.props.flipPattern](1);
33030
+ if (this.props.flipPattern) {
33031
+ this._currPattern = PATTERNS[this.props.flipPattern](0);
33032
+ this._nextPattern = PATTERNS[this.props.flipPattern](1);
33033
+ }
32917
33034
  this._patternIndex = 2;
32918
33035
  };
32919
33036
  this._shiftPatterns = () => {
32920
- this._currPattern = this._nextPattern;
32921
- this._nextPattern = PATTERNS[this.props.flipPattern](this._patternIndex);
32922
- this._patternIndex++;
33037
+ if (this.props.flipPattern) {
33038
+ this._currPattern = this._nextPattern;
33039
+ this._nextPattern = PATTERNS[this.props.flipPattern](this._patternIndex);
33040
+ this._patternIndex++;
33041
+ }
32923
33042
  };
32924
- this._flipTile = (tileY, tileX) => {
32925
- const newCells = flipTilesPattern(this.props.cells, tileY, tileX, this._currPattern);
33043
+ this._flipTile = ({
33044
+ value: {
33045
+ y,
33046
+ x
33047
+ }
33048
+ }) => {
33049
+ const newCells = flipTilesPattern(this.props.cells, y, x, this._currPattern);
32926
33050
  this._shiftPatterns();
32927
33051
  this.change({
32928
33052
  cells: newCells,
32929
- moveCount: this.props.moveCount + 1
33053
+ moveCount: (this.props.moveCount || 0) + 1
32930
33054
  });
32931
33055
  };
32932
33056
  this._reset = () => {
@@ -32975,12 +33099,6 @@ class LightsPuzzle extends React.Component {
32975
33099
  }
32976
33100
 
32977
33101
  // grading function
32978
- LightsPuzzle.propTypes = _extends({}, propTypes, {
32979
- cells: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.bool)),
32980
- startCells: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.bool)),
32981
- flipPattern: PropTypes.string.isRequired,
32982
- moveCount: PropTypes.number.isRequired
32983
- });
32984
33102
  LightsPuzzle.defaultProps = {
32985
33103
  cells: [[false, false, false], [false, false, false], [false, false, false]],
32986
33104
  startCells: [[false, false, false], [false, false, false], [false, false, false]],
@@ -35456,7 +35574,7 @@ class Molecule extends React.Component {
35456
35574
  // screen readers?
35457
35575
  let content = /*#__PURE__*/React.createElement("canvas", {
35458
35576
  className: "molecule-canvas",
35459
- id: this.props.id + "-molecule"
35577
+ id: this.props.widgetId + "-molecule"
35460
35578
  // eslint-disable-next-line react/no-string-refs
35461
35579
  ,
35462
35580
  ref: "canvas"
@@ -35473,11 +35591,6 @@ class Molecule extends React.Component {
35473
35591
  }, content);
35474
35592
  }
35475
35593
  }
35476
- Molecule.propTypes = {
35477
- id: PropTypes.string.isRequired,
35478
- rotationAngle: PropTypes.number,
35479
- smiles: PropTypes.string
35480
- };
35481
35594
  class MoleculeWidget extends React.Component {
35482
35595
  constructor(...args) {
35483
35596
  super(...args);
@@ -35506,17 +35619,12 @@ class MoleculeWidget extends React.Component {
35506
35619
  }
35507
35620
  render() {
35508
35621
  return /*#__PURE__*/React.createElement(Molecule, {
35509
- id: this.props.widgetId,
35622
+ widgetId: this.props.widgetId,
35510
35623
  smiles: this.props.smiles,
35511
35624
  rotationAngle: this.props.rotationAngle
35512
35625
  });
35513
35626
  }
35514
35627
  }
35515
- MoleculeWidget.propTypes = {
35516
- rotationAngle: PropTypes.number,
35517
- smiles: PropTypes.string,
35518
- widgetId: PropTypes.string
35519
- };
35520
35628
  MoleculeWidget.defaultProps = {
35521
35629
  rotationAngle: 0
35522
35630
  };
@@ -38850,6 +38958,7 @@ var Passage$1 = {
38850
38958
  isLintable: true
38851
38959
  };
38852
38960
 
38961
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
38853
38962
  class PassageRefTarget extends React.Component {
38854
38963
  constructor(..._args) {
38855
38964
  super(..._args);
@@ -38885,10 +38994,6 @@ class PassageRefTarget extends React.Component {
38885
38994
  });
38886
38995
  }
38887
38996
  }
38888
- PassageRefTarget.propTypes = _extends({}, propTypes, {
38889
- content: PropTypes.string,
38890
- linterContext: linterContextProps
38891
- });
38892
38997
  PassageRefTarget.defaultProps = {
38893
38998
  content: "",
38894
38999
  linterContext: linterContextDefault
@@ -39828,11 +39933,6 @@ class Separator extends React.Component {
39828
39933
  }, this.props.data.bottomText));
39829
39934
  }
39830
39935
  }
39831
- Separator.propTypes = {
39832
- // TODO(colin): figure out and add shape.
39833
- data: PropTypes.any,
39834
- index: PropTypes.number
39835
- };
39836
39936
  class ReactionDiagramWidget extends React.Component {
39837
39937
  constructor(...args) {
39838
39938
  super(...args);
@@ -39874,7 +39974,7 @@ class ReactionDiagramWidget extends React.Component {
39874
39974
  key: id,
39875
39975
  className: "molecule-container"
39876
39976
  }, /*#__PURE__*/React.createElement(Molecule, {
39877
- id: id,
39977
+ widgetId: id,
39878
39978
  rotationAngle: this.props.rotationAngle[i],
39879
39979
  smiles: s
39880
39980
  }), i === this.props.smiles.length - 1 ? null : /*#__PURE__*/React.createElement(Separator, {
@@ -39885,16 +39985,6 @@ class ReactionDiagramWidget extends React.Component {
39885
39985
  );
39886
39986
  }
39887
39987
  }
39888
- ReactionDiagramWidget.propTypes = {
39889
- // TODO(colin): at the moment, these must be arrays of two elements;
39890
- // we're limited to a single reaction step. At some point, add support
39891
- // for more steps in the reaction.
39892
- rotationAngle: PropTypes.arrayOf(PropTypes.number),
39893
- // eslint-disable-next-line react/forbid-prop-types
39894
- separators: PropTypes.arrayOf(PropTypes.object),
39895
- smiles: PropTypes.arrayOf(PropTypes.string),
39896
- widgetId: PropTypes.string
39897
- };
39898
39988
  ReactionDiagramWidget.defaultProps = {
39899
39989
  smiles: [],
39900
39990
  rotationAngle: [],
@@ -42128,7 +42218,7 @@ const init = function init(options) {
42128
42218
 
42129
42219
  // This file is processed by a Rollup plugin (replace) to inject the production
42130
42220
  const libName = "@khanacademy/perseus";
42131
- const libVersion = "19.7.3";
42221
+ const libVersion = "19.9.0";
42132
42222
  addLibraryVersionToPerseusDebug(libName, libVersion);
42133
42223
 
42134
42224
  const apiVersion = {
@@ -42690,17 +42780,13 @@ class ServerItemRenderer extends React.Component {
42690
42780
  this.blurTimeoutID = void 0;
42691
42781
  this._handleFocusChange = (newFocus, oldFocus) => {
42692
42782
  if (newFocus != null) {
42693
- var _this$props$apiOption;
42694
42783
  this._setCurrentFocus(newFocus);
42695
-
42696
- // Call the interactionCallback, if it exists, with the current user input data
42697
- (_this$props$apiOption = this.props.apiOptions) == null || _this$props$apiOption.interactionCallback == null || _this$props$apiOption.interactionCallback(this.questionRenderer.getUserInputForWidgets());
42698
42784
  } else {
42699
42785
  this._onRendererBlur(oldFocus);
42700
42786
  }
42701
42787
  };
42702
42788
  this.handleInteractWithWidget = widgetId => {
42703
- var _this$props$apiOption2;
42789
+ var _this$props$apiOption;
42704
42790
  const withRemoved = _.difference(this.state.questionHighlightedWidgets, [widgetId]);
42705
42791
  this.setState({
42706
42792
  questionCompleted: false,
@@ -42708,7 +42794,7 @@ class ServerItemRenderer extends React.Component {
42708
42794
  });
42709
42795
 
42710
42796
  // Call the interactionCallback, if it exists, with the current user input data
42711
- (_this$props$apiOption2 = this.props.apiOptions) == null || _this$props$apiOption2.interactionCallback == null || _this$props$apiOption2.interactionCallback(this.questionRenderer.getUserInputForWidgets());
42797
+ (_this$props$apiOption = this.props.apiOptions) == null || _this$props$apiOption.interactionCallback == null || _this$props$apiOption.interactionCallback(this.questionRenderer.getUserInputForWidgets());
42712
42798
  };
42713
42799
  // This must be pre-bound otherwise SvgImage's shouldComponentUpdate
42714
42800
  // won't behave correctly and we'll get an infinite loop.
@@ -43266,8 +43352,6 @@ const ItemExtras = [
43266
43352
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
43267
43353
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
43268
43354
 
43269
- // TODO(FEI-3983): Create proper types for these.
43270
-
43271
43355
  /**
43272
43356
  * Traverses a {content, widgets, images} renderer props object,
43273
43357
  * such as `itemData.question`