@khanacademy/perseus 47.0.0 → 48.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.
package/dist/index.js CHANGED
@@ -2412,9 +2412,6 @@ function scoreExpression(userInput, rubric, strings, locale) {
2412
2412
  // We matched a graded answer form, so we can now tell the user
2413
2413
  // whether their input was correct or incorrect, and hand out
2414
2414
  // points accordingly
2415
- // TODO(eater): Seems silly to translate result to this
2416
- // invalid/points thing and immediately translate it back in
2417
- // ItemRenderer.scoreInput()
2418
2415
  return {
2419
2416
  type: "points",
2420
2417
  earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
@@ -3363,7 +3360,7 @@ const supportsPassiveEvents = () => {
3363
3360
  window.addEventListener("testPassive", null, opts);
3364
3361
  // @ts-expect-error - TS2769 - No overload matches this call.
3365
3362
  window.removeEventListener("testPassive", null, opts);
3366
- } catch (e) {
3363
+ } catch {
3367
3364
  // Intentionally left empty!
3368
3365
  }
3369
3366
  return supportsPassive;
@@ -3604,7 +3601,9 @@ function changeViewportTag(contentString, callback) {
3604
3601
  // finished resetting.
3605
3602
  // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
3606
3603
  // eslint-disable-next-line no-restricted-syntax
3607
- callback && setTimeout(callback, 0);
3604
+ if (callback != null) {
3605
+ setTimeout(callback, 0);
3606
+ }
3608
3607
  }, 0);
3609
3608
  }
3610
3609
 
@@ -16964,7 +16963,7 @@ class ImageLoader extends React__namespace.Component {
16964
16963
  onKeyUp = e => {
16965
16964
  // 13 is enter key, 32 is space key
16966
16965
  if (e.keyCode === 13 || e.keyCode === 32) {
16967
- imgProps.onClick && imgProps.onClick(e);
16966
+ imgProps.onClick?.(e);
16968
16967
  }
16969
16968
  };
16970
16969
  onKeyDown = e => {
@@ -17033,7 +17032,7 @@ const doJSONP = function (url, options) {
17033
17032
 
17034
17033
  // A cleanup function to run when we're done.
17035
17034
  function cleanup() {
17036
- document.head && document.head.removeChild(script);
17035
+ document.head?.removeChild(script);
17037
17036
  delete window[options.callbackName];
17038
17037
  }
17039
17038
 
@@ -17057,7 +17056,7 @@ const doJSONP = function (url, options) {
17057
17056
  });
17058
17057
 
17059
17058
  // Insert the script to start the download.
17060
- document.head && document.head.appendChild(script);
17059
+ document.head?.appendChild(script);
17061
17060
  };
17062
17061
 
17063
17062
  // For offline exercises in the mobile app, we download the graphie data
@@ -17396,7 +17395,7 @@ class SvgImage extends React__namespace.Component {
17396
17395
  $image.width() < this.props.width || this.props.zoomToFullSizeOnMobile) {
17397
17396
  ZoomService.handleZoomClick(e, this.props.zoomToFullSizeOnMobile);
17398
17397
  }
17399
- this.props.trackInteraction && this.props.trackInteraction();
17398
+ this.props.trackInteraction?.();
17400
17399
  };
17401
17400
  handleUpdate = status => {
17402
17401
  this.props.onUpdate();
@@ -19803,8 +19802,8 @@ class WidgetContainer extends React__namespace.Component {
19803
19802
  };
19804
19803
 
19805
19804
  // Note: if you add more props here, please consider whether or not
19806
- // it should be auto-serialized (e.g. used in scoreInput()). See
19807
- // widget-jsonify-deprecated.jsx and widget-prop-denylist.jsx
19805
+ // it should be auto-serialized.
19806
+ // See widget-jsonify-deprecated.jsx and widget-prop-denylist.jsx
19808
19807
 
19809
19808
  // We default to an empty object for style instead of null
19810
19809
  // because of a strange bug where the static styles aren't applied
@@ -21100,15 +21099,6 @@ class Renderer extends React__namespace.Component {
21100
21099
  return combinedScore;
21101
21100
  }
21102
21101
 
21103
- /**
21104
- * @deprecated use scorePerseusItem
21105
- */
21106
- guessAndScore = () => {
21107
- const totalGuess = this.getUserInput();
21108
- const totalScore = this.score();
21109
- return [totalGuess, totalScore];
21110
- };
21111
-
21112
21102
  // TranslationLinter callback
21113
21103
  handletranslationLintErrors = lintErrors => {
21114
21104
  if (!this._isMounted) {
@@ -21786,11 +21776,11 @@ class SimpleKeypadInput extends React__namespace.Component {
21786
21776
  keypadType: mathInput.KeypadType.FRACTION
21787
21777
  }, () => {
21788
21778
  if (_this._isMounted) {
21789
- onFocus && onFocus();
21779
+ onFocus?.();
21790
21780
  }
21791
21781
  });
21792
21782
  } else {
21793
- onFocus && onFocus();
21783
+ onFocus?.();
21794
21784
  }
21795
21785
  },
