@khanacademy/perseus 30.0.1 → 31.0.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/index.js CHANGED
@@ -41,6 +41,8 @@ var Switch = require('@khanacademy/wonder-blocks-switch');
41
41
  var Pill = require('@khanacademy/wonder-blocks-pill');
42
42
  var reactPopper = require('react-popper');
43
43
  var createReactClass = require('create-react-class');
44
+ var IconButton = require('@khanacademy/wonder-blocks-icon-button');
45
+ var cornersOutIcon = require('@phosphor-icons/core/regular/corners-out.svg');
44
46
  var Link = require('@khanacademy/wonder-blocks-link');
45
47
 
46
48
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -85,6 +87,8 @@ var questionIcon__default = /*#__PURE__*/_interopDefaultLegacy(questionIcon);
85
87
  var Switch__default = /*#__PURE__*/_interopDefaultLegacy(Switch);
86
88
  var Pill__default = /*#__PURE__*/_interopDefaultLegacy(Pill);
87
89
  var createReactClass__default = /*#__PURE__*/_interopDefaultLegacy(createReactClass);
90
+ var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
91
+ var cornersOutIcon__default = /*#__PURE__*/_interopDefaultLegacy(cornersOutIcon);
88
92
  var Link__default = /*#__PURE__*/_interopDefaultLegacy(Link);
89
93
 
