@khanacademy/perseus 20.7.0 → 20.8.1

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
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { keyTranslator, CursorContext, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$1 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import * as i18n from '@khanacademy/wonder-blocks-i18n';
7
7
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
8
8
  import classNames from 'classnames';
@@ -10,9 +10,8 @@ import * as React from 'react';
10
10
  import React__default, { useState, useEffect, useRef } from 'react';
11
11
  import _, { debounce as debounce$1 } from 'underscore';
12
12
  import Clickable from '@khanacademy/wonder-blocks-clickable';
13
- import Color, { fade } from '@khanacademy/wonder-blocks-color';
14
13
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
15
- import Spacing from '@khanacademy/wonder-blocks-spacing';
14
+ import { color, spacing } from '@khanacademy/wonder-blocks-tokens';
16
15
  import { StyleSheet, css } from 'aphrodite';
17
16
  import $ from 'jquery';
18
17
  import PropTypes from 'prop-types';
@@ -31,8 +30,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
31
30
  import caretDown from '@phosphor-icons/core/assets/regular/caret-down.svg';
32
31
  import caretUp from '@phosphor-icons/core/assets/regular/caret-up.svg';
33
32
  import debounce$2 from 'lodash.debounce';
34
- import { Point as Point$2, vec, useTransformContext, useMovable, Vector, Polygon, Coordinates, Mafs } from 'mafs';
35
- import { color } from '@khanacademy/wonder-blocks-tokens';
33
+ import { Point as Point$2, useTransformContext, vec, useMovable, Vector, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
36
34
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
37
35
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
38
36
  import { LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
@@ -331,16 +329,16 @@ const MathInputIcon = ({
331
329
  focused,
332
330
  active
333
331
  }) => {
334
- let color;
332
+ let fillColor;
335
333
  switch (true) {
336
334
  case focused || active:
337
- color = Color.white;
335
+ fillColor = color.white;
338
336
  break;
339
337
  case hovered:
340
- color = Color.blue;
338
+ fillColor = color.blue;
341
339
  break;
342
340
  default:
343
- color = Color.offBlack;
341
+ fillColor = color.offBlack;
344
342
  break;
345
343
  }
346
344
  const dynamicClass = active || focused ? styles$D.iconActive : styles$D.iconInactive;
@@ -350,7 +348,7 @@ const MathInputIcon = ({
350
348
  xmlns: "http://www.w3.org/2000/svg",
351
349
  width: "16",
352
350
  height: "16",
353
- fill: color,
351
+ fill: fillColor,
354
352
  viewBox: "0 0 256 256"
355
353
  }, /*#__PURE__*/React.createElement("path", {
356
354
  d: "M112,72a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16h64A8,8,0,0,1,112,72Zm-8,104H80V152a8,8,0,0,0-16,0v24H40a8,8,0,0,0,0,16H64v24a8,8,0,0,0,16,0V192h24a8,8,0,0,0,0-16Zm48,0h64a8,8,0,0,0,0-16H152a8,8,0,0,0,0,16Zm64,16H152a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Zm-61.66-90.34a8,8,0,0,0,11.32,0L184,83.31l18.34,18.35a8,8,0,0,0,11.32-11.32L195.31,72l18.35-18.34a8,8,0,0,0-11.32-11.32L184,60.69,165.66,42.34a8,8,0,0,0-11.32,11.32L172.69,72,154.34,90.34A8,8,0,0,0,154.34,101.66Z"
@@ -387,7 +385,7 @@ const mapButtonSets = buttonSets => {
387
385
  };
388
386
  const inputFocused = {
389
387
  borderWidth: 2,
390
- borderColor: Color.blue,
388
+ borderColor: color.blue,
391
389
  margin: -1
392
390
  };
393
391
  const styles$D = StyleSheet.create({
@@ -395,35 +393,35 @@ const styles$D = StyleSheet.create({
395
393
  display: "flex",
396
394
  justifyContent: "center",
397
395
  height: "100%",
398
- padding: Spacing.xxxSmall_4,
396
+ padding: spacing.xxxSmall_4,
399
397
  borderRadius: 1
400
398
  },
401
399
  iconInactive: {
402
- backgroundColor: Color.offBlack8
400
+ backgroundColor: color.offBlack8
403
401
  },
404
402
  iconActive: {
405
- backgroundColor: Color.offBlack64
403
+ backgroundColor: color.offBlack64
406
404
  },
407
405
  outerWrapper: {
408
406
  display: "inline-block",
409
407
  borderStyle: "solid",
410
408
  borderWidth: 1,
411
- borderColor: Color.offBlack50,
409
+ borderColor: color.offBlack50,
412
410
  borderRadius: 3,
413
- background: Color.white,
411
+ background: color.white,
414
412
  ":hover": inputFocused
415
413
  },
416
414
  wrapperFocused: inputFocused,
417
415
  wrapperError: {
418
- borderColor: Color.red,
419
- background: fade(Color.red, 0.08),
416
+ borderColor: color.red,
417
+ background: color.fadedRed8,
420
418
  ":hover": {
421
- borderColor: Color.red
419
+ borderColor: color.red
422
420
  }
423
421
  },
424
422
  popoverContent: {
425
423
  padding: 0,
426
- paddingBottom: Spacing.xxSmall_6,
424
+ paddingBottom: spacing.xxSmall_6,
427
425
  maxWidth: "initial"
428
426
  }
429
427
  });
@@ -21763,13 +21761,13 @@ const styles$A = StyleSheet.create({
21763
21761
  width: 40
21764
21762
  },
21765
21763
  leftAlign: {
21766
- paddingLeft: Spacing.xxxSmall_4,
21764
+ paddingLeft: spacing.xxxSmall_4,
21767
21765
  paddingRight: 0
21768
21766
  },
21769
21767
  rightAlign: {
21770
21768
  textAlign: "right",
21771
21769
  paddingLeft: 0,
21772
- paddingRight: Spacing.xxxSmall_4
21770
+ paddingRight: spacing.xxxSmall_4
21773
21771
  },
21774
21772
  answerStateUnanswered: {
21775
21773
  backgroundColor: "#eee",
@@ -22867,19 +22865,19 @@ function ChoiceInner(props) {
22867
22865
  function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct) {
22868
22866
  let backgroundColor;
22869
22867
  let borderColor;
22870
- let color;
22868
+ let color$1;
22871
22869
  if (!showCorrectness && pressed) {
22872
- borderColor = Color.blue;
22873
- color = Color.blue;
22870
+ borderColor = color.blue;
22871
+ color$1 = color.blue;
22874
22872
  backgroundColor = "transparent";
22875
22873
  } else if (checked) {
22876
- const bg = showCorrectness && correct ? Color.green : Color.blue;
22877
- color = white$1;
22874
+ const bg = showCorrectness && correct ? color.green : color.blue;
22875
+ color$1 = white$1;
22878
22876
  backgroundColor = bg;
22879
22877
  borderColor = bg;
22880
22878
  } else {
22881
- borderColor = Color.offBlack64;
22882
- color = Color.offBlack64;
22879
+ borderColor = color.offBlack64;
22880
+ color$1 = color.offBlack64;
22883
22881
  }
22884
22882
 
22885
22883
  // define shape
@@ -22892,7 +22890,7 @@ function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, cor
22892
22890
  return {
22893
22891
  backgroundColor,
22894
22892
  borderColor,
22895
- color,
22893
+ color: color$1,
22896
22894
  borderRadius
22897
22895
  };
22898
22896
  }
@@ -22913,7 +22911,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
22913
22911
  return /*#__PURE__*/React.createElement("div", {
22914
22912
  className: css(styles$y.iconWrapper)
22915
22913
  }, /*#__PURE__*/React.createElement(FocusRing, {
22916
- color: Color.blue,
22914
+ color: color.blue,
22917
22915
  visible: focused || hovered,
22918
22916
  multipleSelect: multipleSelect
22919
22917
  }, /*#__PURE__*/React.createElement("div", {
@@ -22978,9 +22976,9 @@ const styles$w = StyleSheet.create({
22978
22976
  color: gray68
22979
22977
  },
22980
22978
  circleIncorrectAnswered: {
22981
- backgroundColor: Color.red,
22982
- borderColor: Color.red,
22983
- color: Color.white
22979
+ backgroundColor: color.red,
22980
+ borderColor: color.red,
22981
+ color: color.white
22984
22982
  }
22985
22983
  });
22986
22984
 
@@ -23048,13 +23046,13 @@ const styles$v = StyleSheet.create({
23048
23046
  textTransform: "uppercase"
23049
23047
  },
23050
23048
  correct: {
23051
- color: Color.green
23049
+ color: color.green
23052
23050
  },
23053
23051
  incorrectAnswered: {
23054
- color: Color.red
23052
+ color: color.red
23055
23053
  },
23056
23054
  incorrect: {
23057
- color: Color.offBlack64
23055
+ color: color.offBlack64
23058
23056
  }
23059
23057
  });
23060
23058
 
@@ -23129,7 +23127,7 @@ const Choice = function Choice(props) {
23129
23127
  // @ts-expect-error - TS2322 - Type '{ dispay: string; flexDirection: "column"; color: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
23130
23128
  dispay: "flex",
23131
23129
  flexDirection: "column",
23132
- color: Color.offBlack
23130
+ color: color.offBlack
23133
23131
  },
23134
23132
  className: descriptionClassName
23135
23133
  }, /*#__PURE__*/React.createElement("div", {
@@ -23172,7 +23170,7 @@ const Choice = function Choice(props) {
23172
23170
  disabled: disabled || apiOptions.readOnly,
23173
23171
  style: {
23174
23172
  flex: 1,
23175
- color: Color.offBlack,
23173
+ color: color.offBlack,
23176
23174
  userSelect: "text"
23177
23175
  },
23178
23176
  ref: forwardedRef,
@@ -23187,9 +23185,9 @@ const Choice = function Choice(props) {
23187
23185
  flexDirection: "row",
23188
23186
  justifyContent: "center",
23189
23187
  alignContent: "center",
23190
- paddingTop: Spacing.xSmall_8,
23191
- paddingBottom: Spacing.xSmall_8,
23192
- paddingLeft: Spacing.xSmall_8
23188
+ paddingTop: spacing.xSmall_8,
23189
+ paddingBottom: spacing.xSmall_8,
23190
+ paddingLeft: spacing.xSmall_8
23193
23191
  }
23194
23192
  }, /*#__PURE__*/React.createElement(ChoiceIcon, {
23195
23193
  pos: pos,
@@ -23205,7 +23203,7 @@ const Choice = function Choice(props) {
23205
23203
  previouslyAnswered: previouslyAnswered
23206
23204
  }), /*#__PURE__*/React.createElement("span", {
23207
23205
  style: {
23208
- paddingLeft: Spacing.small_12,
23206
+ paddingLeft: spacing.small_12,
23209
23207
  textAlign: "left",
23210
23208
  flex: 1,
23211
23209
  paddingTop: 4
@@ -23225,7 +23223,7 @@ const Choice = function Choice(props) {
23225
23223
  content: i18n._("Cross out option"),
23226
23224
  closeButtonVisible: true,
23227
23225
  actions: /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(Strut, {
23228
- size: Spacing.medium_16
23226
+ size: spacing.medium_16
23229
23227
  }), /*#__PURE__*/React.createElement(Button, {
23230
23228
  kind: "primary",
23231
23229
  "aria-label": i18n._("Cross out Choice %(letter)s", {
@@ -23273,7 +23271,7 @@ const Choice = function Choice(props) {
23273
23271
  }) => /*#__PURE__*/React.createElement(Icon, {
23274
23272
  icon: ellipsisHorizontalIcon,
23275
23273
  size: 3,
23276
- color: Color.offBlack64
23274
+ color: color.offBlack64
23277
23275
  })))), showRationale && /*#__PURE__*/React.createElement("div", {
23278
23276
  className: rationaleClassName,
23279
23277
  "data-test-id": `perseus-radio-rationale-content-${pos}`
@@ -23289,7 +23287,7 @@ const styles$u = StyleSheet.create({
23289
23287
  padding: intermediateCheckboxPadding,
23290
23288
  paddingTop: 0,
23291
23289
  marginLeft: 54,
23292
- color: Color.offBlack64,
23290
+ color: color.offBlack64,
23293
23291
  [mediaQueries.smOrSmaller]: {
23294
23292
  padding: intermediateCheckboxPaddingPhone,
23295
23293
  paddingTop: 0
@@ -24687,8 +24685,8 @@ class Definition extends React.Component {
24687
24685
  pressed
24688
24686
  }) => /*#__PURE__*/React.createElement("span", {
24689
24687
  style: {
24690
- color: Color.blue,
24691
- borderBottom: hovered || focused || pressed ? `2px solid ${Color.blue}` : "none"
24688
+ color: color.blue,
24689
+ borderBottom: hovered || focused || pressed ? `2px solid ${color.blue}` : "none"
24692
24690
  }
24693
24691
  }, this.props.togglePrompt))));
24694
24692
  }
@@ -24699,7 +24697,7 @@ Definition.defaultProps = {
24699
24697
  };
24700
24698
  const styles$q = {
24701
24699
  tooltipBody: {
24702
- color: Color.offBlack,
24700
+ color: color.offBlack,
24703
24701
  fontSize: 20,
24704
24702
  fontWeight: 500,
24705
24703
  lineHeight: "30px"
@@ -24999,7 +24997,7 @@ class GradedGroupAnswerBar extends React.Component {
24999
24997
  onNextQuestion
25000
24998
  } = this.props;
25001
24999
  const answerBarStyle = _extends({}, styles$o.answerBar, {
25002
- backgroundColor: answerBarState === "CORRECT" ? Color.offWhite : "white",
25000
+ backgroundColor: answerBarState === "CORRECT" ? color.offWhite : "white",
25003
25001
  // Center the "Correct!" message only when there's no next question
25004
25002
  justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
25005
25003
  });
@@ -25029,7 +25027,7 @@ class GradedGroupAnswerBar extends React.Component {
25029
25027
  }, /*#__PURE__*/React.createElement("span", {
25030
25028
  style: {
25031
25029
  fontSize: 28,
25032
- color: Color.green
25030
+ color: color.green
25033
25031
  }
25034
25032
  }, /*#__PURE__*/React.createElement(InlineIcon, _extends({}, iconStar, {
25035
25033
  style: {
@@ -25059,7 +25057,7 @@ const styles$o = {
25059
25057
  marginTop: phoneMargin,
25060
25058
  paddingLeft: phoneMargin,
25061
25059
  paddingRight: 10,
25062
- borderTop: `1px solid ${Color.offBlack50}`
25060
+ borderTop: `1px solid ${color.offBlack50}`
25063
25061
  },
25064
25062
  tryAgainIcon: {
25065
25063
  fontSize: 28,
@@ -25361,7 +25359,7 @@ const styles$n = StyleSheet.create({
25361
25359
  padding: 0,
25362
25360
  border: "none",
25363
25361
  marginTop: 20,
25364
- color: Color.blue,
25362
+ color: color.blue,
25365
25363
  cursor: "pointer",
25366
25364
  display: "block",
25367
25365
  clear: "both"
@@ -25369,7 +25367,7 @@ const styles$n = StyleSheet.create({
25369
25367
  explanationTitle: {
25370
25368
  backgroundColor: "unset",
25371
25369
  marginTop: 20,
25372
- color: Color.blue,
25370
+ color: color.blue,
25373
25371
  marginBottom: 10,
25374
25372
  cursor: "pointer",
25375
25373
  fontSize: 14,
@@ -25578,7 +25576,7 @@ const styles$m = StyleSheet.create({
25578
25576
  },
25579
25577
  title: {
25580
25578
  fontSize: 12,
25581
- color: Color.offBlack64,
25579
+ color: color.offBlack64,
25582
25580
  textTransform: "uppercase",
25583
25581
  marginBottom: 11,
25584
25582
  letterSpacing: 0.8
@@ -25612,7 +25610,7 @@ const styles$m = StyleSheet.create({
25612
25610
  height: 10,
25613
25611
  borderRadius: "100%",
25614
25612
  borderWidth: 2,
25615
- borderColor: Color.blue,
25613
+ borderColor: color.blue,
25616
25614
  borderStyle: "solid"
25617
25615
  },
25618
25616
  indicatorDotFocused: {
@@ -25620,7 +25618,7 @@ const styles$m = StyleSheet.create({
25620
25618
  borderStyle: "double"
25621
25619
  },
25622
25620
  indicatorDotActive: {
25623
- backgroundColor: Color.blue,
25621
+ backgroundColor: color.blue,
25624
25622
  width: "100%",
25625
25623
  height: "100%"
25626
25624
  },
@@ -28216,6 +28214,54 @@ const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.
28216
28214
  return (coord + ranges[i][1]) / extent;
28217
28215
  }));
28218
28216
 
28217
+ const useTransform = (...points) => {
28218
+ const {
28219
+ viewTransform,
28220
+ userTransform
28221
+ } = useTransformContext();
28222
+ const transformToPx = vec.matrixMult(viewTransform, userTransform);
28223
+ const transformedPoints = points.map(point => vec.transform(point, transformToPx));
28224
+ return transformedPoints;
28225
+ };
28226
+
28227
+ /**
28228
+ * Given two points, find the tips that extends through the points to the edge of the range.
28229
+ * @param collinearPoint - The point that the line passes through. Needed to establish slope.
28230
+ * @param extendFrom - The point that the line extends from to the edge of the graph.
28231
+ */
28232
+ const getRayIntersectionCoords = (collinearPoint, extendFrom, range) => {
28233
+ // edges of the graph
28234
+ const [[xMin, xMax], [yMin, yMax]] = range;
28235
+ const [aX, aY] = collinearPoint;
28236
+ const [bX, bY] = extendFrom;
28237
+ const yDiff = bY - aY;
28238
+ const xDiff = bX - aX;
28239
+ const slope = yDiff / xDiff;
28240
+ const yAtXMin = slope * (xMin - aX) + aY;
28241
+ const yAtXMax = slope * (xMax - aX) + aY;
28242
+ const xAtYMin = (yMin - aY) / slope + aX;
28243
+ const xAtYMax = (yMax - aY) / slope + aX;
28244
+
28245
+ // clock analogy to describe quadrants
28246
+ switch (true) {
28247
+ // 12 o'clock to 2:59
28248
+ case yDiff > 0 && xDiff >= 0:
28249
+ return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28250
+ // 3 o'clock to 5:59
28251
+ case yDiff <= 0 && xDiff > 0:
28252
+ // xAtYMin evaluates to -Infinity here, so we use absolute value
28253
+ return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28254
+ // 9 o'clock to 11:59
28255
+ case yDiff >= 0 && xDiff < 0:
28256
+ return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28257
+ // 6 o'clock to 8:59
28258
+ case yDiff < 0 && xDiff <= 0:
28259
+ return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28260
+ default:
28261
+ return [xMax, yAtXMax];
28262
+ }
28263
+ };
28264
+
28219
28265
  const defaultStroke = "var(--movable-line-stroke-color)";
28220
28266
  const MovableLine = props => {
28221
28267
  const {
@@ -28226,18 +28272,12 @@ const MovableLine = props => {
28226
28272
  stroke = defaultStroke
28227
28273
  } = props;
28228
28274
  const midpoint = vec.midpoint(start, end);
28229
- const {
28230
- viewTransform,
28231
- userTransform
28232
- } = useTransformContext();
28233
- const transformToPx = vec.matrixMult(viewTransform, userTransform);
28234
- const startPtPx = vec.transform(start, transformToPx);
28235
- const endPtPx = vec.transform(end, transformToPx);
28275
+ const [startPtPx, endPtPx] = useTransform(start, end);
28236
28276
  let startExtend = undefined;
28237
28277
  let endExtend = undefined;
28238
28278
  if (extend) {
28239
- startExtend = extend.start ? getExtensionCoords(end, start, extend.range) : undefined;
28240
- endExtend = extend.end ? getExtensionCoords(start, end, extend.range) : undefined;
28279
+ startExtend = extend.start ? getRayIntersectionCoords(end, start, extend.range) : undefined;
28280
+ endExtend = extend.end ? getRayIntersectionCoords(start, end, extend.range) : undefined;
28241
28281
  }
28242
28282
  const line = useRef(null);
28243
28283
  const {
@@ -28299,50 +28339,12 @@ function SVGLine(props) {
28299
28339
  });
28300
28340
  }
28301
28341
 
28302
- /**
28303
- * Given two points, find the tips that extends through the points to the edge of the range.
28304
- * @param collinearPoint - The point that the line passes through. Needed to establish slope.
28305
- * @param extendFrom - The point that the line extends from to the edge of the graph.
28306
- */
28307
- const getExtensionCoords = (collinearPoint, extendFrom, range) => {
28308
- // edges of the graph
28309
- const [[xMin, xMax], [yMin, yMax]] = range;
28310
- const [aX, aY] = collinearPoint;
28311
- const [bX, bY] = extendFrom;
28312
- const yDiff = bY - aY;
28313
- const xDiff = bX - aX;
28314
- const slope = yDiff / xDiff;
28315
- const yAtXMin = slope * (xMin - aX) + aY;
28316
- const yAtXMax = slope * (xMax - aX) + aY;
28317
- const xAtYMin = (yMin - aY) / slope + aX;
28318
- const xAtYMax = (yMax - aY) / slope + aX;
28319
-
28320
- // clock analogy to describe quadrants
28321
- switch (true) {
28322
- // 12 o'clock to 2:59
28323
- case yDiff > 0 && xDiff >= 0:
28324
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28325
- // 3 o'clock to 5:59
28326
- case yDiff <= 0 && xDiff > 0:
28327
- // xAtYMin evaluates to -Infinity here, so we use absolute value
28328
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28329
- // 9 o'clock to 11:59
28330
- case yDiff >= 0 && xDiff < 0:
28331
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28332
- // 6 o'clock to 8:59
28333
- case yDiff < 0 && xDiff <= 0:
28334
- return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28335
- default:
28336
- return [xMax, yAtXMax];
28337
- }
28338
- };
28339
-
28340
28342
  const StyledMovablePoint = props => {
28341
28343
  const hitboxRef = useRef(null);
28342
28344
  const {
28343
28345
  point,
28344
28346
  onMove,
28345
- color = Color.blue
28347
+ color: color$1 = color.blue
28346
28348
  } = props;
28347
28349
  const {
28348
28350
  dragging
@@ -28352,12 +28354,7 @@ const StyledMovablePoint = props => {
28352
28354
  onMove,
28353
28355
  constrain: p => p
28354
28356
  });
28355
- const {
28356
- viewTransform,
28357
- userTransform
28358
- } = useTransformContext();
28359
- const transformToPx = vec.matrixMult(viewTransform, userTransform);
28360
- const [x, y] = vec.transform(point, transformToPx);
28357
+ const [[x, y]] = useTransform(point);
28361
28358
  return /*#__PURE__*/React.createElement("g", {
28362
28359
  ref: hitboxRef,
28363
28360
  className: "movable-point",
@@ -28366,7 +28363,7 @@ const StyledMovablePoint = props => {
28366
28363
  cursor: dragging ? "grabbing" : "grab",
28367
28364
  touchAction: "none",
28368
28365
  outline: "none",
28369
- "--movable-point-color": color
28366
+ "--movable-point-color": color$1
28370
28367
  }
28371
28368
  }, /*#__PURE__*/React.createElement("circle", {
28372
28369
  className: "movable-point-hitbox",
@@ -28386,7 +28383,7 @@ const StyledMovablePoint = props => {
28386
28383
  cx: x,
28387
28384
  cy: y,
28388
28385
  style: {
28389
- fill: color
28386
+ fill: color$1
28390
28387
  }
28391
28388
  }));
28392
28389
  };
@@ -28524,6 +28521,152 @@ const RayGraph = props => {
28524
28521
  }));
28525
28522
  };
28526
28523
 
28524
+ const Angle = ({
28525
+ centerPoint,
28526
+ endPoints,
28527
+ range,
28528
+ polygonPoints,
28529
+ showAngles
28530
+ }) => {
28531
+ const [centerX, centerY] = centerPoint;
28532
+ const areClockwise = clockwise$1([centerPoint, ...endPoints]);
28533
+ const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
28534
+ const radius = 0.3;
28535
+ const a = vec.dist(centerPoint, endPoints[0]);
28536
+ const b = vec.dist(centerPoint, endPoints[1]);
28537
+ const c = vec.dist(endPoints[0], endPoints[1]);
28538
+
28539
+ // Law of cosines
28540
+ const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
28541
+ const y1 = centerY + (startY - centerY) / a * radius;
28542
+ const x2 = centerX + (endX - centerX) / b * radius;
28543
+ const x1 = centerX + (startX - centerX) / a * radius;
28544
+ const y2 = centerY + (endY - centerY) / b * radius;
28545
+
28546
+ // Fourth point that would create a rhomboid
28547
+ // Used to calculate the inside of the angle and to create square
28548
+ const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
28549
+ if (!showAngles) {
28550
+ return isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleArc, {
28551
+ start: [x1, y1],
28552
+ vertex: [x2, y2],
28553
+ end: [x3, y3]
28554
+ }) : null;
28555
+ }
28556
+
28557
+ // Midpoint betwen ends of arc
28558
+ const isInside = shouldDrawArcInside([x3, y3], centerPoint, range, polygonPoints);
28559
+ const largeArcFlag = isInside ? 1 : 0;
28560
+ const sweepFlag = isInside ? 1 : 0;
28561
+ const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
28562
+ let angleInDegrees = angle * (180 / Math.PI);
28563
+ // If we have triggered "largArcFlag", the angle should be greater than 180
28564
+ if (isInside) {
28565
+ angleInDegrees = 360 - angleInDegrees;
28566
+ }
28567
+ const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
28568
+ const angleLabel = Number.isInteger(angleLabelNumber) ? angleLabelNumber : "≈ " + angleLabelNumber;
28569
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
28570
+ id: "background",
28571
+ x: "-5%",
28572
+ width: "110%",
28573
+ y: "0%",
28574
+ height: "100%"
28575
+ }, /*#__PURE__*/React.createElement("feFlood", {
28576
+ floodColor: "#FFF",
28577
+ floodOpacity: "0.5"
28578
+ }), /*#__PURE__*/React.createElement("feComposite", {
28579
+ operator: "over",
28580
+ in: "SourceGraphic"
28581
+ }))), !isInside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleArc, {
28582
+ start: [x1, y1],
28583
+ vertex: [x2, y2],
28584
+ end: [x3, y3]
28585
+ }) : /*#__PURE__*/React.createElement("g", {
28586
+ style: {
28587
+ transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28588
+ }
28589
+ }, /*#__PURE__*/React.createElement("path", {
28590
+ d: arc,
28591
+ strokeWidth: 0.02,
28592
+ fill: "none"
28593
+ })), /*#__PURE__*/React.createElement(Text$1, {
28594
+ x: x3,
28595
+ y: y3,
28596
+ size: 15,
28597
+ svgTextProps: {
28598
+ filter: "url(#background)",
28599
+ fontWeight: "bold"
28600
+ }
28601
+ // Shift position if text is too close to movable point
28602
+ ,
28603
+ attach: y3 - centerY > 0 ? "s" : "n",
28604
+ attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
28605
+ }, angleLabel, "\xB0"));
28606
+ };
28607
+ const RightAngleArc = ({
28608
+ start: [x1, y1],
28609
+ vertex: [x2, y2],
28610
+ end: [x3, y3]
28611
+ }) => /*#__PURE__*/React.createElement("g", {
28612
+ style: {
28613
+ transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28614
+ }
28615
+ }, /*#__PURE__*/React.createElement("path", {
28616
+ d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
28617
+ strokeWidth: 0.02,
28618
+ fill: "none"
28619
+ }));
28620
+ const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
28621
+ const shouldDrawArcInside = (midPoint, vertex, range, polygonPoints) => {
28622
+ const rangeIntersectionPoint = getRayIntersectionCoords(vertex, midPoint, range);
28623
+ const lines = getLines(polygonPoints);
28624
+ let lineIntersections = 0;
28625
+ lines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersections++);
28626
+
28627
+ // The intersection check will sometimes return false if it intersects with
28628
+ // another vertex, so in the case we get 0 intersections, we check for points.
28629
+ if (lineIntersections === 0) {
28630
+ // find point in array
28631
+ const midpointIndex = polygonPoints.findIndex(([x, y]) => x === midPoint[0] && y === midPoint[1]);
28632
+ // get array sans point and adjacent points
28633
+ const [prev, next] = [(midpointIndex - 1 + polygonPoints.length) % polygonPoints.length, (midpointIndex + 1) % polygonPoints.length];
28634
+ const [[lineAx, lineAy], [lineBx, lineBy]] = [midPoint, rangeIntersectionPoint];
28635
+ for (let i = 0; i < polygonPoints.length; i++) {
28636
+ if ([prev, midpointIndex, next].includes(i)) {
28637
+ continue;
28638
+ }
28639
+ const [x, y] = polygonPoints[i];
28640
+ const intersectsPoint = (y - lineAy) * (lineBx - lineAx) === (lineBy - lineAy) * (x - lineAx);
28641
+ if (intersectsPoint) {
28642
+ return false;
28643
+ }
28644
+ }
28645
+ return true;
28646
+ }
28647
+
28648
+ // If the number of intersections is even, the angle is inside the polygon
28649
+ return isEven(lineIntersections);
28650
+ };
28651
+ const getLines = points => points.map((point, i) => {
28652
+ const next = points[(i + 1) % points.length];
28653
+ return [point, next];
28654
+ });
28655
+
28656
+ // https://stackoverflow.com/a/24392281/7347484
28657
+ // The "intersects" function in geometry doesn't seem to work for this use case
28658
+ const linesIntersect = ([[a, b], [c, d]], [[p, q], [r, s]]) => {
28659
+ const determinant = (c - a) * (s - q) - (r - p) * (d - b);
28660
+ if (determinant === 0) {
28661
+ return false;
28662
+ } else {
28663
+ const lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / determinant;
28664
+ const gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / determinant;
28665
+ return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1;
28666
+ }
28667
+ };
28668
+ const isEven = n => n % 2 === 0;
28669
+
28527
28670
  const PolygonGraph = props => {
28528
28671
  var _points$length;
28529
28672
  const [focused, setFocused] = React.useState(false);
@@ -28533,7 +28676,9 @@ const PolygonGraph = props => {
28533
28676
  } = props;
28534
28677
  const {
28535
28678
  coords,
28536
- type
28679
+ type,
28680
+ showAngles,
28681
+ range
28537
28682
  } = props.graphState;
28538
28683
  const points = coords != null ? coords : [[0, 0]];
28539
28684
  const pointsSum = points.reduce((acc, point) => vec.add(acc, point), [0, 0]);
@@ -28557,6 +28702,21 @@ const PolygonGraph = props => {
28557
28702
  svgPolygonProps: {
28558
28703
  strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)"
28559
28704
  }
28705
+ }), points.map((point, i) => {
28706
+ const pt1 = points.at(i - 1);
28707
+ const pt2 = points[(i + 1) % points.length];
28708
+ if (!pt1 || !pt2) {
28709
+ return null;
28710
+ }
28711
+ return /*#__PURE__*/React.createElement(Angle, {
28712
+ key: "angle-" + i,
28713
+ centerPoint: point,
28714
+ endPoints: [pt1, pt2],
28715
+ active: active,
28716
+ range: range,
28717
+ polygonPoints: points,
28718
+ showAngles: !!showAngles
28719
+ });
28560
28720
  }), /*#__PURE__*/React.createElement(Polygon, {
28561
28721
  points: [...points],
28562
28722
  color: "transparent",
@@ -28574,7 +28734,7 @@ const PolygonGraph = props => {
28574
28734
  className: "movable-polygon"
28575
28735
  }
28576
28736
  }), points.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
28577
- key: i,
28737
+ key: "point-" + i,
28578
28738
  point: point,
28579
28739
  onMove: destination => dispatch(moveControlPoint(i, destination)),
28580
28740
  "data-testid": type + i
@@ -29256,7 +29416,10 @@ var components = /*#__PURE__*/Object.freeze({
29256
29416
  * If a graphie URL is provided in `backgroundImage`, will return the rendered graphie background.
29257
29417
  * Otherwise, returns `null`.
29258
29418
  */
29259
- const getLegacyGrid = (box, backgroundImage) => {
29419
+ const LegacyGrid = ({
29420
+ box,
29421
+ backgroundImage
29422
+ }) => {
29260
29423
  const {
29261
29424
  url,
29262
29425
  width,
@@ -29533,17 +29696,17 @@ function initializeGraphState(params) {
29533
29696
  })
29534
29697
  };
29535
29698
  case "polygon":
29536
- return {
29537
- type: "polygon",
29699
+ return _extends({
29538
29700
  hasBeenInteractedWith: false,
29539
29701
  range,
29540
- snapStep,
29702
+ snapStep
29703
+ }, graph, {
29541
29704
  coords: getPolygonCoords({
29542
29705
  graph,
29543
29706
  range,
29544
29707
  step
29545
29708
  })
29546
- };
29709
+ });
29547
29710
  case "angle":
29548
29711
  case "point":
29549
29712
  case "circle":
@@ -29604,9 +29767,10 @@ const getDefaultSegments = ({
29604
29767
  return [5];
29605
29768
  }
29606
29769
  };
29770
+ const defaultRange = [[-10, 10], [-10, 10]];
29607
29771
  return ys(graph.numSegments).map(y => {
29608
29772
  let endpoints = [[-5, y], [5, y]];
29609
- endpoints = normalizeCoords(endpoints, range);
29773
+ endpoints = normalizeCoords(endpoints, defaultRange);
29610
29774
  endpoints = normalizePoints(range, step, endpoints);
29611
29775
  return endpoints;
29612
29776
  });
@@ -29688,7 +29852,6 @@ const renderGraph = props => {
29688
29852
  };
29689
29853
  const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29690
29854
  const [width, height] = props.box;
29691
- const legacyGrid = getLegacyGrid([width, height], props.backgroundImage);
29692
29855
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
29693
29856
  React.useImperativeHandle(ref, () => ({
29694
29857
  getUserInput: () => getGradableGraph(state, props.graph)
@@ -29699,13 +29862,17 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29699
29862
  height,
29700
29863
  position: "relative"
29701
29864
  }
29702
- }, legacyGrid, /*#__PURE__*/React.createElement(View, {
29865
+ }, /*#__PURE__*/React.createElement(LegacyGrid, {
29866
+ box: props.box,
29867
+ backgroundImage: props.backgroundImage
29868
+ }), /*#__PURE__*/React.createElement(View, {
29703
29869
  style: {
29704
29870
  position: "absolute",
29705
29871
  bottom: 0,
29706
29872
  left: 0
29707
29873
  }
29708
29874
  }, /*#__PURE__*/React.createElement(Mafs, {
29875
+ preserveAspectRatio: false,
29709
29876
  viewBox: {
29710
29877
  x: props.range[0],
29711
29878
  y: props.range[1],
@@ -29715,7 +29882,7 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29715
29882
  zoom: false,
29716
29883
  width: width,
29717
29884
  height: height
29718
- }, !legacyGrid && /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
29885
+ }, /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
29719
29886
  lockedFigures: props.lockedFigures
29720
29887
  }), renderGraph({
29721
29888
  state,
@@ -32650,7 +32817,7 @@ const styles$g = StyleSheet.create({
32650
32817
  });
32651
32818
 
32652
32819
  const BringToFront = {
32653
- boxShadow: `0 8px 8px ${Color.offBlack64}`,
32820
+ boxShadow: `0 8px 8px ${color.offBlack64}`,
32654
32821
  zIndex: 1000
32655
32822
  };
32656
32823
  const AnswerPill = props => {
@@ -32703,7 +32870,7 @@ const styles$f = StyleSheet.create({
32703
32870
  backgroundColor: "#00880b"
32704
32871
  },
32705
32872
  incorrect: {
32706
- backgroundColor: Color.offBlack64
32873
+ backgroundColor: color.offBlack64
32707
32874
  },
32708
32875
  pill: {
32709
32876
  // Reset the Pill's default height in order to account
@@ -32746,7 +32913,7 @@ class Marker extends React.Component {
32746
32913
  // default dot
32747
32914
  let args = {
32748
32915
  size: MARKER_SIZE,
32749
- color: Color.white,
32916
+ color: color.white,
32750
32917
  icon: iconNull
32751
32918
  };
32752
32919
  if (showCorrectness) {
@@ -32809,7 +32976,7 @@ Marker.defaultProps = {
32809
32976
  const styles$e = StyleSheet.create({
32810
32977
  marker: {
32811
32978
  position: "absolute",
32812
- backgroundColor: Color.white,
32979
+ backgroundColor: color.white,
32813
32980
  borderRadius: MARKER_SIZE,
32814
32981
  // Center marker position based on its maximum size.
32815
32982
  width: MARKER_SIZE,
@@ -32817,7 +32984,7 @@ const styles$e = StyleSheet.create({
32817
32984
  marginLeft: MARKER_SIZE / -2,
32818
32985
  marginTop: MARKER_SIZE / -2,
32819
32986
  // Add a shadow to the marker to make it stand out from the image.
32820
- boxShadow: `0 8px 8px ${Color.offBlack8}`
32987
+ boxShadow: `0 8px 8px ${color.offBlack8}`
32821
32988
  },
32822
32989
  // The base and unfilled marker style.
32823
32990
  markerIcon: {
@@ -32827,7 +32994,7 @@ const styles$e = StyleSheet.create({
32827
32994
  boxSizing: "border-box",
32828
32995
  width: MARKER_SIZE,
32829
32996
  height: MARKER_SIZE,
32830
- border: `2px solid ${Color.offBlack64}`,
32997
+ border: `2px solid ${color.offBlack64}`,
32831
32998
  borderRadius: MARKER_SIZE
32832
32999
  },
32833
33000
  // The animation that presents the marker to the learner
@@ -32835,11 +33002,11 @@ const styles$e = StyleSheet.create({
32835
33002
  animationName: {
32836
33003
  "0%": {
32837
33004
  transform: "scale(1)",
32838
- backgroundColor: Color.blue
33005
+ backgroundColor: color.blue
32839
33006
  },
32840
33007
  "100%": {
32841
33008
  transform: "scale(1.3)",
32842
- backgroundColor: Color.blue
33009
+ backgroundColor: color.blue
32843
33010
  }
32844
33011
  },
32845
33012
  animationDirection: "alternate",
@@ -32856,35 +33023,35 @@ const styles$e = StyleSheet.create({
32856
33023
  animationIterationCount: "2"
32857
33024
  },
32858
33025
  markerActive: {
32859
- outline: `2px solid ${Color.blue}`,
33026
+ outline: `2px solid ${color.blue}`,
32860
33027
  outlineOffset: 2
32861
33028
  },
32862
33029
  // The learner is making an initial selection
32863
33030
  markerSelected: {
32864
- boxShadow: `0 8px 8px ${Color.offBlack8}`,
32865
- border: `solid 4px ${Color.white}`,
32866
- backgroundColor: Color.blue,
33031
+ boxShadow: `0 8px 8px ${color.offBlack8}`,
33032
+ border: `solid 4px ${color.white}`,
33033
+ backgroundColor: color.blue,
32867
33034
  borderRadius: MARKER_SIZE,
32868
33035
  transform: "rotate(180deg)"
32869
33036
  },
32870
33037
  // The learner has made a selection
32871
33038
  markerFilled: {
32872
33039
  backgroundColor: "#ECF3FE",
32873
- border: `4px solid ${Color.blue}`
33040
+ border: `4px solid ${color.blue}`
32874
33041
  },
32875
33042
  markerGraded: {
32876
33043
  width: MARKER_SIZE,
32877
33044
  height: MARKER_SIZE,
32878
33045
  justifyContent: "center",
32879
33046
  alignItems: "center",
32880
- border: `2px solid ${Color.white}`
33047
+ border: `2px solid ${color.white}`
32881
33048
  },
32882
33049
  markerCorrect: {
32883
33050
  background: "#00880b" // WB green darkened by 18%
32884
33051
  },
32885
33052
 
32886
33053
  markerIncorrect: {
32887
- background: Color.offBlack64
33054
+ background: color.offBlack64
32888
33055
  }
32889
33056
  });
32890
33057
 
@@ -33313,6 +33480,7 @@ class LabelImage extends React.Component {
33313
33480
  const answerChoicesActive = index === activeMarkerIndex;
33314
33481
  const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
33315
33482
  return /*#__PURE__*/React.createElement(View, {
33483
+ key: index,
33316
33484
  style: {
33317
33485
  position: "absolute",
33318
33486
  left: `${marker.x}%`,
@@ -37911,7 +38079,7 @@ const styles$8 = StyleSheet.create({
37911
38079
  // and the "Add highlight" action fails.
37912
38080
  userSelect: "none",
37913
38081
  fontFamily: `"Lato", sans-serif`,
37914
- padding: `10px ${Spacing.medium_16}px`,
38082
+ padding: `10px ${spacing.medium_16}px`,
37915
38083
  cursor: "pointer"
37916
38084
  }
37917
38085
  });
@@ -41594,7 +41762,7 @@ const VideoTranscriptLink = props => {
41594
41762
  const video = (_result$data = result.data) == null ? void 0 : _result$data.video;
41595
41763
  return /*#__PURE__*/React.createElement(View, {
41596
41764
  style: styles$4.transcriptLink
41597
- }, /*#__PURE__*/React.createElement(Text$1, null, video == null ? void 0 : video.title), /*#__PURE__*/React.createElement(Strut, {
41765
+ }, /*#__PURE__*/React.createElement(Text$2, null, video == null ? void 0 : video.title), /*#__PURE__*/React.createElement(Strut, {
41598
41766
  size: 10
41599
41767
  }), /*#__PURE__*/React.createElement(Link, {
41600
41768
  href: "/transcript/" + ((video == null ? void 0 : video.contentId) || "videoNotFound"),
@@ -41763,7 +41931,7 @@ const init = function init(options) {
41763
41931
 
41764
41932
  // This file is processed by a Rollup plugin (replace) to inject the production
41765
41933
  const libName = "@khanacademy/perseus";
41766
- const libVersion = "20.7.0";
41934
+ const libVersion = "20.8.1";
41767
41935
  addLibraryVersionToPerseusDebug(libName, libVersion);
41768
41936
 
41769
41937
  const apiVersion = {
@@ -44420,6 +44588,66 @@ function injectWidgets(content, widgets) {
44420
44588
  return context;
44421
44589
  }
44422
44590
 
44591
+ /* Widgets that have individual answers */
44592
+ const INDIVIDUAL_ANSWER_WIDGETS = ["interactive-graph", "categorizer", "grapher"];
44593
+
44594
+ /* Widgets that are supported for automatic scoring */
44595
+ const SUPPORTED_WIDGETS = ["radio", "numeric-input", "input-number", "expression", ...INDIVIDUAL_ANSWER_WIDGETS];
44596
+ const getWidgetTypeFromWidgetKey = widgetKey => {
44597
+ return widgetKey.split(" ")[0];
44598
+ };
44599
+
44600
+ /* Verify if the perseus item has supported widgets for automatic scoring */
44601
+ const isWrongAnswerSupported = widgetKeys => {
44602
+ return widgetKeys.length !== 0 && widgetKeys.every(widgetKey => SUPPORTED_WIDGETS.includes(getWidgetTypeFromWidgetKey(widgetKey)));
44603
+ };
44604
+
44605
+ /* Verify if the widget key has an individual answer for the coach report view */
44606
+ const shouldHaveIndividualAnswer = widgetKey => {
44607
+ return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeFromWidgetKey(widgetKey));
44608
+ };
44609
+
44610
+ /* Returns the answer userInput submission for currently supported widgets */
44611
+ // TODO (LEMS-1834): Fix userInput any types to be specific
44612
+ const getAnswerFromUserInput = (widgetType, userInput) => {
44613
+ switch (widgetType) {
44614
+ case "categorizer":
44615
+ return userInput.values;
44616
+ case "input-number":
44617
+ return userInput.currentValue;
44618
+ case "numeric-input":
44619
+ return userInput.currentValue;
44620
+ case "radio":
44621
+ return userInput.choicesSelected;
44622
+ }
44623
+ return userInput;
44624
+ };
44625
+
44626
+ /* Returns the correct answer for a given widget key and Perseus Item */
44627
+ // TODO (LEMS-1835): We should fix the resonse type from getWidget to be specific.
44628
+ // TODO (LEMS-1836): We should also consider adding the getOneCorrectAnswerFromRubric method to all widgets.
44629
+ const getCorrectAnswerForWidgetKey = (widgetKey, itemData) => {
44630
+ const rubric = itemData.question.widgets[widgetKey].options;
44631
+ const widgetType = getWidgetTypeFromWidgetKey(widgetKey);
44632
+ const widget = getWidget(widgetType);
44633
+
44634
+ // @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
44635
+ return widget == null || widget.getOneCorrectAnswerFromRubric == null ? void 0 : widget.getOneCorrectAnswerFromRubric(rubric);
44636
+ };
44637
+
44638
+ /* Verify if the widget key exists in the content string of the Perseus Item */
44639
+ const isWidgetKeyInContent = (perseusItem, widgetKey) => {
44640
+ return perseusItem.question.content.indexOf(widgetKey) !== -1;
44641
+ };
44642
+
44643
+ /* Return an array of all the widget keys that exist in the content string of a Perseus Item */
44644
+ const getValidWidgetKeys = perseusItem => {
44645
+ const {
44646
+ widgets
44647
+ } = perseusItem.question;
44648
+ return keys(widgets).filter(key => isWidgetKeyInContent(perseusItem, key));
44649
+ };
44650
+
44423
44651
  /**
44424
44652
  * Helper to parse PerseusItem JSON
44425
44653
  * Why not just use JSON.parse? We want:
@@ -44591,15 +44819,14 @@ function getWidgetRegex() {
44591
44819
  }
44592
44820
 
44593
44821
  /**
44594
- * Add a widget placeholder using the provided widget type and instance number.
44595
- * ex. addWidget(WidgetType.Radio, 1) => "[[☃ radio 1]]"
44822
+ * Add a widget placeholder using the widget ID.
44823
+ * ex. addWidget("radio 1") => "[[☃ radio 1]]"
44596
44824
  *
44597
- * @param {WidgetType} widgetType
44598
- * @param {number} instance
44825
+ * @param {string} id
44599
44826
  * @returns {string}
44600
44827
  */
44601
- function addWidget(widgetType, instance) {
44602
- return `[[☃ ${widgetType} ${String(instance)}]]`;
44828
+ function addWidget(id) {
44829
+ return `[[☃ ${id}]]`;
44603
44830
  }
44604
44831
 
44605
44832
  /**
@@ -44691,5 +44918,5 @@ const EditorJsonify = {
44691
44918
  }
44692
44919
  };
44693
44920
 
44694
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, WidgetType, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, convertWidgetNameToEnum, displaySigFigs, generateTestPerseusItem, genericPerseusItemData, getAllWidgetIds, getAllWidgetTypes, getAnswersFromWidgets, getQuestionWidgetIds, getWidgetTypeFromWidgetId, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, traverse, violatingWidgets, allWidgets as widgets };
44921
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, WidgetType, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, convertWidgetNameToEnum, displaySigFigs, generateTestPerseusItem, genericPerseusItemData, getAllWidgetIds, getAllWidgetTypes, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetKey, getQuestionWidgetIds, getValidWidgetKeys, getWidgetTypeFromWidgetId, getWidgetTypeFromWidgetKey, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetKeyInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, violatingWidgets, allWidgets as widgets };
44695
44922
  //# sourceMappingURL=index.js.map