21796
21786
  value: value == null ? "" : "" + value
@@ -22029,9 +22019,6 @@ function scoreInputNumber(userInput, rubric, strings) {
22029
22019
  // If `currentValue` is not TeX, this should be a no-op.
22030
22020
  const currentValue = ParseTex$1(userInput.currentValue);
22031
22021
  const result = val(currentValue);
22032
-
22033
- // TODO(eater): Seems silly to translate result to this invalid/points
22034
- // thing and immediately translate it back in ItemRenderer.scoreInput()
22035
22022
  if (result.empty) {
22036
22023
  return {
22037
22024
  type: "invalid",
@@ -22395,10 +22382,6 @@ function scoreNumericInput(userInput, rubric, strings) {
22395
22382
  message: matchedAnswer?.message ?? null,
22396
22383
  guess: localValue
22397
22384
  };
22398
-
22399
- // TODO(eater): Seems silly to translate result to this
22400
- // invalid/points thing and immediately translate it
22401
- // back in ItemRenderer.scoreInput()
22402
22385
  if (result.empty) {
22403
22386
  return {
22404
22387
  type: "invalid",
@@ -25255,7 +25238,7 @@ const scrollElementToOffset = function (elementOrWindow, offset) {
25255
25238
  // TODO(jeff, WEB-1378): Use Wonder Blocks Timing API
25256
25239
  // eslint-disable-next-line no-restricted-syntax
25257
25240
  clearInterval(scrollInterval);
25258
- callback && callback();
25241
+ callback?.();
25259
25242
  }
25260
25243
  }, tickLength);
25261
25244
  };
@@ -27317,7 +27300,7 @@ class CSProgram extends React__namespace.Component {
27317
27300
  let data = {};
27318
27301
  try {
27319
27302
  data = JSON.parse(e.originalEvent.data);
27320
- } catch (err) {
27303
+ } catch {
27321
27304
  return;
27322
27305
  }
27323
27306
  if (___default["default"].isUndefined(data.testsPassed)) {
@@ -27616,7 +27599,7 @@ class Dropdown extends React__namespace.Component {
27616
27599
  key: "placeholder",
27617
27600
  value: "0",
27618
27601
  disabled: true,
27619
- label: this.props.placeholder
27602
+ label: this.props.placeholder || " "
27620
27603
  }), ...this.props.choices.map((choice, i) => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
27621
27604
  key: String(i + 1),
27622
27605
  value: String(i + 1),
@@ -27637,15 +27620,14 @@ class Dropdown extends React__namespace.Component {
27637
27620
  }
27638
27621
  }, this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
27639
27622
  tag: "label",
27640
- id: ids.get("dropdown-label")
27623
+ htmlFor: ids.get("dropdown")
27641
27624
  }, this.props.visibleLabel), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
27642
27625
  id: ids.get("dropdown"),
27643
27626
  placeholder: "",
27644
27627
  onChange: value => this._handleChange(parseInt(value)),
27645
27628
  selectedValue: String(this.props.selected),
27646
27629
  disabled: this.props.apiOptions.readOnly,
27647
- "aria-label": this.props.ariaLabel || this.context.strings.selectAnAnswer,
27648
- "aria-labelledby": ids.get("dropdown-label")
27630
+ "aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
27649
27631
  // This is currently necessary for SRs to read the labels properly.
27650
27632
  // However, WB is working on a change to add the "combobox" role to
27651
27633
  // all dropdowns.
@@ -28345,7 +28327,6 @@ class GradedGroupSet extends React__namespace.Component {
28345
28327
  currentGroup: 0
28346
28328
  };
28347
28329
  shouldComponentUpdate(nextProps, nextState) {
28348
- nextProps.gradedGroups;
28349
28330
  return nextProps !== this.props || nextState !== this.state;
28350
28331
  }
28351
28332
  change = (() => {
@@ -29869,7 +29850,7 @@ class Iframe extends React__namespace.Component {
29869
29850
  let data = {};
29870
29851
  try {
29871
29852
  data = JSON.parse(e.originalEvent.data);
29872
- } catch (err) {
29853
+ } catch {
29873
29854
  return;
29874
29855
  }
29875
29856
  if (___default["default"].isUndefined(data.testsPassed)) {
@@ -30964,10 +30945,10 @@ class Graph extends React__namespace.Component {
30964
30945
  this.props.onMouseDown(coord);
30965
30946
  }, this) : undefined;
30966
30947
  const onMouseOver = $instructionsWrapper ? function () {
30967
- $instructionsWrapper && $instructionsWrapper.css("opacity", invisible);
30948
+ $instructionsWrapper?.css("opacity", invisible);
30968
30949
  } : undefined;
30969
30950
  const onMouseOut = $instructionsWrapper ? function () {
30970
- $instructionsWrapper && $instructionsWrapper.css("opacity", visible);
30951
+ $instructionsWrapper?.css("opacity", visible);
30971
30952
  } : undefined;
30972
30953
  /* eslint-enable indent */
30973
30954
 
@@ -31680,6 +31661,7 @@ function replaceOutsideTeX(mathString) {
31680
31661
  const parsed = mathOnlyParser(mathString);
31681
31662
  let result = "";
31682
31663
  for (const piece of parsed) {
31664
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
31683
31665
  piece.type === "math" ? result += "$" + piece.content + "$" : piece.type === "specialCharacter" ? result += escapeIfUnescaped(piece.content) : result += piece.content;
31684
31666
  }
31685
31667
  return `\\text{${result}}`;
@@ -32532,11 +32514,39 @@ const LegacyGrid = _ref => {
32532
32514
  return null;
32533
32515
  };
32534
32516
 
32517
+ /**
32518
+ * The Perseus "data schema" file.
32519
+ *
32520
+ * This file, and the types in it, represents the "data schema" that Perseus
32521
+ * uses. The @khanacademy/perseus-editor package edits and produces objects
32522
+ * that conform to the types in this file. Similarly, the top-level renderers
32523
+ * in @khanacademy/perseus, consume objects that conform to these types.
32524
+ *
32525
+ * WARNING: This file should not import any types from elsewhere so that it is
32526
+ * easy to reason about changes that alter the Perseus schema. This helps
32527
+ * ensure that it is not changed accidentally when upgrading a dependant
32528
+ * package or other part of Perseus code. Note that TypeScript does type
32529
+ * checking via something called "structural typing". This means that as long
32530
+ * as the shape of a type matches, the name it goes by doesn't matter. As a
32531
+ * result, a `Coord` type that looks like this `[x: number, y: number]` is
32532
+ * _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
32533
+ * number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
32534
+ * number, y: number]` is compatible with `[min: number, max: number]`. The
32535
+ * labels are for humans, not TypeScript. :)
32536
+ *
32537
+ * If you make changes to types in this file, be very sure that:
32538
+ *
32539
+ * a) the changes are backwards compatible. If they are not, old data from
32540
+ * previous versions of the "schema" could become unrenderable, or worse,
32541
+ * introduce hard-to-diagnose bugs.
32542
+ * b) the parsing code (`util/parse-perseus-json/`) is updated to handle
32543
+ * the new format _as well as_ the old format.
32544
+ */
32545
+
32535
32546
  // TODO(FEI-4010): Remove `Perseus` prefix for all types here
32536
- // TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
32537
32547
 
32538
- // Range is replaced within this file with Interval, but it is used elsewhere
32539
- // and exported from the package, so we need to keep it around.
32548
+ // Same name as Mafs
32549
+
32540
32550
  /**
32541
32551
  * Our core set of Perseus widgets.
32542
32552
  *
@@ -32580,6 +32590,7 @@ const LegacyGrid = _ref => {
32580
32590
  *
32581
32591
  * @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
32582
32592
  */
32593
+
32583
32594
  /**
32584
32595
  * A map of widget IDs to widget options. This is most often used as the type
32585
32596
  * for a set of widgets defined in a `PerseusItem` but can also be useful to
@@ -32592,6 +32603,7 @@ const LegacyGrid = _ref => {
32592
32603
  * @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
32593
32604
  * by augmenting the PerseusWidgetTypes with new widget types!
32594
32605
  */
32606
+
32595
32607
  /**
32596
32608
  * A "PerseusItem" is a classic Perseus item. It is rendered by the
32597
32609
  * `ServerItemRenderer` and the layout is pre-set.
@@ -32599,10 +32611,12 @@ const LegacyGrid = _ref => {
32599
32611
  * To render more complex Perseus items, see the `Item` type in the multi item
32600
32612
  * area.
32601
32613
  */
32614
+
32602
32615
  /**
32603
32616
  * A "PerseusArticle" is an item that is meant to be rendered as an article.
32604
32617
  * This item is never scored and is rendered by the `ArticleRenderer`.
32605
32618
  */
32619
+
32606
32620
  const ItemExtras = [
32607
32621
  // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
32608
32622
  "calculator",
@@ -34619,42 +34633,64 @@ function renderPointGraph(state, dispatch) {
34619
34633
  };
34620
34634
  }
34621
34635
  function PointGraph(props) {
34622
- const numPoints = props.graphState.numPoints;
34636
+ const {
34637
+ numPoints
34638
+ } = props.graphState;
34639
+ const graphConfig = useGraphConfig();
34640
+ const pointsRef = React__namespace.useRef([]);
34641
+
34642
+ // Dimensions to build the graph overlay for Unlimited Point.
34643
+ const {
34644
+ range: [x, y]
34645
+ } = graphConfig;
34646
+ const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
34647
+
34648
+ // This useEffect is to handle the focus snapping for Unlimited Point.
34649
+ React__namespace.useEffect(() => {
34650
+ const focusedIndex = props.graphState.focusedPointIndex;
34651
+ if (focusedIndex != null) {
34652
+ pointsRef.current[focusedIndex]?.focus();
34653
+ }
34654
+ }, [props.graphState.focusedPointIndex, pointsRef]);
34655
+ const statefulProps = {
34656
+ ...props,
34657
+ graphConfig,
34658
+ pointsRef,
34659
+ top,
34660
+ left
34661
+ };
34623
34662
  if (numPoints === "unlimited") {
34624
- return UnlimitedPointGraph(props);
34663
+ return UnlimitedPointGraph(statefulProps);
34625
34664
  }
34626
- return LimitedPointGraph(props);
34665
+ return LimitedPointGraph(statefulProps);
34627
34666
  }
34628
- function LimitedPointGraph(props) {
34667
+ function LimitedPointGraph(statefulProps) {
34629
34668
  const {
34630
34669
  dispatch
34631
- } = props;
34632
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
34670
+ } = statefulProps;
34671
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, statefulProps.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
34633
34672
  key: i,
34634
34673
  point: point,
34635
34674
  sequenceNumber: i + 1,
34636
34675
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
34637
34676
  })));
34638
34677
  }
34639
- function UnlimitedPointGraph(props) {
34678
+ function UnlimitedPointGraph(statefulProps) {
34640
34679
  const {
34641
- dispatch
34642
- } = props;
34643
- const graphConfig = useGraphConfig();
34680
+ dispatch,
34681
+ graphConfig,
34682
+ pointsRef,
34683
+ top,
34684
+ left
34685
+ } = statefulProps;
34686
+ const {
34687
+ coords
34688
+ } = statefulProps.graphState;
34644
34689
  const {
34645
- range: [[minX, maxX], [minY, maxY]]
34690
+ graphDimensionsInPixels
34646
34691
  } = graphConfig;
34647
- const width = maxX - minX;
34648
- const height = maxY - minY;
34649
- const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
34650
- const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
34651
- const itemsRef = React__namespace.useRef([]);
34652
- React__namespace.useEffect(() => {
34653
- const focusedIndex = props.graphState.focusedPointIndex;
34654
- if (focusedIndex != null) {
34655
- itemsRef.current[focusedIndex]?.focus();
34656
- }
34657
- }, [props.graphState.focusedPointIndex, itemsRef]);
34692
+ const widthPx = graphDimensionsInPixels[0];
34693
+ const heightPx = graphDimensionsInPixels[1];
34658
34694
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
34659
34695
  style: {
34660
34696
  fill: "rgba(0,0,0,0)",
@@ -34671,13 +34707,13 @@ function UnlimitedPointGraph(props) {
34671
34707
  const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
34672
34708
  dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
34673
34709
  }
34674
- }), props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
34710
+ }), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
34675
34711
  key: i,