90
94
  function _extends() {
@@ -360,7 +364,9 @@ const mockStrings = {
360
364
  mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
361
365
  sin: "sin",
362
366
  cos: "cos",
363
- tan: "tan"
367
+ tan: "tan",
368
+ simulationLoadFail: "Sorry, this simulation cannot load.",
369
+ simulationLocaleWarning: "Sorry, this simulation isn't available in your language."
364
370
  };
365
371
 
366
372
  /**
@@ -619,7 +625,7 @@ class InnerMathInput extends React__namespace.Component {
619
625
  className = className + " " + this.props.className;
620
626
  }
621
627
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
622
- style: [styles$D.outerWrapper, this.state.focused && styles$D.wrapperFocused, this.props.hasError && styles$D.wrapperError]
628
+ style: [styles$E.outerWrapper, this.state.focused && styles$E.wrapperFocused, this.props.hasError && styles$E.wrapperError]
623
629
  }, /*#__PURE__*/React__namespace.createElement("div", {
624
630
  style: {
625
631
  display: "flex",
@@ -654,7 +660,7 @@ class InnerMathInput extends React__namespace.Component {
654
660
  style: a11y.srOnly
655
661
  }, this.context.strings.mathInputDescription), /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
656
662
  closeButtonVisible: true,
657
- style: styles$D.popoverContent
663
+ style: styles$E.popoverContent
658
664
  }, /*#__PURE__*/React__namespace.createElement(mathInput.DesktopKeypad, _extends({
659
665
  onAnalyticsEvent: this.props.analytics.onAnalyticsEvent,
660
666
  extraKeys: this.props.extraKeys,
@@ -719,9 +725,9 @@ const MathInputIcon = _ref => {
719
725
  fillColor = wonderBlocksTokens.color.offBlack;
720
726
  break;
721
727
  }
722
- const dynamicClass = active || focused ? styles$D.iconActive : styles$D.iconInactive;
728
+ const dynamicClass = active || focused ? styles$E.iconActive : styles$E.iconInactive;
723
729
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
724
- style: [styles$D.iconContainer, dynamicClass]
730
+ style: [styles$E.iconContainer, dynamicClass]
725
731
  }, /*#__PURE__*/React__namespace.createElement("svg", {
726
732
  xmlns: "http://www.w3.org/2000/svg",
727
733
  width: "16",
@@ -766,7 +772,7 @@ const inputFocused = {
766
772
  borderColor: wonderBlocksTokens.color.blue,
767
773
  margin: -1
768
774
  };
769
- const styles$D = aphrodite.StyleSheet.create({
775
+ const styles$E = aphrodite.StyleSheet.create({
770
776
  iconContainer: {
771
777
  display: "flex",
772
778
  justifyContent: "center",
@@ -2601,7 +2607,7 @@ class Expression extends React__namespace.Component {
2601
2607
  render() {
2602
2608
  if (this.props.apiOptions.customKeypad) {
2603
2609
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
2604
- className: aphrodite.css(styles$C.mobileLabelInputWrapper)
2610
+ className: aphrodite.css(styles$D.mobileLabelInputWrapper)
2605
2611
  }, !!this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
2606
2612
  htmlFor: this._textareaId,
2607
2613
  tag: "label"
@@ -2635,7 +2641,7 @@ class Expression extends React__namespace.Component {
2635
2641
  ERROR_TITLE
2636
2642
  } = this.context.strings;
2637
2643
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
2638
- className: aphrodite.css(styles$C.desktopLabelInputWrapper)
2644
+ className: aphrodite.css(styles$D.desktopLabelInputWrapper)
2639
2645
  }, !!this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
2640
2646
  htmlFor: this._textareaId,
2641
2647
  tag: "label"
@@ -2676,7 +2682,7 @@ class Expression extends React__namespace.Component {
2676
2682
  }))));
2677
2683
  }
2678
2684
  }
2679
- const styles$C = aphrodite.StyleSheet.create({
2685
+ const styles$D = aphrodite.StyleSheet.create({
2680
2686
  mobileLabelInputWrapper: {
2681
2687
  padding: "15px 4px 0"
2682
2688
  },
@@ -18371,33 +18377,33 @@ class Lint extends React__namespace.Component {
18371
18377
  let warningText;
18372
18378
  let warningTextStyle;
18373
18379
  if (this.props.severity === Severity.Error) {
18374
- severityStyle = styles$B.indicatorError;
18380
+ severityStyle = styles$C.indicatorError;
18375
18381
  warningText = "Error";
18376
- warningTextStyle = styles$B.publishBlockingError;
18382
+ warningTextStyle = styles$C.publishBlockingError;
18377
18383
  } else if (this.props.severity === Severity.Warning) {
18378
- severityStyle = styles$B.indicatorWarning;
18384
+ severityStyle = styles$C.indicatorWarning;
18379
18385
  warningText = "Warning";
18380
- warningTextStyle = styles$B.warning;
18386
+ warningTextStyle = styles$C.warning;
18381
18387
  } else {
18382
- severityStyle = styles$B.indicatorGuideline;
18388
+ severityStyle = styles$C.indicatorGuideline;
18383
18389
  warningText = "Recommendation";
18384
- warningTextStyle = styles$B.warning;
18390
+ warningTextStyle = styles$C.warning;
18385
18391
  }
18386
18392
  return /*#__PURE__*/React__namespace.createElement("a", {
18387
18393
  href: `https://khanacademy.org/r/linter-rules#${this.props.ruleName}`,
18388
18394
  target: "lint-help-window",
18389
18395
  className: aphrodite.css(style)
18390
18396
  }, /*#__PURE__*/React__namespace.createElement("span", {
18391
- className: aphrodite.css(styles$B.indicator, severityStyle)
18397
+ className: aphrodite.css(styles$C.indicator, severityStyle)
18392
18398
  }, this.props.severity === 1 && /*#__PURE__*/React__namespace.createElement(InlineIcon, exclamationIcon)), /*#__PURE__*/React__namespace.createElement("div", {
18393
- className: aphrodite.css(styles$B.tooltip, tooltipAbove && styles$B.tooltipAbove)
18399
+ className: aphrodite.css(styles$C.tooltip, tooltipAbove && styles$C.tooltipAbove)
18394
18400
  }, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React__namespace.createElement("p", {
18395
18401
  key: i,
18396
- className: aphrodite.css(styles$B.tooltipParagraph)
18402
+ className: aphrodite.css(styles$C.tooltipParagraph)
18397
18403
  }, /*#__PURE__*/React__namespace.createElement("span", {
18398
18404
  className: aphrodite.css(warningTextStyle)
18399
18405
  }, warningText, ":", " "), m)), /*#__PURE__*/React__namespace.createElement("div", {
18400
- className: aphrodite.css(styles$B.tail, tooltipAbove && styles$B.tailAbove)
18406
+ className: aphrodite.css(styles$C.tail, tooltipAbove && styles$C.tailAbove)
18401
18407
  })));
18402
18408
  };
18403
18409
 
@@ -18425,20 +18431,20 @@ class Lint extends React__namespace.Component {
18425
18431
  }
18426
18432
  if (blockHighlight) {
18427
18433
  return /*#__PURE__*/React__namespace.createElement("span", {
18428
- className: aphrodite.css(styles$B.lintContainer, styles$B.lintContainerBlock)
18429
- }, this.renderLink(styles$B.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18434
+ className: aphrodite.css(styles$C.lintContainer, styles$C.lintContainerBlock)
18435
+ }, this.renderLink(styles$C.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18430
18436
  }
18431
18437
  if (inline) {
18432
18438
  return /*#__PURE__*/React__namespace.createElement("span", {
18433
- className: aphrodite.css(styles$B.lintContainer)
18434
- }, this.renderLink(styles$B.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18439
+ className: aphrodite.css(styles$C.lintContainer)
18440
+ }, this.renderLink(styles$C.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18435
18441
  }
18436
18442
  return /*#__PURE__*/React__namespace.createElement("div", {
18437
- className: aphrodite.css(styles$B.lintContainer)
18438
- }, this.renderLink(styles$B.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
18443
+ className: aphrodite.css(styles$C.lintContainer)
18444
+ }, this.renderLink(styles$C.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
18439
18445
  }
18440
18446
  }
18441
- const styles$B = aphrodite.StyleSheet.create({
18447
+ const styles$C = aphrodite.StyleSheet.create({
18442
18448
  // This is the class of the outermost element.
18443
18449
  // We use relative positioning so that the lint indicator can be
18444
18450
  // positioned absolutely relative to the position of the linty container.
@@ -20572,7 +20578,7 @@ class Renderer extends React__namespace.Component {
20572
20578
  };
20573
20579
  const mobileInnerStyle = {
20574
20580
  ...innerStyle,
20575
- ...styles$A.mobileZoomableParentFix
20581
+ ...styles$B.mobileZoomableParentFix
20576
20582
  };
20577
20583
  return /*#__PURE__*/React__namespace.createElement("div", {
20578
20584
  key: state.key,
@@ -20712,7 +20718,7 @@ class Renderer extends React__namespace.Component {
20712
20718
  };
20713
20719
  const mobileInnerStyle = {
20714
20720
  ...innerStyle,
20715
- ...styles$A.mobileZoomableParentFix
20721
+ ...styles$B.mobileZoomableParentFix
20716
20722
  };
20717
20723
  const wrappedOutput = /*#__PURE__*/React__namespace.createElement("div", {
20718
20724
  style: {
@@ -21220,7 +21226,7 @@ class Renderer extends React__namespace.Component {
21220
21226
  return this.lastRenderedMarkdown;
21221
21227
  }
21222
21228
  }
21223
- const styles$A = {
21229
+ const styles$B = {
21224
21230
  mobileZoomableParentFix: {
21225
21231
  // NOTE(abdul): There is an issue where transform animations will
21226
21232
  // cause the Zoomable component to disappear when running on the
@@ -22016,10 +22022,10 @@ class InputNumber extends React__namespace.Component {
22016
22022
  // Note: This is _very_ similar to what `numeric-input.jsx` does. If
22017
22023
  // you modify this, double-check if you also need to modify that
22018
22024
  // component.
22019
- const inputStyles = [styles$z.default, this.props.size === "small" ? styles$z.small : null, this.props.rightAlign ? styles$z.rightAlign : styles$z.leftAlign];
22025
+ const inputStyles = [styles$A.default, this.props.size === "small" ? styles$A.small : null, this.props.rightAlign ? styles$A.rightAlign : styles$A.leftAlign];
22020
22026
  // Unanswered
22021
22027
  if (rubric && !this.props.currentValue) {
22022
- inputStyles.push(styles$z.answerStateUnanswered);
22028
+ inputStyles.push(styles$A.answerStateUnanswered);
22023
22029
  }
22024
22030
  return /*#__PURE__*/React__namespace.createElement(InputWithExamples
22025
22031
  // eslint-disable-next-line react/no-string-refs
@@ -22159,7 +22165,7 @@ class InputNumber extends React__namespace.Component {
22159
22165
  return answerString;
22160
22166
  }
22161
22167
  }
22162
- const styles$z = aphrodite.StyleSheet.create({
22168
+ const styles$A = aphrodite.StyleSheet.create({
22163
22169
  default: {
22164
22170
  width: 80,
22165
22171
  height: "auto"
@@ -23152,7 +23158,7 @@ const FocusRing = function (props) {
23152
23158
  };
23153
23159
  return /*#__PURE__*/React__namespace.createElement("span", {
23154
23160
  "data-testid": "focus-ring",
23155
- className: aphrodite.css(styles$y.ring),
23161
+ className: aphrodite.css(styles$z.ring),
23156
23162
  style: style
23157
23163
  }, children);
23158
23164
  };
@@ -23161,7 +23167,7 @@ FocusRing.defaultProps = {
23161
23167
  color: kaGreen$1,
23162
23168
  multipleSelect: false
23163
23169
  };
23164
- const styles$y = aphrodite.StyleSheet.create({
23170
+ const styles$z = aphrodite.StyleSheet.create({
23165
23171
  ring: {
23166
23172
  margin: "auto",
23167
23173
  display: "inline-block",
@@ -23188,7 +23194,7 @@ function getChoiceLetter(pos, strings) {
23188
23194
  }
23189
23195
 
23190
23196
  const CHOICE_ICON_SIZE = 24;
23191
- const styles$x = aphrodite.StyleSheet.create({
23197
+ const styles$y = aphrodite.StyleSheet.create({
23192
23198
  iconWrapper: {
23193
23199
  display: "inline-block",
23194
23200
  position: "relative"
@@ -23211,7 +23217,7 @@ const CrossOutLine = function (props) {
23211
23217
  width: CROSS_OUT_LINE_SIZE,
23212
23218
  height: CROSS_OUT_LINE_SIZE,
23213
23219
  viewBox: `0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`,
23214
- className: aphrodite.css(styles$w.crossOutLine),
23220
+ className: aphrodite.css(styles$x.crossOutLine),
23215
23221
  "data-testid": "choice-icon__cross-out-line"
23216
23222
  }, /*#__PURE__*/React__namespace.createElement("line", {
23217
23223
  // The line stretches from the bottom-left to top-right.
@@ -23226,7 +23232,7 @@ const CrossOutLine = function (props) {
23226
23232
  strokeLinecap: "round"
23227
23233
  }));
23228
23234
  };
23229
- const styles$w = aphrodite.StyleSheet.create({
23235
+ const styles$x = aphrodite.StyleSheet.create({
23230
23236
  crossOutLine: {
23231
23237
  // Center the icon within the container.
23232
23238
  position: "absolute",
@@ -23310,7 +23316,7 @@ const ChoiceIcon = function (props) {
23310
23316
  } = props;
23311
23317
  const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct);
23312
23318
  return /*#__PURE__*/React__namespace.createElement("div", {
23313
- className: aphrodite.css(styles$x.iconWrapper)
23319
+ className: aphrodite.css(styles$y.iconWrapper)
23314
23320
  }, /*#__PURE__*/React__namespace.createElement(FocusRing, {
23315
23321
  color: wonderBlocksTokens.color.blue,
23316
23322
  visible: focused || hovered,
@@ -23319,13 +23325,13 @@ const ChoiceIcon = function (props) {
23319
23325
  // @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
23320
23326
  style: dynamicStyles,
23321
23327
  "data-testid": "choice-icon__library-choice-icon",
23322
- className: aphrodite.css(styles$v.circle, showCorrectness && correct && styles$v.circleCorrect, showCorrectness && !correct && styles$v.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$v.circleIncorrectAnswered)
23328
+ className: aphrodite.css(styles$w.circle, showCorrectness && correct && styles$w.circleCorrect, showCorrectness && !correct && styles$w.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$w.circleIncorrectAnswered)
23323
23329
  // used in BaseRadio editMode to check
23324
23330
  // if we actually clicked on the radio icon
23325
23331
  ,
23326
23332
  "data-is-radio-icon": true
23327
23333
  }, /*#__PURE__*/React__namespace.createElement("div", {
23328
- className: aphrodite.css(styles$v.innerWrapper)
23334
+ className: aphrodite.css(styles$w.innerWrapper)
23329
23335
  }, /*#__PURE__*/React__namespace.createElement(ChoiceInner, {
23330
23336
  pos: pos,
23331
23337
  showCorrectness: showCorrectness,
@@ -23334,7 +23340,7 @@ const ChoiceIcon = function (props) {
23334
23340
  color: dynamicStyles.borderColor
23335
23341
  }));
23336
23342
  };
23337
- const styles$v = aphrodite.StyleSheet.create({
23343
+ const styles$w = aphrodite.StyleSheet.create({
23338
23344
  innerWrapper: {
23339
23345
  width: CHOICE_ICON_SIZE,
23340
23346
  height: CHOICE_ICON_SIZE,
@@ -23429,19 +23435,19 @@ const OptionStatus = function (props) {
23429
23435
  }
23430
23436
  let textStyle;
23431
23437
  if (correct) {
23432
- textStyle = styles$u.correct;
23438
+ textStyle = styles$v.correct;
23433
23439
  } else {
23434
23440
  if (checked || previouslyAnswered) {
23435
- textStyle = styles$u.incorrectAnswered;
23441
+ textStyle = styles$v.incorrectAnswered;
23436
23442
  } else {
23437
- textStyle = styles$u.incorrect;
23443
+ textStyle = styles$v.incorrect;
23438
23444
  }
23439
23445
  }
23440
23446
  return /*#__PURE__*/React__namespace.createElement("div", {
23441
- className: aphrodite.css(styles$u.text, textStyle)
23447
+ className: aphrodite.css(styles$v.text, textStyle)
23442
23448
  }, renderText(checked, correct, crossedOut, strings));
23443
23449
  };
23444
- const styles$u = aphrodite.StyleSheet.create({
23450
+ const styles$v = aphrodite.StyleSheet.create({
23445
23451
  text: {
23446
23452
  alignItems: "center",
23447
23453
  display: "flex",
@@ -23510,8 +23516,8 @@ const Choice = function (props) {
23510
23516
  crossedOut: updatedCrossedOut
23511
23517
  });
23512
23518
  }
23513
- const descriptionClassName = classNames__default["default"]("description", aphrodite.css(styles$t.description));
23514
- const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$t.rationale));
23519
+ const descriptionClassName = classNames__default["default"]("description", aphrodite.css(styles$u.description));
23520
+ const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$u.rationale));
23515
23521
 
23516
23522
  // We want to show the choices as dimmed out when the choices are disabled.
23517
23523
  // However, we don't want to do this when we're in review mode in the
@@ -23678,7 +23684,7 @@ const Choice = function (props) {
23678
23684
  "data-testid": `perseus-radio-rationale-content-${pos}`
23679
23685
  }, rationale));
23680
23686
  };
23681
- const styles$t = aphrodite.StyleSheet.create({
23687
+ const styles$u = aphrodite.StyleSheet.create({
23682
23688
  description: {
23683
23689
  display: "inline-block",
23684
23690
  width: "100%"
@@ -23877,10 +23883,10 @@ const BaseRadio = function (props) {
23877
23883
  const isMobile = apiOptions.isMobile;
23878
23884
  const firstChoiceHighlighted = choices[0].highlighted;
23879
23885
  const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
23880
- const className = classNames__default["default"]("perseus-widget-radio", !editMode && "perseus-rendered-radio", aphrodite.css(styles$s.radio, styles$s.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$s.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$s.radioContainerLastHighlighted));
23881
- const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$s.instructions, isMobile && styles$s.instructionsMobile));
23886
+ const className = classNames__default["default"]("perseus-widget-radio", !editMode && "perseus-rendered-radio", aphrodite.css(styles$t.radio, styles$t.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$t.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$t.radioContainerLastHighlighted));
23887
+ const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$t.instructions, isMobile && styles$t.instructionsMobile));
23882
23888
  const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect, strings);
23883
- const responsiveClassName = aphrodite.css(styles$s.responsiveFieldset);
23889
+ const responsiveClassName = aphrodite.css(styles$t.responsiveFieldset);
23884
23890
  const fieldset = /*#__PURE__*/React__namespace.createElement("fieldset", {
23885
23891
  className: `perseus-widget-radio-fieldset ${responsiveClassName}`
23886
23892
  }, /*#__PURE__*/React__namespace.createElement("legend", {
@@ -23931,7 +23937,7 @@ const BaseRadio = function (props) {
23931
23937
  const aphroditeClassName = checked => {
23932
23938
  // Whether or not to show correctness borders
23933
23939
  // for this choice and the next choice.
23934
- return aphrodite.css(sharedStyles.aboveScratchpad, styles$s.item, styles$s.responsiveItem, checked && styles$s.selectedItem, checked && choice.highlighted && styles$s.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$s.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$s.nextHighlighted);
23940
+ return aphrodite.css(sharedStyles.aboveScratchpad, styles$t.item, styles$t.responsiveItem, checked && styles$t.selectedItem, checked && choice.highlighted && styles$t.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$t.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$t.nextHighlighted);
23935
23941
  };
23936
23942
 
23937
23943
  // HACK(abdulrahman): Preloads the selection-state
@@ -24005,14 +24011,14 @@ const BaseRadio = function (props) {
24005
24011
  // Allow for horizontal scrolling if content is too wide, which may be
24006
24012
  // an issue especially on phones.
24007
24013
  return /*#__PURE__*/React__namespace.createElement("div", {
24008
- className: aphrodite.css(styles$s.responsiveContainer)
24014
+ className: aphrodite.css(styles$t.responsiveContainer)
24009
24015
  }, fieldset);
24010
24016
  };
24011
24017
  BaseRadio.defaultProps = {
24012
24018
  editMode: false,
24013
24019
  multipleSelect: false
24014
24020
  };
24015
- const styles$s = aphrodite.StyleSheet.create({
24021
+ const styles$t = aphrodite.StyleSheet.create({
24016
24022
  instructions: {
24017
24023
  display: "block",
24018
24024
  color: gray17,
@@ -24696,9 +24702,9 @@ class Categorizer extends React__namespace.Component {
24696
24702
  indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
24697
24703
  }
24698
24704
  const table = /*#__PURE__*/React__namespace.createElement("table", {
24699
- className: "categorizer-table " + aphrodite.css(styles$r.mobileTable)
24705
+ className: "categorizer-table " + aphrodite.css(styles$s.mobileTable)
24700
24706
  }, /*#__PURE__*/React__namespace.createElement("thead", null, /*#__PURE__*/React__namespace.createElement("tr", null, /*#__PURE__*/React__namespace.createElement("td", {
24701
- className: aphrodite.css(styles$r.emptyHeaderCell)
24707
+ className: aphrodite.css(styles$s.emptyHeaderCell)
24702
24708
  }), this.props.categories.map((category, i) => {
24703
24709
  // Array index is the correct key here, as that's
24704
24710
  // how category grading actually works -- no way
@@ -24706,7 +24712,7 @@ class Categorizer extends React__namespace.Component {
24706
24712
  // middle. (If we later add that, this should be
24707
24713
  // fixed.)
24708
24714
  return /*#__PURE__*/React__namespace.createElement("th", {
24709
- className: aphrodite.css(styles$r.header),
24715
+ className: aphrodite.css(styles$s.header),
24710
24716
  key: i
24711
24717
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
24712
24718
  content: category,
@@ -24728,7 +24734,7 @@ class Categorizer extends React__namespace.Component {
24728
24734
  })), self.props.categories.map((catName, catNum) => {
24729
24735
  const selected = self.props.values[itemNum] === catNum;
24730
24736
  return /*#__PURE__*/React__namespace.createElement("td", {
24731
- className: "category " + aphrodite.css(styles$r.cell),
24737
+ className: "category " + aphrodite.css(styles$s.cell),
24732
24738
  key: catNum
24733
24739
  }, /*#__PURE__*/React__namespace.createElement("div", {
24734
24740
  className: ClassNames.INTERACTIVE,
@@ -24743,7 +24749,7 @@ class Categorizer extends React__namespace.Component {
24743
24749
  onChange: () => this.onChange(itemNum, catNum),
24744
24750
  onClick: e => e.stopPropagation()
24745
24751
  }), !isMobile && /*#__PURE__*/React__namespace.createElement("span", {
24746
- className: aphrodite.css(styles$r.radioSpan, selected && styles$r.checkedRadioSpan, this.props.static && selected && styles$r.staticCheckedRadioSpan)
24752
+ className: aphrodite.css(styles$s.radioSpan, selected && styles$s.checkedRadioSpan, this.props.static && selected && styles$s.staticCheckedRadioSpan)
24747
24753
  }, selected ? /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircle) : /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircleThin))));
24748
24754
  }));
24749
24755
  /* eslint-enable max-len */
@@ -24755,7 +24761,7 @@ class Categorizer extends React__namespace.Component {
24755
24761
  "categorizer-container": true,
24756
24762
  "static-mode": this.props.static
24757
24763
  });
24758
- const inlineStyles = this.props.apiOptions.isMobile ? [styles$r.fullBleedContainer] : [];
24764
+ const inlineStyles = this.props.apiOptions.isMobile ? [styles$s.fullBleedContainer] : [];
24759
24765
  return /*#__PURE__*/React__namespace.createElement("div", {
24760
24766
  className: extraClassNames + " " + aphrodite.css(...inlineStyles)
24761
24767
  }, table);
@@ -24804,7 +24810,7 @@ const pageMargin = 16;
24804
24810
 
24805
24811
  // Stylesheets aren't directly testable
24806
24812
  /* c8 ignore next */
24807
- const styles$r = aphrodite.StyleSheet.create({
24813
+ const styles$s = aphrodite.StyleSheet.create({
24808
24814
  mobileTable: {
24809
24815
  [mediaQueries.smOrSmaller]: {
24810
24816
  minWidth: "auto"
@@ -25067,7 +25073,7 @@ class CSProgram extends React__namespace.Component {
25067
25073
  // creator "went wild".
25068
25074
  // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
25069
25075
  return /*#__PURE__*/React__namespace.createElement("div", {
25070
- className: aphrodite.css(styles$q.widthOverride, styleContainer && styles$q.container)
25076
+ className: aphrodite.css(styles$r.widthOverride, styleContainer && styles$r.container)
25071
25077
  }, /*#__PURE__*/React__namespace.createElement("iframe", {
25072
25078
  sandbox: sandboxOptions,
25073
25079
  src: url,
@@ -25077,7 +25083,7 @@ class CSProgram extends React__namespace.Component {
25077
25083
  }));
25078
25084
  }
25079
25085
  }
25080
- const styles$q = aphrodite.StyleSheet.create({
25086
+ const styles$r = aphrodite.StyleSheet.create({
25081
25087
  // Override the inherited width from the perseus paragraph class
25082
25088
  // 820 is minimum width for 420px editor and 400px canvas
25083
25089
  widthOverride: {
@@ -25124,7 +25130,7 @@ class Definition extends React__namespace.Component {
25124
25130
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.Popover, {
25125
25131
  content: /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
25126
25132
  color: "white",
25127
- style: styles$p.tooltipBody,
25133
+ style: styles$q.tooltipBody,
25128
25134
  closeButtonVisible: true
25129
25135
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
25130
25136
  apiOptions: this.props.apiOptions,
@@ -25156,7 +25162,7 @@ class Definition extends React__namespace.Component {
25156
25162
  });
25157
25163
  }
25158
25164
  }
25159
- const styles$p = {
25165
+ const styles$q = {
25160
25166
  tooltipBody: {
25161
25167
  color: wonderBlocksTokens.color.offBlack,
25162
25168
  fontSize: 20,
@@ -25383,7 +25389,7 @@ class Explanation extends React__namespace.Component {
25383
25389
  marginRight: "-6px",
25384
25390
  "white-space": "normal"
25385
25391
  };
25386
- const contentStyling = [styles$o.content, this.state.expanded ? styles$o.contentExpanded : styles$o.contentCollapsed, allowTransition && (this.state.expanded ? styles$o.transitionExpanded : styles$o.transitionCollapsed)];
25392
+ const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
25387
25393
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.UniqueIDProvider, {
25388
25394
  mockOnFirstRender: true,
25389
25395
  scope: "explanation-widget"
@@ -25402,7 +25408,7 @@ class Explanation extends React__namespace.Component {
25402
25408
  "aria-hidden": !this.state.expanded,
25403
25409
  testId: "content-container"
25404
25410
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25405
- style: styles$o.contentWrapper
25411
+ style: styles$p.contentWrapper
25406
25412
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
25407
25413
  apiOptions: this.props.apiOptions,
25408
25414
  content: this.props.explanation,
@@ -25421,7 +25427,7 @@ class Explanation extends React__namespace.Component {
25421
25427
  const leftBorderSpacing = 23;
25422
25428
  const verticalContentPadding = 10;
25423
25429
  const arrowHeight = 14;
25424
- const styles$o = aphrodite.StyleSheet.create({
25430
+ const styles$p = aphrodite.StyleSheet.create({
25425
25431
  content: {
25426
25432
  borderLeft: "0px solid #ccc",
25427
25433
  display: "inline-grid",
@@ -25487,15 +25493,15 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25487
25493
  nextQuestion
25488
25494
  } = this.context.strings;
25489
25495
  const answerBarStyle = {
25490
- ...styles$n.answerBar,
25496
+ ...styles$o.answerBar,
25491
25497
  backgroundColor: answerBarState === "CORRECT" ? wonderBlocksTokens.color.offWhite : "white",
25492
25498
  // Center the "Correct!" message only when there's no next question
25493
25499
  justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
25494
25500
  };
25495
25501
  const message = answerBarState === "INCORRECT" ? /*#__PURE__*/React__namespace.createElement("span", {
25496
- style: styles$n.text
25502
+ style: styles$o.text
25497
25503
  }, /*#__PURE__*/React__namespace.createElement("span", {
25498
- style: styles$n.tryAgainIcon
25504
+ style: styles$o.tryAgainIcon
25499
25505
  }, /*#__PURE__*/React__namespace.createElement(InlineIcon, iconTryAgain)), /*#__PURE__*/React__namespace.createElement("span", {
25500
25506
  style: {
25501
25507
  marginLeft: 8
@@ -25514,7 +25520,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25514
25520
  return /*#__PURE__*/React__namespace.createElement("div", {
25515
25521
  style: answerBarStyle
25516
25522
  }, /*#__PURE__*/React__namespace.createElement("span", {
25517
- style: styles$n.text
25523
+ style: styles$o.text
25518
25524
  }, /*#__PURE__*/React__namespace.createElement("span", {
25519
25525
  style: {
25520
25526
  fontSize: 28,
@@ -25536,7 +25542,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25536
25542
  }
25537
25543
  }
25538
25544
  const fontSize = 17;
25539
- const styles$n = {
25545
+ const styles$o = {
25540
25546
  answerBar: {
25541
25547
  display: "flex",
25542
25548
  alignItems: "center",
@@ -25736,7 +25742,7 @@ class GradedGroup extends React__namespace.Component {
25736
25742
  }));
25737
25743
  gradeStatus = this.context.strings.incorrect;
25738
25744
  }
25739
- const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$m.gradedGroupInSet) : aphrodite.css(styles$m.gradedGroup);
25745
+ const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$n.gradedGroupInSet) : aphrodite.css(styles$n.gradedGroup);
25740
25746
  const classes = classNames__default["default"]({
25741
25747
  [mobileClass]: apiOptions.isMobile,
25742
25748
  "perseus-graded-group": true,
@@ -25755,7 +25761,7 @@ class GradedGroup extends React__namespace.Component {
25755
25761
  return /*#__PURE__*/React__namespace.createElement("div", {
25756
25762
  className: classes
25757
25763
  }, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
25758
- className: aphrodite.css(styles$m.title)
25764
+ className: aphrodite.css(styles$n.title)
25759
25765
  }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
25760
25766
  // eslint-disable-next-line react/no-string-refs
25761
25767
  ref: "renderer",
@@ -25789,7 +25795,7 @@ class GradedGroup extends React__namespace.Component {
25789
25795
  }, this.context.strings.nextQuestion), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("button", {
25790
25796
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
25791
25797
  tabIndex: "0",
25792
- className: aphrodite.css(styles$m.explanationTitle),
25798
+ className: aphrodite.css(styles$n.explanationTitle),
25793
25799
  onClick: () => this.setState({
25794
25800
  showHint: false
25795
25801
  }),
@@ -25822,7 +25828,7 @@ class GradedGroup extends React__namespace.Component {
25822
25828
  showHint: true
25823
25829
  });
25824
25830
  },
25825
- className: aphrodite.css(styles$m.showHintLink)
25831
+ className: aphrodite.css(styles$n.showHintLink)
25826
25832
  }, this.context.strings.explain)), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React__namespace.createElement(GradedGroupAnswerBar, {
25827
25833
  apiOptions: apiOptions,
25828
25834
  answerBarState: answerBarState,
@@ -25834,7 +25840,7 @@ class GradedGroup extends React__namespace.Component {
25834
25840
  const traverseChildWidgets$2 = function (props, traverseRenderer) {
25835
25841
  return ___default["default"].extend({}, props, traverseRenderer(props));
25836
25842
  };
25837
- const styles$m = aphrodite.StyleSheet.create({
25843
+ const styles$n = aphrodite.StyleSheet.create({
25838
25844
  gradedGroupInSet: {
25839
25845
  // Reset a few desktop-only styles that come from graded-group.less
25840
25846
  marginLeft: 0,
@@ -25905,20 +25911,20 @@ class Indicators extends React__namespace.Component {
25905
25911
  return /*#__PURE__*/React__namespace.createElement("ul", {
25906
25912
  // reduntantly add class name for use in .less files--
25907
25913
  // the styles object key gets hashed
25908
- className: classNames__default["default"](aphrodite.css(styles$l.indicatorContainer), "indicatorContainer")
25914
+ className: classNames__default["default"](aphrodite.css(styles$m.indicatorContainer), "indicatorContainer")
25909
25915
  }, this.props.gradedGroups.map((_ref, i) => {
25910
25916
  let {
25911
25917
  title
25912
25918
  } = _ref;
25913
25919
  return /*#__PURE__*/React__namespace.createElement("li", {
25914
- className: aphrodite.css(styles$l.indicator),
25920
+ className: aphrodite.css(styles$m.indicator),
25915
25921
  key: title
25916
25922
  }, /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
25917
25923
  role: "button",
25918
25924
  "aria-label": this.context.strings.skipToTitle({
25919
25925
  title
25920
25926
  }),
25921
- style: styles$l.indicatorButton,
25927
+ style: styles$m.indicatorButton,
25922
25928
  onClick: () => this.props.onChangeCurrentGroup(i),
25923
25929
  onKeyDown: e => this.handleKeyDown(e, i)
25924
25930
  }, _ref2 => {
@@ -25928,9 +25934,9 @@ class Indicators extends React__namespace.Component {
25928
25934
  pressed
25929
25935
  } = _ref2;
25930
25936
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25931
- style: [styles$l.indicatorDot, (hovered || focused || pressed) && styles$l.indicatorDotFocused]
25937
+ style: [styles$m.indicatorDot, (hovered || focused || pressed) && styles$m.indicatorDotFocused]
25932
25938
  }, i === this.props.currentGroup && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25933
- style: styles$l.indicatorDotActive
25939
+ style: styles$m.indicatorDotActive
25934
25940
  }, /*#__PURE__*/React__namespace.createElement("span", {
25935
25941
  className: aphrodite.css(a11y.srOnly)
25936
25942
  }, this.context.strings.current)));
@@ -26006,7 +26012,7 @@ class GradedGroupSet extends React__namespace.Component {
26006
26012
  } = getDependencies();
26007
26013
  if (JIPT.useJIPT && this.props.gradedGroups.length > 1) {
26008
26014
  return /*#__PURE__*/React__namespace.createElement("div", {
26009
- className: aphrodite.css(styles$l.container)
26015
+ className: aphrodite.css(styles$m.container)
26010
26016
  }, this.props.gradedGroups.map((gradedGroup, i) => {
26011
26017
  return (
26012
26018
  /*#__PURE__*/
@@ -26029,13 +26035,13 @@ class GradedGroupSet extends React__namespace.Component {
26029
26035
  const numGroups = this.props.gradedGroups.length;
26030
26036
  const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
26031
26037
  return /*#__PURE__*/React__namespace.createElement("div", {
26032
- className: aphrodite.css(styles$l.container)
26038
+ className: aphrodite.css(styles$m.container)
26033
26039
  }, /*#__PURE__*/React__namespace.createElement("div", {
26034
- className: aphrodite.css(styles$l.top)
26040
+ className: aphrodite.css(styles$m.top)
26035
26041
  }, /*#__PURE__*/React__namespace.createElement("div", {
26036
- className: aphrodite.css(styles$l.title)
26042
+ className: aphrodite.css(styles$m.title)
26037
26043
  }, currentGroup.title), /*#__PURE__*/React__namespace.createElement("div", {
26038
- className: aphrodite.css(styles$l.spacer)
26044
+ className: aphrodite.css(styles$m.spacer)
26039
26045
  }), /*#__PURE__*/React__namespace.createElement(Indicators, {
26040
26046
  currentGroup: this.state.currentGroup,
26041
26047
  gradedGroups: this.props.gradedGroups,
@@ -26073,7 +26079,7 @@ var GradedGroupSet$1 = {
26073
26079
  tracking: "all",
26074
26080
  isLintable: true
26075
26081
  };
26076
- const styles$l = aphrodite.StyleSheet.create({
26082
+ const styles$m = aphrodite.StyleSheet.create({
26077
26083
  top: {
26078
26084
  display: "flex",
26079
26085
  flexDirection: "row"
@@ -26179,7 +26185,7 @@ class ButtonGroup extends React__namespace.Component {
26179
26185
  id: "" + i,
26180
26186
  ref: "button" + i,
26181
26187
  key: "" + i,
26182
- className: aphrodite.css(styles$k.buttonStyle, button.value === value && styles$k.selectedStyle),
26188
+ className: aphrodite.css(styles$l.buttonStyle, button.value === value && styles$l.selectedStyle),
26183
26189
  onClick: () => this.toggleSelect(button.value)
26184
26190
  }, button.content || "" + button.value);
26185
26191
  });
@@ -26192,7 +26198,7 @@ class ButtonGroup extends React__namespace.Component {
26192
26198
  }, buttons);
26193
26199
  }
26194
26200
  }
26195
- const styles$k = aphrodite.StyleSheet.create({
26201
+ const styles$l = aphrodite.StyleSheet.create({
26196
26202
  buttonStyle: {
26197
26203
  backgroundColor: "white",
26198
26204
  border: "1px solid #ccc",
@@ -28625,72 +28631,6 @@ class Graph extends React__namespace.Component {
28625
28631
  }
28626
28632
  }
28627
28633
 
28628
- const defaultGraphConfig = {
28629
- range: [[0, 1], [0, 1]],
28630
- tickStep: [1, 1],
28631
- gridStep: [1, 1],
28632
- snapStep: [1, 1],
28633
- markings: "none",
28634
- showTooltips: false,
28635
- graphDimensionsInPixels: [1, 1],
28636
- width: 0,
28637
- height: 0,
28638
- labels: [],
28639
- disableKeyboardInteraction: false
28640
- };
28641
- const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28642
- function useGraphConfig() {
28643
- return React__default["default"].useContext(GraphConfigContext);
28644
- }
28645
-
28646
- const matrixBuilder = mafs.vec.matrixBuilder;
28647
-
28648
- // When converting vectors, we don't need to translate because a vector
28649
- // is like a pair of x and y distances, which are not bound to specific graph locations.
28650
- function vectorsToPixels(vectors, graphState) {
28651
- let translation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0];
28652
- const {
28653
- range,
28654
- width,
28655
- height
28656
- } = graphState;
28657
- const [[xMin, xMax], [yMin, yMax]] = range;
28658
- const transformToPx = matrixBuilder().translate(...translation).scale(width / (xMax - xMin), -height / (yMax - yMin)).get();
28659
- return vectors.map(p => mafs.vec.transform(p, transformToPx));
28660
- }
28661
- function dimensionsToPixels(dimens, graphState) {
28662
- const {
28663
- range,
28664
- width,
28665
- height
28666
- } = graphState;
28667
- const [[xMin, xMax], [yMin, yMax]] = range;
28668
- const transformToPx = matrixBuilder().scale(width / (xMax - xMin), height / (yMax - yMin)).get();
28669
- return dimens.map(d => mafs.vec.transform(d, transformToPx));
28670
- }
28671
-
28672
- // When converting points, we translate so the pixel point ends up in the right location.
28673
- // This is because points are specific locations on graphs, unlike vectors.
28674
- function pointToPixel(point, graphState) {
28675
- const [[xMin], [, yMax]] = graphState.range;
28676
- const [a, b] = [-xMin, -yMax];
28677
- return vectorsToPixels([point], graphState, [a, b])[0];
28678
- }
28679
- const useTransformVectorsToPixels = function () {
28680
- const graphState = useGraphConfig();
28681
- for (var _len = arguments.length, vectors = new Array(_len), _key = 0; _key < _len; _key++) {
28682
- vectors[_key] = arguments[_key];
28683
- }
28684
- return vectorsToPixels(vectors, graphState);
28685
- };
28686
- const useTransformDimensionsToPixels = function () {
28687
- const graphState = useGraphConfig();
28688
- for (var _len2 = arguments.length, dimens = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
28689
- dimens[_key2] = arguments[_key2];
28690
- }
28691
- return dimensionsToPixels(dimens, graphState);
28692
- };
28693
-
28694
28634
  // Restricts `value` to be between `min` and `max`.
28695
28635
  function clamp(value, min, max) {
28696
28636
  if (value < min) {
@@ -28840,6 +28780,86 @@ const findAngle = (point1, point2, vertex) => {
28840
28780
  return findAngle(point1, vertex) - findAngle(point2, vertex);
28841
28781
  };
28842
28782
 
28783
+ const defaultGraphConfig = {
28784
+ range: [[0, 1], [0, 1]],
28785
+ tickStep: [1, 1],
28786
+ gridStep: [1, 1],
28787
+ snapStep: [1, 1],
28788
+ markings: "none",
28789
+ showTooltips: false,
28790
+ graphDimensionsInPixels: [1, 1],
28791
+ width: 0,
28792
+ height: 0,
28793
+ labels: [],
28794
+ disableKeyboardInteraction: false
28795
+ };
28796
+ const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28797
+ function useGraphConfig() {
28798
+ return React__default["default"].useContext(GraphConfigContext);
28799
+ }
28800
+
28801
+ const matrixBuilder = mafs.vec.matrixBuilder;
28802
+
28803
+ // When converting vectors, we don't need to translate because a vector
28804
+ // is like a pair of x and y distances, which are not bound to specific graph locations.
28805
+ function vectorsToPixels(vectors, graphState) {
28806
+ let translation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0];
28807
+ const {
28808
+ range,
28809
+ width,
28810
+ height
28811
+ } = graphState;
28812
+ const [[xMin, xMax], [yMin, yMax]] = range;
28813
+ const transformToPx = matrixBuilder().translate(...translation).scale(width / (xMax - xMin), -height / (yMax - yMin)).get();
28814
+ return vectors.map(p => mafs.vec.transform(p, transformToPx));
28815
+ }
28816
+ function dimensionsToPixels(dimens, graphState) {
28817
+ const {
28818
+ range,
28819
+ width,
28820
+ height
28821
+ } = graphState;
28822
+ const [[xMin, xMax], [yMin, yMax]] = range;
28823
+ const transformToPx = matrixBuilder().scale(width / (xMax - xMin), height / (yMax - yMin)).get();
28824
+ return dimens.map(d => mafs.vec.transform(d, transformToPx));
28825
+ }
28826
+
28827
+ // When converting points, we translate so the pixel point ends up in the right location.
28828
+ // This is because points are specific locations on graphs, unlike vectors.
28829
+ function pointToPixel(point, graphState) {
28830
+ const [[xMin], [, yMax]] = graphState.range;
28831
+ const [a, b] = [-xMin, -yMax];
28832
+ return vectorsToPixels([point], graphState, [a, b])[0];
28833
+ }
28834
+ const useTransformVectorsToPixels = function () {
28835
+ const graphState = useGraphConfig();
28836
+ for (var _len = arguments.length, vectors = new Array(_len), _key = 0; _key < _len; _key++) {
28837
+ vectors[_key] = arguments[_key];
28838
+ }
28839
+ return vectorsToPixels(vectors, graphState);
28840
+ };
28841
+ const useTransformDimensionsToPixels = function () {
28842
+ const graphState = useGraphConfig();
28843
+ for (var _len2 = arguments.length, dimens = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
28844
+ dimens[_key2] = arguments[_key2];
28845
+ }
28846
+ return dimensionsToPixels(dimens, graphState);
28847
+ };
28848
+ function pixelsToVectors(pixels, graphState) {
28849
+ const [[xMin, xMax], [yMin, yMax]] = graphState.range;
28850
+ const {
28851
+ width,
28852
+ height
28853
+ } = graphState;
28854
+ const xSpan = xMax - xMin;
28855
+ const ySpan = yMax - yMin;
28856
+ return pixels.map(pixel => {
28857
+ const x = pixel[X] / width * xSpan + xMin;
28858
+ const y = yMax - pixel[Y] / height * ySpan;
28859
+ return [x, y];
28860
+ });
28861
+ }
28862
+
28843
28863
  function AxisLabels() {
28844
28864
  const {
28845
28865
  range,
@@ -29430,7 +29450,7 @@ const VisibleIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
29430
29450
  width: "24",
29431
29451
  height: "24",
29432
29452
  viewBox: "0 0 24 24",
29433
- className: aphrodite.css(styles$j.icon)
29453
+ className: aphrodite.css(styles$k.icon)
29434
29454
  }, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("path", {
29435
29455
  id: "a",
29436
29456
  d: "M7.401 10.035c-1.424.748-2.599 1.905-3.544 " + "3.48a1 1 0 0 1-1.714-1.03C4.325 8.849 7.652 7 " + "12 7c4.348 0 7.675 1.848 9.857 5.486a1 1 0 0 " + "1-1.714 1.028c-.945-1.574-2.12-2.73-3.544-" + "3.48a5 5 0 1 1-9.198 0zM12 15a3 3 0 1 0 0-6 3 3 " + "0 0 0 0 6z"
@@ -29461,7 +29481,7 @@ const HiddenIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
29461
29481
  width: "24",
29462
29482
  height: "24",
29463
29483
  viewBox: "0 0 24 24",
29464
- className: aphrodite.css(styles$j.icon)
29484
+ className: aphrodite.css(styles$k.icon)
29465
29485
  }, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("path", {
29466
29486
  id: "a",
29467
29487
  d: "M8.794 7.38C9.791 7.127 10.86 7 12 7c4.348 0 " + "7.675 1.848 9.857 5.486a1 1 0 0 1-1.714 " + "1.028c-.945-1.574-2.12-2.73-3.544-3.48.258." + "604.401 1.268.401 1.966 0 1.02-.305 " + "1.967-.828 2.757l2.535 2.536a1 1 0 0 " + "1-1.414 1.414l-12-12a1 1 0 0 1 " + "1.414-1.414L8.794 7.38zm5.914 5.913a3 3 0 0 " + "0-4.001-4.001l4 4.001zM6.072 8.486l2.976 " + "2.976a3 3 0 0 0 3.49 3.49l1.579 1.58A5 5 0 " + "0 1 7.4 10.035c-1.424.747-2.599 1.904-3.544 " + "3.478a1 1 0 0 1-1.714-1.028c1.049-1.75 " + "2.363-3.085 3.929-4z"
@@ -29496,20 +29516,20 @@ const HUD = _ref => {
29496
29516
  let state;
29497
29517
  let icon;
29498
29518
  if (enabled) {
29499
- state = styles$j.enabled;
29519
+ state = styles$k.enabled;
29500
29520
  icon = /*#__PURE__*/React__namespace.createElement(VisibleIcon, null);
29501
29521
  } else {
29502
- state = styles$j.disabled;
29522
+ state = styles$k.disabled;
29503
29523
  icon = /*#__PURE__*/React__namespace.createElement(HiddenIcon, null);
29504
29524
  }
29505
29525
  return /*#__PURE__*/React__namespace.createElement("button", {
29506
- className: aphrodite.css(styles$j.hud, fixedPosition && styles$j.hudFixedPosition, state),
29526
+ className: aphrodite.css(styles$k.hud, fixedPosition && styles$k.hudFixedPosition, state),
29507
29527
  onClick: e => {
29508
29528
  onClick();
29509
29529
  }
29510
29530
  }, icon, message);
29511
29531
  };
29512
- const styles$j = aphrodite.StyleSheet.create({
29532
+ const styles$k = aphrodite.StyleSheet.create({
29513
29533
  hud: {
29514
29534
  boxSizing: "border-box",
29515
29535
  height: 36,
@@ -29562,16 +29582,16 @@ const styles$j = aphrodite.StyleSheet.create({
29562
29582
  const InfoTipBase = props => {
29563
29583
  return /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29564
29584
  content: /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.Body, {
29565
- style: styles$i.body
29585
+ style: styles$j.body
29566
29586
  }, props.children),
29567
29587
  placement: "right"
29568
29588
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
29569
29589
  size: "small",
29570
29590
  icon: questionIcon__default["default"],
29571
- style: styles$i.spacingLeft
29591
+ style: styles$j.spacingLeft
29572
29592
  }));
29573
29593
  };
29574
- const styles$i = aphrodite.StyleSheet.create({
29594
+ const styles$j = aphrodite.StyleSheet.create({
29575
29595
  spacingLeft: {
29576
29596
  marginInlineStart: wonderBlocksTokens.spacing.xxxSmall_4
29577
29597
  },
@@ -29647,7 +29667,7 @@ class MultiButtonGroup extends React__namespace.Component {
29647
29667
  id: "" + i,
29648
29668
  key: "" + i,
29649
29669
  ref: "button" + i,
29650
- className: aphrodite.css(styles$h.buttonStyle, selected && styles$h.selectedStyle),
29670
+ className: aphrodite.css(styles$i.buttonStyle, selected && styles$i.selectedStyle),
29651
29671
  onClick: () => this.toggleSelect(button.value)
29652
29672
  }, button.content || "" + button.value);
29653
29673
  });
@@ -29659,7 +29679,7 @@ class MultiButtonGroup extends React__namespace.Component {
29659
29679
  }, buttons);
29660
29680
  }
29661
29681
  }
29662
- const styles$h = aphrodite.StyleSheet.create({
29682
+ const styles$i = aphrodite.StyleSheet.create({
29663
29683
  buttonStyle: {
29664
29684
  backgroundColor: "white",
29665
29685
  border: "1px solid #ccc",
@@ -30102,6 +30122,8 @@ const ItemExtras = [
30102
30122
 
30103
30123
  // prettier-ignore
30104
30124
 
30125
+ // prettier-ignore
30126
+
30105
30127
  //prettier-ignore
30106
30128
 
30107
30129
  // A background image applied to various widgets.
@@ -30122,8 +30144,53 @@ const lockedFigureFillStyles = {
30122
30144
  translucent: 0.4,
30123
30145
  solid: 1
30124
30146
  };
30147
+
30148
+ // Not associated with a specific figure
30149
+
30125
30150
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
30126
30151
 
30152
+ function LockedLabel(props) {
30153
+ const {
30154
+ coord,
30155
+ text,
30156
+ color,
30157
+ size
30158
+ } = props;
30159
+ const [x, y] = pointToPixel(coord, useGraphConfig());
30160
+ const {
30161
+ TeX
30162
+ } = getDependencies();
30163
+
30164
+ // Move this all outside the SVG element
30165
+ return /*#__PURE__*/React__namespace.createElement("span", {
30166
+ className: "locked-label",
30167
+ style: {
30168
+ position: "absolute",
30169
+ left: x,
30170
+ top: y,
30171
+ color: lockedFigureColors[color],
30172
+ fontSize: wonderBlocksTokens.font.size[size]
30173
+ }
30174
+ }, /*#__PURE__*/React__namespace.createElement(TeX, null, text));
30175
+ }
30176
+
30177
+ function GraphLockedLabelsLayer(props) {
30178
+ const {
30179
+ lockedFigures
30180
+ } = props;
30181
+ return lockedFigures.map((figure, i) => {
30182
+ if (figure.type === "label") {
30183
+ return /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
30184
+ key: `label-${i}`
30185
+ }, figure));
30186
+ }
30187
+
30188
+ // TODO(LEMS-2271): Add support for labels within
30189
+ // other locked figure types
30190
+ return null;
30191
+ });
30192
+ }
30193
+
30127
30194
  const LockedEllipse = props => {
30128
30195
  const {
30129
30196
  center,
@@ -30438,6 +30505,12 @@ const GraphLockedLayer = props => {
30438
30505
  return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
30439
30506
  key: `function-${index}`
30440
30507
  }, figure));
30508
+ case "label":
30509
+ // This is rendered outside the SVG element, since
30510
+ // TeX cannot be rendered inside an SVG.
30511
+ // See graph-locked-labels-layer.tsx for
30512
+ // the component that renders these.
30513
+ return null;
30441
30514
  default:
30442
30515
  /**
30443
30516
  * Devlopment-time future-proofing: This should
@@ -30467,7 +30540,8 @@ const actions = {
30467
30540
  movePointInFigure
30468
30541
  },
30469
30542
  pointGraph: {
30470
- movePoint
30543
+ movePoint,
30544
+ addPoint
30471
30545
  },
30472
30546
  polygon: {
30473
30547
  movePoint,
@@ -30496,6 +30570,13 @@ function moveLine(itemIndex, delta) {
30496
30570
  delta
30497
30571
  };
30498
30572
  }
30573
+ const ADD_POINT = "add-point";
30574
+ function addPoint(location) {
30575
+ return {
30576
+ type: ADD_POINT,
30577
+ location
30578
+ };
30579
+ }
30499
30580
  const MOVE_ALL = "move-all";
30500
30581
  function moveAll(delta) {
30501
30582
  return {
@@ -32054,7 +32135,7 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
32054
32135
  };
32055
32136
  }
32056
32137
 
32057
- function PointGraph(props) {
32138
+ function LimitedPointGraph(props) {
32058
32139
  const {
32059
32140
  dispatch
32060
32141
  } = props;
@@ -32064,6 +32145,46 @@ function PointGraph(props) {
32064
32145
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
32065
32146
  })));
32066
32147
  }
32148
+ function UnlimitedPointGraph(props) {
32149
+ const {
32150
+ dispatch
32151
+ } = props;
32152
+ const graphState = useGraphConfig();
32153
+ const {
32154
+ range: [[minX, maxX], [minY, maxY]]
32155
+ } = graphState;
32156
+ const width = maxX - minX;
32157
+ const height = maxY - minY;
32158
+ const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
32159
+ const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
32160
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
32161
+ style: {
32162
+ fill: "rgba(0,0,0,0)"
32163
+ },
32164
+ width: widthPx,
32165
+ height: heightPx,
32166
+ x: left,
32167
+ y: top,
32168
+ onClick: event => {
32169
+ const elementRect = event.currentTarget.getBoundingClientRect();
32170
+ const x = event.clientX - elementRect.x;
32171
+ const y = event.clientY - elementRect.y;
32172
+ const graphCoordinates = pixelsToVectors([[x, y]], graphState);
32173
+ dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
32174
+ }
32175
+ }), props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
32176
+ key: i,
32177
+ point: point,
32178
+ onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
32179
+ })));
32180
+ }
32181
+ function PointGraph(props) {
32182
+ const numPoints = props.graphState.numPoints;
32183
+ if (numPoints === "unlimited") {
32184
+ return UnlimitedPointGraph(props);
32185
+ }
32186
+ return LimitedPointGraph(props);
32187
+ }
32067
32188
 
32068
32189
  const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
32069
32190
 
@@ -32224,11 +32345,11 @@ const MafsGraph = props => {
32224
32345
  labels,
32225
32346
  disableKeyboardInteraction: readOnly || !!props.static
32226
32347
  }
32227
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32348
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32228
32349
  className: "mafs-graph",
32229
32350
  style: {
32230
- width,
32231
32351
  height,
32352
+ width: "intrinsic",
32232
32353
  position: "relative",
32233
32354
  padding: "25px 25px 0 0",
32234
32355
  boxSizing: "content-box",
@@ -32240,6 +32361,8 @@ const MafsGraph = props => {
32240
32361
  }, /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
32241
32362
  box: props.box,
32242
32363
  backgroundImage: props.backgroundImage
32364
+ }), props.showLabelsFlag && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
32365
+ lockedFigures: props.lockedFigures
32243
32366
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32244
32367
  style: {
32245
32368
  position: "absolute",
@@ -32269,7 +32392,41 @@ const MafsGraph = props => {
32269
32392
  }), props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32270
32393
  state,
32271
32394
  dispatch
32272
- })))));
32395
+ })))), renderGraphControls({
32396
+ state,
32397
+ dispatch
32398
+ })));
32399
+ };
32400
+ const renderPointGraphControls = props => /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32401
+ kind: "secondary",
32402
+ style: {
32403
+ width: "100%",
32404
+ marginLeft: "20px"
32405
+ },
32406
+ onClick: () => {
32407
+ props.dispatch(actions.pointGraph.addPoint([0, 0]));
32408
+ }
32409
+ }, "Add Point");
32410
+ const renderGraphControls = props => {
32411
+ const {
32412
+ state,
32413
+ dispatch
32414
+ } = props;
32415
+ const {
32416
+ type
32417
+ } = state;
32418
+ switch (type) {
32419
+ case "point":
32420
+ if (state.numPoints === "unlimited") {
32421
+ return renderPointGraphControls({
32422
+ state,
32423
+ dispatch
32424
+ });
32425
+ }
32426
+ return null;
32427
+ default:
32428
+ return null;
32429
+ }
32273
32430
  };
32274
32431
  const renderGraph = props => {
32275
32432
  const {
@@ -32385,7 +32542,8 @@ function initializeGraphState(params) {
32385
32542
  return {
32386
32543
  ...shared,
32387
32544
  type: graph.type,
32388
- coords: getPointCoords(graph, range, step)
32545
+ coords: getPointCoords(graph, range, step),
32546
+ numPoints: graph.numPoints || 0
32389
32547
  };
32390
32548
  case "circle":
32391
32549
  return {
@@ -32641,6 +32799,8 @@ function interactiveGraphReducer(state, action) {
32641
32799
  return doChangeSnapStep(state, action);
32642
32800
  case CHANGE_RANGE:
32643
32801
  return doChangeRange(state, action);
32802
+ case ADD_POINT:
32803
+ return doAddPoint(state, action);
32644
32804
  default:
32645
32805
  throw new wonderStuffCore.UnreachableCaseError(action);
32646
32806
  }
@@ -32981,6 +33141,29 @@ function doChangeRange(state, action) {
32981
33141
  range: action.range
32982
33142
  };
32983
33143
  }
33144
+ function doAddPoint(state, action) {
33145
+ if (state.type !== "point") {
33146
+ return state;
33147
+ }
33148
+ const {
33149
+ snapStep
33150
+ } = state;
33151
+ const snappedPoint = snap(snapStep, action.location);
33152
+
33153
+ // Check if there's already a point in that spot
33154
+ for (const point of state.coords) {
33155
+ if (point[X] === snappedPoint[X] && point[Y] === snappedPoint[Y]) {
33156
+ return state;
33157
+ }
33158
+ }
33159
+
33160
+ // If there's no point in spot where we want the new point to go we add it there
33161
+ return {
33162
+ ...state,
33163
+ hasBeenInteractedWith: true,
33164
+ coords: [...state.coords, snappedPoint]
33165
+ };
33166
+ }
32984
33167
  const getDeltaVertex = (maxMoves, minMoves, delta) => {
32985
33168
  const [deltaX, deltaY] = delta;
32986
33169
  const maxXMove = Math.min(...maxMoves.map(move => move[X]));
@@ -34698,6 +34881,7 @@ class InteractiveGraph extends React__namespace.Component {
34698
34881
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
34699
34882
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
34700
34883
  return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
34884
+ showLabelsFlag: this.props.apiOptions?.flags?.["mafs"]?.["interactive-graph-locked-features-labels"],
34701
34885
  ref: this.mafsRef,
34702
34886
  gridStep: gridStep,
34703
34887
  snapStep: snapStep,
@@ -35260,10 +35444,7 @@ function shouldUseMafs(mafsFlags, graph) {
35260
35444
  switch (graph.type) {
35261
35445
  case "point":
35262
35446
  if (graph.numPoints === UNLIMITED) {
35263
- // TODO(benchristel): add a feature flag for the "unlimited"
35264
- // case once we've implemented point graphs with unlimited
35265
- // points
35266
- return false;
35447
+ return Boolean(mafsFlags["unlimited-point"]);
35267
35448
  }
35268
35449
  return Boolean(mafsFlags["point"]);
35269
35450
  case "polygon":
@@ -36319,7 +36500,7 @@ const HideAnswersToggle = props => {
36319
36500
  strings
36320
36501
  } = usePerseusI18n();
36321
36502
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
36322
- style: styles$g.switchWrapper
36503
+ style: styles$h.switchWrapper
36323
36504
  }, /*#__PURE__*/React__namespace.createElement(Switch__default["default"], {
36324
36505
  id: switchId,
36325
36506
  checked: props.areAnswersHidden,
@@ -36331,7 +36512,7 @@ const HideAnswersToggle = props => {
36331
36512
  tag: "label"
36332
36513
  }, strings.hideAnswersToggleLabel));
36333
36514
  };
36334
- const styles$g = aphrodite.StyleSheet.create({
36515
+ const styles$h = aphrodite.StyleSheet.create({
36335
36516
  switchWrapper: {
36336
36517
  display: "flex",
36337
36518
  flexDirection: "row",
@@ -36389,7 +36570,7 @@ const AnswerPill = props => {
36389
36570
  id: idFactory.get("perseus-label-image-widget-answer-pill"),
36390
36571
  onClick: correct ? undefined : onClick,
36391
36572
  ref: ref,
36392
- style: [style, popperStyle, styles$f.pill, correct && styles$f.correct, incorrect && styles$f.incorrect, (focused || hovered) && BringToFront]
36573
+ style: [style, popperStyle, styles$g.pill, correct && styles$g.correct, incorrect && styles$g.incorrect, (focused || hovered) && BringToFront]
36393
36574
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
36394
36575
  content: answerString,
36395
36576
  strings: strings,
@@ -36397,7 +36578,7 @@ const AnswerPill = props => {
36397
36578
  }));
36398
36579
  });
36399
36580
  };
36400
- const styles$f = aphrodite.StyleSheet.create({
36581
+ const styles$g = aphrodite.StyleSheet.create({
36401
36582
  correct: {
36402
36583
  // WB green darkened by 18%
36403
36584
  backgroundColor: "#00880b"
@@ -36463,25 +36644,25 @@ class Marker extends React__namespace.Component {
36463
36644
  icon: iconNull
36464
36645
  };
36465
36646
  if (showCorrectness) {
36466
- iconStyles = [styles$e.markerGraded, showCorrectness === "correct" ? styles$e.markerCorrect : styles$e.markerIncorrect, isOpen && styles$e.markerSelected];
36647
+ iconStyles = [styles$f.markerGraded, showCorrectness === "correct" ? styles$f.markerCorrect : styles$f.markerIncorrect, isOpen && styles$f.markerSelected];
36467
36648
  args = {
36468
36649
  ...args,
36469
36650
  icon: showCorrectness === "correct" ? iconCheck : iconMinus
36470
36651
  };
36471
36652
  } else if (selectedAnswers && selectedAnswers.length > 0) {
36472
- iconStyles = [styles$e.markerFilled, isOpen && styles$e.markerSelected];
36653
+ iconStyles = [styles$f.markerFilled, isOpen && styles$f.markerSelected];
36473
36654
  } else if (isOpen) {
36474
- iconStyles = [styles$e.markerSelected];
36655
+ iconStyles = [styles$f.markerSelected];
36475
36656
  args = {
36476
36657
  ...args,
36477
36658
  icon: iconChevronDown,
36478
36659
  size: 8
36479
36660
  };
36480
36661
  } else if (showPulsate) {
36481
- iconStyles = [styles$e.markerPulsateBase, this._mounted && shouldReduceMotion() ? showPulsate && styles$e.markerUnfilledPulsateOnce : showPulsate && styles$e.markerUnfilledPulsateInfinite];
36662
+ iconStyles = [styles$f.markerPulsateBase, this._mounted && shouldReduceMotion() ? showPulsate && styles$f.markerUnfilledPulsateOnce : showPulsate && styles$f.markerUnfilledPulsateInfinite];
36482
36663
  }
36483
36664
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
36484
- style: [styles$e.markerIcon, iconStyles],
36665
+ style: [styles$f.markerIcon, iconStyles],
36485
36666
  ref: node => this._icon = node
36486
36667
  }, /*#__PURE__*/React__namespace.createElement(Icon, args));
36487
36668
  }
@@ -36502,7 +36683,7 @@ class Marker extends React__namespace.Component {
36502
36683
  const markerDisabled = showCorrectness === "correct";
36503
36684
  const active = hovered || focused;
36504
36685
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
36505
- style: [styles$e.marker, active && !markerDisabled && styles$e.markerActive],
36686
+ style: [styles$f.marker, active && !markerDisabled && styles$f.markerActive],
36506
36687
  "aria-label": markerDisabled ? this.context.strings.correctExcited : label
36507
36688
  }, this.renderIcon()), !!selected && showAnswer && /*#__PURE__*/React__namespace.createElement(AnswerPill, {
36508
36689
  selectedAnswers: selected,
@@ -36515,7 +36696,7 @@ class Marker extends React__namespace.Component {
36515
36696
  }));
36516
36697
  }
36517
36698
  }
36518
- const styles$e = aphrodite.StyleSheet.create({
36699
+ const styles$f = aphrodite.StyleSheet.create({
36519
36700
  marker: {
36520
36701
  position: "absolute",
36521
36702
  backgroundColor: wonderBlocksTokens.color.white,
@@ -37105,13 +37286,13 @@ class LabelImage extends React__namespace.Component {
37105
37286
  const promptString = isMobile ? multipleAnswers ? strings.tapMultiple : strings.tapSingle : multipleAnswers ? strings.clickMultiple : strings.clickSingle;
37106
37287
  const choicesString = strings.choices;
37107
37288
  return /*#__PURE__*/React__namespace.createElement("div", {
37108
- className: classNames__default["default"]("perseus-label-image-widget-instructions", aphrodite.css(styles$d.instructions))
37289
+ className: classNames__default["default"]("perseus-label-image-widget-instructions", aphrodite.css(styles$e.instructions))
37109
37290
  }, /*#__PURE__*/React__namespace.createElement("div", {
37110
- className: aphrodite.css(styles$d.instructionsCaption)
37291
+ className: aphrodite.css(styles$e.instructionsCaption)
37111
37292
  }, promptString, " ", !hideChoices && choicesString), !hideChoices && /*#__PURE__*/React__namespace.createElement("div", {
37112
- className: aphrodite.css(styles$d.instructionsChoices)
37293
+ className: aphrodite.css(styles$e.instructionsChoices)
37113
37294
  }, choices.map((choice, index) => /*#__PURE__*/React__namespace.createElement("div", {
37114
- className: aphrodite.css(styles$d.instructionsChoice),
37295
+ className: aphrodite.css(styles$e.instructionsChoice),
37115
37296
  key: index
37116
37297
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
37117
37298
  content: choice,
@@ -37129,20 +37310,20 @@ class LabelImage extends React__namespace.Component {
37129
37310
  activeMarkerIndex
37130
37311
  } = this.state;
37131
37312
  return /*#__PURE__*/React__namespace.createElement("div", null, this.renderInstructions(), /*#__PURE__*/React__namespace.createElement("div", {
37132
- className: aphrodite.css(styles$d.markersCanvas),
37313
+ className: aphrodite.css(styles$e.markersCanvas),
37133
37314
  style: {
37134
37315
  maxWidth: imageWidth,
37135
37316
  maxHeight: imageHeight
37136
37317
  }
37137
37318
  }, /*#__PURE__*/React__namespace.createElement("div", {
37138
- className: aphrodite.css(styles$d.imageContainer,
37319
+ className: aphrodite.css(styles$e.imageContainer,
37139
37320
  // Ensure image interaction is disabled while answer
37140
37321
  // choices popup is visible. This addresses specific
37141
37322
  // case where user may click on question image to
37142
37323
  // dismiss the popup. If the image is larger in size
37143
37324
  // than its rendered in the widget, this would
37144
37325
  // result in a zoom of the image.
37145
- activeMarkerIndex !== -1 && styles$d.imageInteractionDisabled)
37326
+ activeMarkerIndex !== -1 && styles$e.imageInteractionDisabled)
37146
37327
  }, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref3 => {
37147
37328
  let {
37148
37329
  setAssetStatus
@@ -37168,7 +37349,7 @@ class LabelImage extends React__namespace.Component {
37168
37349
  }));
37169
37350
  }
37170
37351
  }
37171
- const styles$d = aphrodite.StyleSheet.create({
37352
+ const styles$e = aphrodite.StyleSheet.create({
37172
37353
  instructions: {
37173
37354
  paddingBottom: 16
37174
37355
  },
@@ -37267,7 +37448,7 @@ class Placeholder extends React__namespace.Component {
37267
37448
  const {
37268
37449
  layout
37269
37450
  } = this.props;
37270
- const className = aphrodite.css(styles$c.card, styles$c.placeholder, layout === Layout.HORIZONTAL && styles$c.horizontalCard);
37451
+ const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout === Layout.HORIZONTAL && styles$d.horizontalCard);
37271
37452
  const style = {
37272
37453
  width: this.props.width,
37273
37454
  height: this.props.height
@@ -37366,7 +37547,7 @@ class Draggable extends React__namespace.Component {
37366
37547
  // We need to keep backwards compatbility with rules specified directly
37367
37548
  // in CSS. Hence the hacky tacking on of manual classNames.
37368
37549
  // See sortable.less for details.
37369
- let className = aphrodite.css(styles$c.card, styles$c.draggable, layout === Layout.HORIZONTAL && styles$c.horizontalCard, layout === Layout.VERTICAL && styles$c.verticalCard, type === ItemState.DRAGGING && styles$c.dragging, type === ItemState.DISABLED && styles$c.disabled, !includePadding && styles$c.unpaddedCard) + " " + ClassNames.INTERACTIVE + " perseus-sortable-draggable";
37550
+ let className = aphrodite.css(styles$d.card, styles$d.draggable, layout === Layout.HORIZONTAL && styles$d.horizontalCard, layout === Layout.VERTICAL && styles$d.verticalCard, type === ItemState.DRAGGING && styles$d.dragging, type === ItemState.DISABLED && styles$d.disabled, !includePadding && styles$d.unpaddedCard) + " " + ClassNames.INTERACTIVE + " perseus-sortable-draggable";
37370
37551
  if (!includePadding) {
37371
37552
  className += " perseus-sortable-draggable-unpadded";
37372
37553
  }
@@ -37667,7 +37848,7 @@ class Sortable extends React__namespace.Component {
37667
37848
  } = this.props;
37668
37849
  // We need to keep backwards compatbility with rules specified directly
37669
37850
  // in CSS. See sortable.less for details.
37670
- const className = aphrodite.css(styles$c.sortable) + " perseus-sortable";
37851
+ const className = aphrodite.css(styles$d.sortable) + " perseus-sortable";
37671
37852
  const syncWidth = this.props.constraints?.width || layout === Layout.VERTICAL;
37672
37853
  const syncHeight = this.props.constraints?.height || layout === Layout.HORIZONTAL;
37673
37854
  ___default["default"].each(this.state.items, function (item, i, items) {
@@ -37898,7 +38079,7 @@ class Sortable extends React__namespace.Component {
37898
38079
  return ___default["default"].pluck(this.state.items, "option");
37899
38080
  }
37900
38081
  }
37901
- const styles$c = aphrodite.StyleSheet.create({
38082
+ const styles$d = aphrodite.StyleSheet.create({
37902
38083
  sortable: {
37903
38084
  boxSizing: "border-box",
37904
38085
  float: "left",
@@ -38027,25 +38208,25 @@ class Matcher extends React__namespace.Component {
38027
38208
  };
38028
38209
  const cellMarginPx = this.props.apiOptions.isMobile ? 8 : 5;
38029
38210
  return /*#__PURE__*/React__namespace.createElement("table", {
38030
- className: aphrodite.css(styles$b.widget) + " " + HACKY_CSS_CLASSNAME
38211
+ className: aphrodite.css(styles$c.widget) + " " + HACKY_CSS_CLASSNAME
38031
38212
  }, /*#__PURE__*/React__namespace.createElement("tbody", null, showLabels && /*#__PURE__*/React__namespace.createElement("tr", {
38032
- className: aphrodite.css(styles$b.row)
38213
+ className: aphrodite.css(styles$c.row)
38033
38214
  }, /*#__PURE__*/React__namespace.createElement("th", {
38034
- className: aphrodite.css(styles$b.column, styles$b.columnLabel)
38215
+ className: aphrodite.css(styles$c.column, styles$c.columnLabel)
38035
38216
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
38036
38217
  content: this.props.labels[0] || "...",
38037
38218
  linterContext: this.props.linterContext,
38038
38219
  strings: this.context.strings
38039
38220
  })), /*#__PURE__*/React__namespace.createElement("th", {
38040
- className: aphrodite.css(styles$b.column, styles$b.columnRight, styles$b.columnLabel)
38221
+ className: aphrodite.css(styles$c.column, styles$c.columnRight, styles$c.columnLabel)
38041
38222
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
38042
38223
  content: this.props.labels[1] || "...",
38043
38224
  linterContext: this.props.linterContext,
38044
38225
  strings: this.context.strings
38045
38226
  }))), /*#__PURE__*/React__namespace.createElement("tr", {
38046
- className: aphrodite.css(styles$b.row)
38227
+ className: aphrodite.css(styles$c.row)
38047
38228
  }, /*#__PURE__*/React__namespace.createElement("td", {
38048
- className: aphrodite.css(styles$b.column)
38229
+ className: aphrodite.css(styles$c.column)
38049
38230
  }, /*#__PURE__*/React__namespace.createElement(Sortable, {
38050
38231
  options: left,
38051
38232
  layout: Layout.VERTICAL,
@@ -38060,7 +38241,7 @@ class Matcher extends React__namespace.Component {
38060
38241
  ,
38061
38242
  ref: "left"
38062
38243
  })), /*#__PURE__*/React__namespace.createElement("td", {
38063
- className: aphrodite.css(styles$b.column, styles$b.columnRight)
38244
+ className: aphrodite.css(styles$c.column, styles$c.columnRight)
38064
38245
  }, /*#__PURE__*/React__namespace.createElement(Sortable
38065
38246
  // @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
38066
38247
  , {
@@ -38146,7 +38327,7 @@ ___default["default"].extend(Matcher, {
38146
38327
  });
38147
38328
  const padding = 5;
38148
38329
  const border = "1px solid #444";
38149
- const styles$b = aphrodite.StyleSheet.create({
38330
+ const styles$c = aphrodite.StyleSheet.create({
38150
38331
  widget: {
38151
38332
  paddingTop: padding,
38152
38333
  maxWidth: "100%",
@@ -38821,9 +39002,9 @@ function drawText(ctx, item) {
38821
39002
  if (item.value === null) {
38822
39003
  return;
38823
39004
  }
38824
- ctx.fillStyle = styles$a.fgColor;
38825
- ctx.font = styles$a.font;
38826
- ctx.fillText(item.value, item.pos[0] - styles$a.fontSizePx / 2 + 1, item.pos[1] + styles$a.fontSizePx / 2);
39005
+ ctx.fillStyle = styles$b.fgColor;
39006
+ ctx.font = styles$b.font;
39007
+ ctx.fillText(item.value, item.pos[0] - styles$b.fontSizePx / 2 + 1, item.pos[1] + styles$b.fontSizePx / 2);
38827
39008
  }
38828
39009
 
38829
39010
  /**
@@ -38844,16 +39025,16 @@ function drawText(ctx, item) {
38844
39025
  function drawDoubleLine(ctx, item) {
38845
39026
  // Outer line that forms both of the bond lines.
38846
39027
  let path = new Path2D();
38847
- ctx.lineWidth = 5 * styles$a.lineWidth;
38848
- ctx.strokeStyle = styles$a.fgColor;
39028
+ ctx.lineWidth = 5 * styles$b.lineWidth;
39029
+ ctx.strokeStyle = styles$b.fgColor;
38849
39030
  path.moveTo(item.startPos[0], item.startPos[1]);
38850
39031
  path.lineTo(item.endPos[0], item.endPos[1]);
38851
39032
  ctx.stroke(path);
38852
39033
 
38853
39034
  // Inner white line that separates the two bond lines.
38854
39035
  path = new Path2D();
38855
- ctx.lineWidth = 3 * styles$a.lineWidth;
38856
- ctx.strokeStyle = styles$a.bgColor;
39036
+ ctx.lineWidth = 3 * styles$b.lineWidth;
39037
+ ctx.strokeStyle = styles$b.bgColor;
38857
39038
  path.moveTo(item.startPos[0], item.startPos[1]);
38858
39039
  path.lineTo(item.endPos[0], item.endPos[1]);
38859
39040
  ctx.stroke(path);
@@ -38880,16 +39061,16 @@ function drawTripleLine(ctx, item) {
38880
39061
 
38881
39062
  // Outer line that will form the two outer bond lines.
38882
39063
  let path = new Path2D();
38883
- ctx.lineWidth = 7 * styles$a.lineWidth;
38884
- ctx.strokeStyle = styles$a.fgColor;
39064
+ ctx.lineWidth = 7 * styles$b.lineWidth;
39065
+ ctx.strokeStyle = styles$b.fgColor;
38885
39066
  path.moveTo(item.startPos[0], item.startPos[1]);
38886
39067
  path.lineTo(item.endPos[0], item.endPos[1]);
38887
39068
  ctx.stroke(path);
38888
39069
 
38889
39070
  // Middle white line that separates the bonds
38890
39071
  path = new Path2D();
38891
- ctx.lineWidth = 5 * styles$a.lineWidth;
38892
- ctx.strokeStyle = styles$a.bgColor;
39072
+ ctx.lineWidth = 5 * styles$b.lineWidth;
39073
+ ctx.strokeStyle = styles$b.bgColor;
38893
39074
  path.moveTo(item.startPos[0], item.startPos[1]);
38894
39075
  path.lineTo(item.endPos[0], item.endPos[1]);
38895
39076
  ctx.stroke(path);
@@ -38911,8 +39092,8 @@ function drawTripleLine(ctx, item) {
38911
39092
  */
38912
39093
  function drawLine(ctx, item) {
38913
39094
  const path = new Path2D();
38914
- ctx.lineWidth = styles$a.lineWidth;
38915
- ctx.strokeStyle = styles$a.fgColor;
39095
+ ctx.lineWidth = styles$b.lineWidth;
39096
+ ctx.strokeStyle = styles$b.fgColor;
38916
39097
  path.moveTo(item.startPos[0], item.startPos[1]);
38917
39098
  path.lineTo(item.endPos[0], item.endPos[1]);
38918
39099
  ctx.stroke(path);
@@ -38964,7 +39145,7 @@ function compareElements(item0, item1) {
38964
39145
  function draw(ctx, items) {
38965
39146
  items.sort(compareElements).forEach(drawItem(ctx));
38966
39147
  }
38967
- const styles$a = {
39148
+ const styles$b = {
38968
39149
  bgColor: "rgb(255, 255, 255)",
38969
39150
  fgColor: "rgb(0, 0, 0)",
38970
39151
  fontSizePx: 12,
@@ -41525,7 +41706,7 @@ class HighlightRenderer extends React__namespace.PureComponent {
41525
41706
  const rects = this.state.cachedHighlightRects;
41526
41707
  return /*#__PURE__*/React__namespace.createElement("div", null, rects.map((rect, index) => /*#__PURE__*/React__namespace.createElement("div", {
41527
41708
  key: index,
41528
- className: aphrodite.css(styles$9.highlightRect),
41709
+ className: aphrodite.css(styles$a.highlightRect),
41529
41710
  style: {
41530
41711
  // NOTE(mdr): We apply `position: absolute` here
41531
41712
  // rather than in Aphrodite styles, because
@@ -41544,7 +41725,7 @@ class HighlightRenderer extends React__namespace.PureComponent {
41544
41725
  })));
41545
41726
  }
41546
41727
  }
41547
- const styles$9 = aphrodite.StyleSheet.create({
41728
+ const styles$a = aphrodite.StyleSheet.create({
41548
41729
  highlightRect: {
41549
41730
  background: "#fffabe" // highlighter yellow :)
41550
41731
  }
@@ -41625,7 +41806,7 @@ class HighlightTooltip extends React__namespace.PureComponent {
41625
41806
  // TooltipContent wouldn't let me overwrite
41626
41807
  // user-select and onClick
41627
41808
  const content = /*#__PURE__*/React__namespace.createElement("div", {
41628
- className: aphrodite.css(styles$8.tooltipLabel),
41809
+ className: aphrodite.css(styles$9.tooltipLabel),
41629
41810
  onClick: this.props.onClick
41630
41811
  }, this.props.label);
41631
41812
  const style = {
@@ -41645,7 +41826,7 @@ class HighlightTooltip extends React__namespace.PureComponent {
41645
41826
  }, /*#__PURE__*/React__namespace.createElement("div", null)));
41646
41827
  }
41647
41828
  }
41648
- const styles$8 = aphrodite.StyleSheet.create({
41829
+ const styles$9 = aphrodite.StyleSheet.create({
41649
41830
  tooltipLabel: {
41650
41831
  // Unlike most tooltip contents, we don't want this label to be
41651
41832
  // selectable. Selecting it is not only unhelpful, but dangerous: if
@@ -42207,7 +42388,7 @@ class HighlightableContent extends React__namespace.PureComponent {
42207
42388
  // starting point: https://phabricator.khanacademy.org/D35623?id=170698
42208
42389
  const buildHighlight = r => this._buildHighlight(r);
42209
42390
  return /*#__PURE__*/React__namespace.createElement("div", {
42210
- className: aphrodite.css(styles$7.container),
42391
+ className: aphrodite.css(styles$8.container),
42211
42392
  ref: container => this._container = container
42212
42393
  }, /*#__PURE__*/React__namespace.createElement("div", null, this.props.enabled && this._container && this._content && /*#__PURE__*/React__namespace.createElement(HighlightingUI, {
42213
42394
  buildHighlight: buildHighlight,
@@ -42228,14 +42409,14 @@ class HighlightableContent extends React__namespace.PureComponent {
42228
42409
  onAddHighlight: this._handleAddHighlight,
42229
42410
  onRemoveHighlight: this._handleRemoveHighlight
42230
42411
  })), /*#__PURE__*/React__namespace.createElement("div", {
42231
- className: aphrodite.css(styles$7.content),
42412
+ className: aphrodite.css(styles$8.content),
42232
42413
  ref: content => this._content = content
42233
42414
  }, /*#__PURE__*/React__namespace.createElement(WordIndexer, {
42234
42415
  onWordsUpdate: this._handleWordsUpdate
42235
42416
  }, this.props.children)));
42236
42417
  }
42237
42418
  }
42238
- const styles$7 = aphrodite.StyleSheet.create({
42419
+ const styles$8 = aphrodite.StyleSheet.create({
42239
42420
  // We render content with z-index 1, and highlights with z-index 0, so that
42240
42421
  // content will receive the browser's native pointer events. (Highlights
42241
42422
  // have their own magic code to manage hover detection, despite being
@@ -42645,7 +42826,7 @@ class LineHeightMeasurer extends React__namespace.Component {
42645
42826
  }
42646
42827
  render() {
42647
42828
  return /*#__PURE__*/React__namespace.createElement("div", {
42648
- className: aphrodite.css(styles$6.measurer),
42829
+ className: aphrodite.css(styles$7.measurer),
42649
42830
  "aria-hidden": "true"
42650
42831
  }, /*#__PURE__*/React__namespace.createElement("div", {
42651
42832
  ref: ref => this._line = ref,
@@ -42653,7 +42834,7 @@ class LineHeightMeasurer extends React__namespace.Component {
42653
42834
  }, "Line Height Measurement"));
42654
42835
  }
42655
42836
  }
42656
- const styles$6 = aphrodite.StyleSheet.create({
42837
+ const styles$7 = aphrodite.StyleSheet.create({
42657
42838
  measurer: {
42658
42839
  position: "absolute",
42659
42840
  top: 0,
@@ -43127,6 +43308,212 @@ var PassageRefTarget$1 = {
43127
43308
  isLintable: true
43128
43309
  };
43129
43310
 
43311
+ /* eslint-disable react/forbid-prop-types, react/sort-comp */
43312
+
43313
+ // For returning user input, but currently the PhET widget
43314
+ // does not support accessing user input
43315
+ // This renders the PhET sim
43316
+ class PhetSimulation extends React__namespace.Component {
43317
+ static contextType = PerseusI18nContext;
43318
+ iframeRef = /*#__PURE__*/React__namespace.createRef();
43319
+ locale;
43320
+ state = {
43321
+ url: null,
43322
+ banner: null
43323
+ };
43324
+ constructor(props) {
43325
+ super(props);
43326
+ this.locale = this.getPhetCompatibleLocale(getDependencies().kaLocale);
43327
+ }
43328
+ getUserInput() {
43329
+ return null;
43330
+ }
43331
+ async componentDidMount() {
43332
+ await this.updateSimState(this.props.url);
43333
+ }
43334
+ async componentDidUpdate(prevProps) {
43335
+ // If the URL has changed, update our state
43336
+ if (prevProps.url !== this.props.url) {
43337
+ await this.updateSimState(this.props.url);
43338
+ }
43339
+ }
43340
+ render() {
43341
+ // We sandbox the iframe so that we allowlist only the functionality
43342
+ // that we need. This makes it safer to present third-party content
43343
+ // from the PhET website.
43344
+ // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
43345
+ const sandboxProperties = "allow-same-origin allow-scripts";
43346
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
43347
+ style: styles$6.container
43348
+ }, this.state.banner !== null &&
43349
+ /*#__PURE__*/
43350
+ // TODO(anna): Make this banner focusable
43351
+ React__namespace.createElement(wonderBlocksCore.View, {
43352
+ style: {
43353
+ marginBottom: phoneMargin
43354
+ }
43355
+ }, /*#__PURE__*/React__namespace.createElement(Banner__default["default"], {
43356
+ layout: "floating",
43357
+ kind: this.state.banner.kind,
43358
+ text: this.state.banner.message
43359
+ })), /*#__PURE__*/React__namespace.createElement("iframe", {
43360
+ ref: this.iframeRef,
43361
+ title: this.props.description,
43362
+ sandbox: sandboxProperties,
43363
+ style: {
43364
+ minWidth: 400,
43365
+ height: 360,
43366
+ width: "100%",
43367
+ borderWidth: 0
43368
+ },
43369
+ src: this.state.url?.toString(),
43370
+ allow: "fullscreen"
43371
+ }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
43372
+ icon: cornersOutIcon__default["default"],
43373
+ onClick: () => {
43374
+ this.iframeRef.current?.requestFullscreen();
43375
+ },
43376
+ kind: "secondary",
43377
+ "aria-label": "Fullscreen",
43378
+ style: {
43379
+ marginTop: 5,
43380
+ marginBottom: 5,
43381
+ alignSelf: "flex-end"
43382
+ },
43383
+ disabled: this.state.url === null
43384
+ }));
43385
+ }
43386
+ change = (() => {
43387
+ var _this = this;
43388
+ return function () {
43389
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
43390
+ args[_key] = arguments[_key];
43391
+ }
43392
+ // @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
43393
+ return change.apply(_this, args);
43394
+ };
43395
+ })();
43396
+ simpleValidate = rubric => {
43397
+ // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof PhetSimulation'.
43398
+ return PhetSimulation.validate(this.getUserInput(), rubric);
43399
+ };
43400
+
43401
+ // kaLocales and PhET locales use different formats and abbreviations.
43402
+ // PhET accepts different formats, i.e. kaLocale's hyphens, but it does not accept
43403
+ // different abbreviations, so in points of divergence of abbreviations, we need to
43404
+ // convert kaLocale abbreviations into abbreviations recognized by PhET.
43405
+ getPhetCompatibleLocale = kaLocale => {
43406
+ switch (kaLocale) {
43407
+ case "pt-pt":
43408
+ return "pt";
43409
+ case "zh-hans":
43410
+ return "zh_CN";
43411
+ case "zh-hant":
43412
+ return "zh_TW";
43413
+ case "fa-af":
43414
+ return "fa_DA";
43415
+ default:
43416
+ return kaLocale;
43417
+ }
43418
+ };
43419
+ displayLoadFailure = () => {
43420
+ this.setState({
43421
+ url: null,
43422
+ banner: {
43423
+ message: this.context.strings.simulationLoadFail,
43424
+ kind: "critical"
43425
+ }
43426
+ });
43427
+ };
43428
+ async updateSimState(urlString) {
43429
+ const url = makeSafeUrl(urlString, this.locale);
43430
+ if (url === null) {
43431
+ this.displayLoadFailure();
43432
+ return;
43433
+ }
43434
+ const response = await fetch(url);
43435
+ if (!response.ok) {
43436
+ this.displayLoadFailure();
43437
+ return;
43438
+ }
43439
+ const showLocaleWarning = await this.showLocaleWarning(url);
43440
+ this.setState({
43441
+ url: url,
43442
+ banner: showLocaleWarning ? {
43443
+ message: this.context.strings.simulationLocaleWarning,
43444
+ kind: "warning"
43445
+ } : null
43446
+ });
43447
+ }
43448
+ async showLocaleWarning(url) {
43449
+ // Do not show a locale warning on an invalid URL
43450
+ if (!url) {
43451
+ return false;
43452
+ }
43453
+ // Grab the simulation name
43454
+ const phetRegex = /https:\/\/phet\.colorado\.edu\/sims\/html\/([a-zA-Z0-9-]+)\/.*/g;
43455
+ const match = phetRegex.exec(url.toString());
43456
+ // Do not show a locale warning on a non-simulation URL
43457
+ if (match === null) {
43458
+ return false;
43459
+ }
43460
+ const simName = match[1];
43461
+ const response = await fetch(`https://phet.colorado.edu/sims/html/${simName}/latest/string-map.json`);
43462
+ if (!response.ok) {
43463
+ return false;
43464
+ }
43465
+ let responseJson;
43466
+ try {
43467
+ responseJson = await response.json();
43468
+ } catch {
43469
+ // If the file exists but there is no content to parse into a JSON,
43470
+ // response.json() will throw an error that we want to catch.
43471
+ return false;
43472
+ }
43473
+ const locales = Object.keys(responseJson);
43474
+
43475
+ // Only display a locale warning if there is no fallback language
43476
+ const baseLocale = this.locale.split("_")[0];
43477
+ for (const l of locales) {
43478
+ if (baseLocale === l.split("_")[0]) {
43479
+ return false;
43480
+ }
43481
+ }
43482
+ return true;
43483
+ }
43484
+ }
43485
+
43486
+ // Setting URL to null will display an error message in the iframe
43487
+ const makeSafeUrl = (urlString, locale) => {
43488
+ if (!URL.canParse(urlString)) {
43489
+ return null;
43490
+ }
43491
+ const url = new URL(urlString);
43492
+ if (url.origin !== "https://phet.colorado.edu") {
43493
+ return null;
43494
+ }
43495
+ url.searchParams.set("locale", locale);
43496
+ return url;
43497
+ };
43498
+ var PhetSimulation$1 = {
43499
+ name: "phet-simulation",
43500
+ displayName: "PhET Simulation",
43501
+ widget: PhetSimulation,
43502
+ // Hides widget from content creators until full release
43503
+ hidden: true,
43504
+ isLintable: true
43505
+ };
43506
+ const styles$6 = aphrodite.StyleSheet.create({
43507
+ container: {
43508
+ borderRadius: borderRadiusLarge,
43509
+ borderWidth: 1,
43510
+ borderColor: basicBorderColor,
43511
+ padding: phoneMargin,
43512
+ paddingBottom: 0,
43513
+ width: 650
43514
+ }
43515
+ });
43516
+
43130
43517
  /* eslint-disable react/no-unsafe */
43131
43518
  const {
43132
43519
  deepEq
@@ -44588,7 +44975,7 @@ var Video$1 = {
44588
44975
  };
44589
44976
 
44590
44977
  /* eslint-disable import/no-named-as-default */
44591
- var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, Plotter$1, PythonProgram$1, Sorter$1, Table$1, Video$1, DeprecatedStandin$1];
44978
+ var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, PhetSimulation$1, Plotter$1, PythonProgram$1, Sorter$1, Table$1, Video$1, DeprecatedStandin$1];
44592
44979
 
44593
44980
  /**
44594
44981
  * This should be called by all clients, specifying whether extra widgets are
@@ -44628,7 +45015,7 @@ const init = function (options) {
44628
45015
 
44629
45016
  // This file is processed by a Rollup plugin (replace) to inject the production
44630
45017
  const libName = "@khanacademy/perseus";
44631
- const libVersion = "30.0.1";
45018
+ const libVersion = "31.0.1";
44632
45019
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44633
45020
 
44634
45021
  const apiVersion = {
@@ -48203,6 +48590,7 @@ const widgetNameToEnum = {
48203
48590
  passage: "PASSAGE",
48204
48591
  "passage-ref": "PASSAGE_REF",
48205
48592
  "passage-ref-target": "PASSAGE_REF_TARGET",
48593
+ "phet-simulation": "PHET_SIMULATION",
48206
48594
  plotter: "PLOTTER",
48207
48595
  "python-program": "PYTHON_PROGRAM",
48208
48596
  "reaction-diagram": "REACTION_DIAGRAM",