34676
34712
  point: point,
34677
34713
  sequenceNumber: i + 1,
34678
34714
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
34679
34715
  ref: ref => {
34680
- itemsRef.current[i] = ref;
34716
+ pointsRef.current[i] = ref;
34681
34717
  },
34682
34718
  onFocus: () => {
34683
34719
  dispatch(actions.pointGraph.focusPoint(i));
@@ -34728,36 +34764,42 @@ function renderPolygonGraph(state, dispatch) {
34728
34764
  interactiveElementsDescription: null
34729
34765
  };
34730
34766
  }
34731
- const LimitedPolygonGraph = props => {
34732
- const [hovered, setHovered] = React__namespace.useState(false);
34733
- // This is more so required for the re-rendering that occurs when state
34734
- // updates; specifically with regard to line weighting and polygon focus.
34735
- const [focusVisible, setFocusVisible] = React__namespace.useState(false);
34767
+ const PolygonGraph = props => {
34736
34768
  const {
34737
34769
  dispatch
34738
34770
  } = props;
34739
34771
  const {
34772
+ numSides,
34740
34773
  coords,
34741
- showAngles,
34742
- showSides,
34743
- range,
34744
34774
  snapStep,
34745
34775
  snapTo = "grid"
34746
34776
  } = props.graphState;
34777
+ const graphConfig = useGraphConfig();
34778
+
34779
+ // Ref to implement the dragging behavior on a Limited/Closed Polygon.
34780
+ const polygonRef = React__namespace.useRef(null);
34781
+ // Ref to manage dynamic focus for the points in Unlimited Polygon
34782
+ const pointsRef = React__namespace.useRef([]);
34783
+ // Ref to manage the last move time for a Limited Polygon.
34784
+ const lastMoveTimeRef = React__namespace.useRef(0);
34785
+
34786
+ // Dimensions to build the graph overlay for Unlimited Polgyon.
34747
34787
  const {
34748
- disableKeyboardInteraction
34749
- } = useGraphConfig();
34788
+ range: [x, y]
34789
+ } = graphConfig;
34790
+ const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
34750
34791
 
34751
34792
  // TODO(benchristel): can the default set of points be removed here? I don't
34752
34793
  // think coords can be null.
34753
34794
  const points = coords ?? [[0, 0]];
34754
- const ref = React__namespace.useRef(null);
34795
+
34796
+ // Logic to build the dragging experience. Primarily used by Limited Polygon.
34755
34797
  const dragReferencePoint = points[0];
34756
34798
  const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
34757
34799
  const {
34758
34800
  dragging
34759
34801
  } = useDraggable({
34760
- gestureTarget: ref,
34802
+ gestureTarget: polygonRef,
34761
34803
  point: dragReferencePoint,
34762
34804
  onMove: newPoint => {
34763
34805
  const delta = mafs.vec.sub(newPoint, dragReferencePoint);
@@ -34765,7 +34807,61 @@ const LimitedPolygonGraph = props => {
34765
34807
  },
34766
34808
  constrainKeyboardMovement: constrain
34767
34809
  });
34768
- const lastMoveTime = React__namespace.useRef(0);
34810
+
34811
+ // Hover/focus state for Limited Polgyon effects.
34812
+ const [hovered, setHovered] = React__namespace.useState(false);
34813
+ // This is more so required for the re-rendering that occurs when state
34814
+ // updates; specifically with regard to line weighting and polygon focus.
34815
+ const [focusVisible, setFocusVisible] = React__namespace.useState(false);
34816
+
34817
+ // This useEffect is to handle the focus snapping for Unlimited Polygon.
34818
+ React__namespace.useEffect(() => {
34819
+ const focusedIndex = props.graphState.focusedPointIndex;
34820
+ if (focusedIndex != null) {
34821
+ pointsRef.current[focusedIndex]?.focus();
34822
+ }
34823
+ }, [props.graphState.focusedPointIndex, pointsRef]);
34824
+ const statefulProps = {
34825
+ ...props,
34826
+ graphConfig,
34827
+ polygonRef,
34828
+ pointsRef,
34829
+ lastMoveTimeRef,
34830
+ left,
34831
+ top,
34832
+ dragging,
34833
+ points,
34834
+ constrain,
34835
+ hovered,
34836
+ setHovered,
34837
+ focusVisible,
34838
+ setFocusVisible
34839
+ };
34840
+ return numSides === "unlimited" ? UnlimitedPolygonGraph(statefulProps) : LimitedPolygonGraph(statefulProps);
34841
+ };
34842
+ const LimitedPolygonGraph = statefulProps => {
34843
+ const {
34844
+ dispatch,
34845
+ hovered,
34846
+ setHovered,
34847
+ focusVisible,
34848
+ setFocusVisible,
34849
+ graphConfig,
34850
+ polygonRef,
34851
+ lastMoveTimeRef,
34852
+ dragging,
34853
+ points,
34854
+ constrain
34855
+ } = statefulProps;
34856
+ const {
34857
+ showAngles,
34858
+ showSides,
34859
+ range,
34860
+ snapTo = "grid"
34861
+ } = statefulProps.graphState;
34862
+ const {
34863
+ disableKeyboardInteraction
34864
+ } = graphConfig;
34769
34865
  const lines = getLines(points);
34770
34866
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
34771
34867
  points: [...points],
@@ -34804,7 +34900,7 @@ const LimitedPolygonGraph = props => {
34804
34900
  points: [...points],
34805
34901
  color: "transparent",
34806
34902
  svgPolygonProps: {
34807
- ref,
34903
+ ref: polygonRef,
34808
34904
  tabIndex: disableKeyboardInteraction ? -1 : 0,
34809
34905
  strokeWidth: TARGET_SIZE,
34810
34906
  style: {
@@ -34816,13 +34912,13 @@ const LimitedPolygonGraph = props => {
34816
34912
  // Required to remove line weighting when user clicks away
34817
34913
  // from the focused polygon
34818
34914
  onKeyDownCapture: () => {
34819
- setFocusVisible(hasFocusVisible(ref.current));
34915
+ setFocusVisible(hasFocusVisible(polygonRef.current));
34820
34916
  },
34821
34917
  // Required for lines to darken on focus
34822
- onFocus: () => setFocusVisible(hasFocusVisible(ref.current)),
34918
+ onFocus: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
34823
34919
  // Required for line weighting to update on blur. Without this,
34824
34920
  // the user has to hover over the shape for it to update
34825
- onBlur: () => setFocusVisible(hasFocusVisible(ref.current)),
34921
+ onBlur: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
34826
34922
  className: "movable-polygon"
34827
34923
  }
34828
34924
  }), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
@@ -34834,94 +34930,86 @@ const LimitedPolygonGraph = props => {
34834
34930
  const now = Date.now();
34835
34931
  const targetFPS = 40;
34836
34932
  const moveThresholdTime = 1000 / targetFPS;
34837
- if (now - lastMoveTime.current > moveThresholdTime) {
34933
+ if (now - lastMoveTimeRef.current > moveThresholdTime) {
34838
34934
  dispatch(actions.polygon.movePoint(i, destination));
34839
- lastMoveTime.current = now;
34935
+ lastMoveTimeRef.current = now;
34840
34936
  }
34841
34937
  }
34842
34938
  })));
34843
34939
  };
34844
- const UnlimitedPolygonGraph = props => {
34940
+ const UnlimitedPolygonGraph = statefulProps => {
34845
34941
  const {
34846
- dispatch
34847
- } = props;
34942
+ dispatch,
34943
+ graphConfig,
34944
+ left,
34945
+ top,
34946
+ pointsRef,
34947
+ points
34948
+ } = statefulProps;
34848
34949
  const {
34849
34950
  coords,
34850
34951
  closedPolygon
34851
- } = props.graphState;
34852
- const graphConfig = useGraphConfig();
34853
- const {
34854
- range: [x, y],
34855
- graphDimensionsInPixels
34856
- } = graphConfig;
34857
- const widthPx = graphDimensionsInPixels[0];
34858
- const heightPx = graphDimensionsInPixels[1];
34859
- const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
34860
- const pointRef = React__namespace.useRef([]);
34952
+ } = statefulProps.graphState;
34861
34953
 
34862
- // TODO(benchristel): can the default set of points be removed here? I don't
34863
- // think coords can be null.
34864
- const points = coords ?? [[0, 0]];
34865
- React__namespace.useEffect(() => {
34866
- const focusedIndex = props.graphState.focusedPointIndex;
34867
- if (focusedIndex != null) {
34868
- pointRef.current[focusedIndex]?.focus();
34869
- }
34870
- }, [props.graphState.focusedPointIndex, pointRef]);
34954
+ // If the polygon is closed, return a LimitedPolygon component.
34871
34955
  if (closedPolygon) {
34872
34956
  const closedPolygonProps = {
34873
- ...props,
34957
+ ...statefulProps,
34874
34958
  numSides: coords.length
34875
34959
  };
34876
34960
  return /*#__PURE__*/React__namespace.createElement(LimitedPolygonGraph, closedPolygonProps);
34877
- } else {
34878
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
34961
+ }
34962
+ const {
34963
+ graphDimensionsInPixels
34964
+ } = graphConfig;
34965
+ const widthPx = graphDimensionsInPixels[0];
34966
+ const heightPx = graphDimensionsInPixels[1];
34967
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
34968
+ style: {
34969
+ fill: "rgba(0,0,0,0)",
34970
+ cursor: "crosshair"
34971
+ },
34972
+ width: widthPx,
34973
+ height: heightPx,
34974
+ x: left,
34975
+ y: top,
34976
+ onClick: event => {
34977
+ const elementRect = event.currentTarget.getBoundingClientRect();
34978
+ const x = event.clientX - elementRect.x;
34979
+ const y = event.clientY - elementRect.y;
34980
+ const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
34981
+ dispatch(actions.polygon.addPoint(graphCoordinates[0]));
34982
+ }
34983
+ }), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
34984
+ points: [...points],
34985
+ color: "var(--movable-line-stroke-color)",
34986
+ svgPolylineProps: {
34987
+ strokeWidth: "var(--movable-line-stroke-weight)",
34879
34988
  style: {
34880
- fill: "rgba(0,0,0,0)",
34881
- cursor: "crosshair"
34882
- },
34883
- width: widthPx,
34884
- height: heightPx,
34885
- x: left,
34886
- y: top,
34887
- onClick: event => {
34888
- const elementRect = event.currentTarget.getBoundingClientRect();
34889
- const x = event.clientX - elementRect.x;
34890
- const y = event.clientY - elementRect.y;
34891
- const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
34892
- dispatch(actions.polygon.addPoint(graphCoordinates[0]));
34893
- }
34894
- }), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
34895
- points: [...points],
34896
- color: "var(--movable-line-stroke-color)",
34897
- svgPolylineProps: {
34898
- strokeWidth: "var(--movable-line-stroke-weight)",
34899
- style: {
34900
- fill: "transparent"
34901
- }
34989
+ fill: "transparent"
34902
34990
  }
34903
- }), props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
34904
- key: i,
34905
- point: point,
34906
- sequenceNumber: i + 1,
34907
- onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
34908
- ref: ref => {
34909
- pointRef.current[i] = ref;
34910
- },
34911
- onFocus: () => {
34912
- dispatch(actions.polygon.focusPoint(i));
34913
- },
34914
- onClick: () => {
34915
- // If the point being clicked is the first point and
34916
- // there's enough points to form a polygon (3 or more)
34917
- // Close the shape before setting focus.
34918
- if (i === 0 && props.graphState.coords.length >= 3) {
34919
- dispatch(actions.polygon.closePolygon());
34920
- }
34921
- dispatch(actions.polygon.clickPoint(i));
34991
+ }
34992
+ }), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
34993
+ key: i,
34994
+ point: point,
34995
+ sequenceNumber: i + 1,
34996
+ onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
34997
+ ref: ref => {
34998
+ pointsRef.current[i] = ref;
34999
+ },
35000
+ onFocus: () => {
35001
+ dispatch(actions.polygon.focusPoint(i));
35002
+ },
35003
+ onClick: () => {
35004
+ // If the point being clicked is the first point and
35005
+ // there's enough points to form a polygon (3 or more)
35006
+ // Close the shape before setting focus.
35007
+ if (i === 0 && coords.length >= 3) {
35008
+ dispatch(actions.polygon.closePolygon());
34922
35009
  }
34923
- })));
34924
- }
35010
+ dispatch(actions.polygon.clickPoint(i));
35011
+ }
35012
+ })));
34925
35013
  };
34926
35014
  function getLines(points) {
34927
35015
  return points.map((point, i) => {
@@ -34933,17 +35021,13 @@ const hasFocusVisible = element => {
34933
35021
  const matches = selector => element?.matches(selector) ?? false;
34934
35022
  try {
34935
35023
  return matches(":focus-visible");
34936
- } catch (e) {
35024
+ } catch {
34937
35025
  // jsdom doesn't support :focus-visible
34938
35026
  // (see https://github.com/jsdom/jsdom/issues/3426),
34939
35027
  // so the call to matches(":focus-visible") will fail in tests.
34940
35028
  return matches(":focus");
34941
35029
  }
34942
35030
  };
34943
- const PolygonGraph = props => {
34944
- const numSides = props.graphState.numSides;
34945
- return numSides === "unlimited" ? UnlimitedPolygonGraph(props) : LimitedPolygonGraph(props);
34946
- };
34947
35031
 
34948
35032
  function renderQuadraticGraph(state, dispatch) {
34949
35033
  return {
@@ -40364,7 +40448,7 @@ class Sortable extends React__namespace.Component {
40364
40448
  this.setState({
40365
40449
  items
40366
40450
  }, () => {
40367
- this.props.onMeasure && this.props.onMeasure({
40451
+ this.props.onMeasure?.({
40368
40452
  widths: widths,
40369
40453
  heights: heights
40370
40454
  });
@@ -40414,7 +40498,7 @@ class Sortable extends React__namespace.Component {
40414
40498
  this.setState({
40415
40499
  items: nextItems
40416
40500
  });
40417
- this.props.onChange && this.props.onChange({});
40501
+ this.props.onChange?.({});
40418
40502
  }
40419
40503
  onMouseMove(key) {
40420
40504
  // Dragging: Rearrange items based on draggable's position
@@ -40494,7 +40578,7 @@ class Sortable extends React__namespace.Component {
40494
40578
  // HACK: We need to know *that* the widget changed, but currently it's
40495
40579
  // not set up in a nice way to tell us *how* it changed, since the
40496
40580
  // permutation of the items is stored in state.
40497
- this.props.onChange && this.props.onChange({});
40581
+ this.props.onChange?.({});
40498
40582
  });
40499
40583
 
40500
40584
  // @ts-expect-error - TS2339 - Property 'animationFrameRequest' does not exist on type 'Sortable'.
@@ -43219,7 +43303,7 @@ class Card extends React__namespace.Component {
43219
43303
  dragging: true
43220
43304
  });
43221
43305
  this.bindMouseMoveUp();
43222
- this.props.onMouseDown && this.props.onMouseDown(loc, this);
43306
+ this.props.onMouseDown?.(loc, this);
43223
43307
  }
43224
43308
  };
43225
43309
  onMouseMove = event => {
@@ -43229,7 +43313,7 @@ class Card extends React__namespace.Component {
43229
43313
  event.preventDefault();
43230
43314
  const loc = Util.extractPointerLocation(event);
43231
43315
  if (loc) {
43232
- this.props.onMouseMove && this.props.onMouseMove(loc);
43316
+ this.props.onMouseMove?.(loc);
43233
43317
  }
43234
43318
  };
43235
43319
  onMouseUp = event => {
@@ -43240,7 +43324,7 @@ class Card extends React__namespace.Component {
43240
43324
  dragging: false
43241
43325
  });
43242
43326
  this.unbindMouseMoveUp();
43243
- this.props.onMouseUp && this.props.onMouseUp(loc);
43327
+ this.props.onMouseUp?.(loc);
43244
43328
  }
43245
43329
  };
43246
43330
  render() {
@@ -45446,7 +45530,7 @@ const init = function () {
45446
45530
 
45447
45531
  // This file is processed by a Rollup plugin (replace) to inject the production
45448
45532
  const libName = "@khanacademy/perseus";
45449
- const libVersion = "47.0.0";
45533
+ const libVersion = "48.0.0";
45450
45534
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45451
45535
 
45452
45536
  const apiVersion = {
@@ -46206,28 +46290,6 @@ class ServerItemRenderer extends React__namespace.Component {
46206
46290
  return this.questionRenderer.getUserInputMap();
46207
46291
  }
46208
46292
 
46209
- /**
46210
- * Grades the item.
46211
- *
46212
- * @deprecated use scorePerseusItem
46213
- */
46214
- scoreInput() {
46215
- const guess = this.getUserInput();
46216
- const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
46217
-
46218
- // Continue to include an empty guess for the now defunct answer area.
46219
- // TODO(alex): Check whether we rely on the format here for
46220
- // analyzing ProblemLogs. If not, remove this layer.
46221
- const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
46222
- const keScore = keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
46223
- const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
46224
- this.setState({
46225
- questionCompleted: keScore.correct,
46226
- questionHighlightedWidgets: emptyQuestionAreaWidgets
46227
- });
46228
- return keScore;
46229
- }
46230
-
46231
46293
  /**
46232
46294
  * Returns an array of all widget IDs in the order they occur in
46233
46295
  * the question content.