@khanacademy/perseus 17.0.0 → 17.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/dependencies.d.ts +1 -0
  3. package/dist/es/index.css +20 -0
  4. package/dist/es/index.js +443 -338
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +20 -0
  7. package/dist/index.js +444 -340
  8. package/dist/index.js.map +1 -1
  9. package/dist/perseus-types.d.ts +1 -0
  10. package/dist/strings.js +1 -0
  11. package/dist/widgets/label-image/answer-pill.d.ts +10 -0
  12. package/dist/widgets/label-image/hide-answers-toggle.d.ts +6 -0
  13. package/dist/widgets/label-image/marker.d.ts +6 -9
  14. package/dist/widgets/label-image/types.d.ts +1 -0
  15. package/dist/widgets/label-image.d.ts +101 -12
  16. package/package.json +30 -22
  17. package/src/__tests__/__snapshots__/renderer.test.tsx.snap +3 -3
  18. package/src/dependencies.ts +4 -0
  19. package/src/perseus-types.ts +2 -0
  20. package/src/styles/widgets/label-image.less +32 -0
  21. package/src/widgets/__stories__/label-image.stories.tsx +73 -3
  22. package/src/widgets/__testdata__/label-image.testdata.ts +118 -1
  23. package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +2 -2
  24. package/src/widgets/label-image/__stories__/answer-pill.stories.tsx +62 -0
  25. package/src/widgets/label-image/__tests__/answer-pill.test.tsx +83 -0
  26. package/src/widgets/label-image/__tests__/hide-answers-toggle.test.tsx +39 -0
  27. package/src/widgets/label-image/answer-choices.tsx +7 -1
  28. package/src/widgets/label-image/answer-pill.tsx +89 -0
  29. package/src/widgets/label-image/hide-answers-toggle.tsx +40 -0
  30. package/src/widgets/label-image/marker.tsx +116 -174
  31. package/src/widgets/label-image/types.ts +1 -0
  32. package/src/widgets/label-image.test.ts +142 -52
  33. package/src/widgets/label-image.tsx +212 -135
  34. package/tsconfig-build.tsbuildinfo +1 -1
package/dist/index.js CHANGED
@@ -28,6 +28,10 @@ var Button = require('@khanacademy/wonder-blocks-button');
28
28
  var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
29
29
  var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
30
30
  var debounce = require('lodash.debounce');
31
+ var Pill = require('@khanacademy/wonder-blocks-pill');
32
+ var reactPopper = require('react-popper');
33
+ var Switch = require('@khanacademy/wonder-blocks-switch');
34
+ var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
31
35
  var createReactClass = require('create-react-class');
32
36
  var Link = require('@khanacademy/wonder-blocks-link');
33
37
  var perseusCore = require('@khanacademy/perseus-core');
@@ -69,6 +73,8 @@ var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
69
73
  var SimpleMarkdown__default = /*#__PURE__*/_interopDefaultLegacy(SimpleMarkdown);
70
74
  var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
71
75
  var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce);
76
+ var Pill__default = /*#__PURE__*/_interopDefaultLegacy(Pill);
77
+ var Switch__default = /*#__PURE__*/_interopDefaultLegacy(Switch);
72
78
  var createReactClass__default = /*#__PURE__*/_interopDefaultLegacy(createReactClass);
73
79
  var Link__default = /*#__PURE__*/_interopDefaultLegacy(Link);
74
80
 
@@ -268,7 +274,7 @@ class MathInput extends React__namespace.Component {
268
274
  className = className + " " + this.props.className;
269
275
  }
270
276
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
271
- style: [styles$A.outerWrapper, this.state.focused && styles$A.wrapperFocused, this.props.hasError && styles$A.wrapperError]
277
+ style: [styles$C.outerWrapper, this.state.focused && styles$C.wrapperFocused, this.props.hasError && styles$C.wrapperError]
272
278
  }, /*#__PURE__*/React__namespace.createElement("div", {
273
279
  style: {
274
280
  display: "flex",
@@ -295,7 +301,7 @@ class MathInput extends React__namespace.Component {
295
301
  dismissEnabled: true,
296
302
  content: () => /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
297
303
  closeButtonVisible: true,
298
- style: styles$A.popoverContent
304
+ style: styles$C.popoverContent
299
305
  }, /*#__PURE__*/React__namespace.createElement(mathInput.DesktopKeypad, _extends({
300
306
  onAnalyticsEvent: this.props.analytics.onAnalyticsEvent,
301
307
  extraKeys: this.props.extraKeys,
@@ -335,9 +341,9 @@ const MathInputIcon = _ref => {
335
341
  color = Color__default["default"].offBlack;
336
342
  break;
337
343
  }
338
- const dynamicClass = active || focused ? styles$A.iconActive : styles$A.iconInactive;
344
+ const dynamicClass = active || focused ? styles$C.iconActive : styles$C.iconInactive;
339
345
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
340
- style: [styles$A.iconContainer, dynamicClass]
346
+ style: [styles$C.iconContainer, dynamicClass]
341
347
  }, /*#__PURE__*/React__namespace.createElement("svg", {
342
348
  xmlns: "http://www.w3.org/2000/svg",
343
349
  width: "16",
@@ -382,7 +388,7 @@ const inputFocused = {
382
388
  borderColor: Color__default["default"].blue,
383
389
  margin: -1
384
390
  };
385
- const styles$A = aphrodite.StyleSheet.create({
391
+ const styles$C = aphrodite.StyleSheet.create({
386
392
  iconContainer: {
387
393
  display: "flex",
388
394
  justifyContent: "center",
@@ -17609,33 +17615,33 @@ class Lint extends React__namespace.Component {
17609
17615
  let warningText;
17610
17616
  let warningTextStyle;
17611
17617
  if (this.props.severity === Severity.Error) {
17612
- severityStyle = styles$z.indicatorError;
17618
+ severityStyle = styles$B.indicatorError;
17613
17619
  warningText = "Error";
17614
- warningTextStyle = styles$z.publishBlockingError;
17620
+ warningTextStyle = styles$B.publishBlockingError;
17615
17621
  } else if (this.props.severity === Severity.Warning) {
17616
- severityStyle = styles$z.indicatorWarning;
17622
+ severityStyle = styles$B.indicatorWarning;
17617
17623
  warningText = "Warning";
17618
- warningTextStyle = styles$z.warning;
17624
+ warningTextStyle = styles$B.warning;
17619
17625
  } else {
17620
- severityStyle = styles$z.indicatorGuideline;
17626
+ severityStyle = styles$B.indicatorGuideline;
17621
17627
  warningText = "Recommendation";
17622
- warningTextStyle = styles$z.warning;
17628
+ warningTextStyle = styles$B.warning;
17623
17629
  }
17624
17630
  return /*#__PURE__*/React__namespace.createElement("a", {
17625
17631
  href: `https://khanacademy.org/r/linter-rules#${this.props.ruleName}`,
17626
17632
  target: "lint-help-window",
17627
17633
  className: aphrodite.css(style)
17628
17634
  }, /*#__PURE__*/React__namespace.createElement("span", {
17629
- className: aphrodite.css(styles$z.indicator, severityStyle)
17635
+ className: aphrodite.css(styles$B.indicator, severityStyle)
17630
17636
  }, this.props.severity === 1 && /*#__PURE__*/React__namespace.createElement(InlineIcon, exclamationIcon)), /*#__PURE__*/React__namespace.createElement("div", {
17631
- className: aphrodite.css(styles$z.tooltip, tooltipAbove && styles$z.tooltipAbove)
17637
+ className: aphrodite.css(styles$B.tooltip, tooltipAbove && styles$B.tooltipAbove)
17632
17638
  }, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React__namespace.createElement("p", {
17633
17639
  key: i,
17634
- className: aphrodite.css(styles$z.tooltipParagraph)
17640
+ className: aphrodite.css(styles$B.tooltipParagraph)
17635
17641
  }, /*#__PURE__*/React__namespace.createElement("span", {
17636
17642
  className: aphrodite.css(warningTextStyle)
17637
17643
  }, warningText, ":", " "), m)), /*#__PURE__*/React__namespace.createElement("div", {
17638
- className: aphrodite.css(styles$z.tail, tooltipAbove && styles$z.tailAbove)
17644
+ className: aphrodite.css(styles$B.tail, tooltipAbove && styles$B.tailAbove)
17639
17645
  })));
17640
17646
  };
17641
17647
 
@@ -17663,20 +17669,20 @@ class Lint extends React__namespace.Component {
17663
17669
  }
17664
17670
  if (blockHighlight) {
17665
17671
  return /*#__PURE__*/React__namespace.createElement("span", {
17666
- className: aphrodite.css(styles$z.lintContainer, styles$z.lintContainerBlock)
17667
- }, this.renderLink(styles$z.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
17672
+ className: aphrodite.css(styles$B.lintContainer, styles$B.lintContainerBlock)
17673
+ }, this.renderLink(styles$B.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
17668
17674
  }
17669
17675
  if (inline) {
17670
17676
  return /*#__PURE__*/React__namespace.createElement("span", {
17671
- className: aphrodite.css(styles$z.lintContainer)
17672
- }, this.renderLink(styles$z.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
17677
+ className: aphrodite.css(styles$B.lintContainer)
17678
+ }, this.renderLink(styles$B.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
17673
17679
  }
17674
17680
  return /*#__PURE__*/React__namespace.createElement("div", {
17675
- className: aphrodite.css(styles$z.lintContainer)
17676
- }, this.renderLink(styles$z.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
17681
+ className: aphrodite.css(styles$B.lintContainer)
17682
+ }, this.renderLink(styles$B.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
17677
17683
  }
17678
17684
  }
17679
- const styles$z = aphrodite.StyleSheet.create({
17685
+ const styles$B = aphrodite.StyleSheet.create({
17680
17686
  // This is the class of the outermost element.
17681
17687
  // We use relative positioning so that the lint indicator can be
17682
17688
  // positioned absolutely relative to the position of the linty container.
@@ -19724,7 +19730,7 @@ class Renderer extends React__namespace.Component {
19724
19730
  };
19725
19731
  const mobileInnerStyle = {
19726
19732
  ...innerStyle,
19727
- ...styles$y.mobileZoomableParentFix
19733
+ ...styles$A.mobileZoomableParentFix
19728
19734
  };
19729
19735
  return /*#__PURE__*/React__namespace.createElement("div", {
19730
19736
  key: state.key,
@@ -19861,7 +19867,7 @@ class Renderer extends React__namespace.Component {
19861
19867
  };
19862
19868
  const mobileInnerStyle = {
19863
19869
  ...innerStyle,
19864
- ...styles$y.mobileZoomableParentFix
19870
+ ...styles$A.mobileZoomableParentFix
19865
19871
  };
19866
19872
  const wrappedOutput = /*#__PURE__*/React__namespace.createElement("div", {
19867
19873
  style: {
@@ -20367,7 +20373,7 @@ class Renderer extends React__namespace.Component {
20367
20373
  return this.lastRenderedMarkdown;
20368
20374
  }
20369
20375
  }
20370
- const styles$y = {
20376
+ const styles$A = {
20371
20377
  mobileZoomableParentFix: {
20372
20378
  // NOTE(abdul): There is an issue where transform animations will
20373
20379
  // cause the Zoomable component to disappear when running on the
@@ -21302,10 +21308,10 @@ class InputNumber extends React__namespace.Component {
21302
21308
  // Note: This is _very_ similar to what `numeric-input.jsx` does. If
21303
21309
  // you modify this, double-check if you also need to modify that
21304
21310
  // component.
21305
- const inputStyles = [styles$x.default, this.props.size === "small" ? styles$x.small : null, this.props.rightAlign ? styles$x.rightAlign : styles$x.leftAlign];
21311
+ const inputStyles = [styles$z.default, this.props.size === "small" ? styles$z.small : null, this.props.rightAlign ? styles$z.rightAlign : styles$z.leftAlign];
21306
21312
  // Unanswered
21307
21313
  if (rubric && !this.props.currentValue) {
21308
- inputStyles.push(styles$x.answerStateUnanswered);
21314
+ inputStyles.push(styles$z.answerStateUnanswered);
21309
21315
  }
21310
21316
  return /*#__PURE__*/React__namespace.createElement(InputWithExamples
21311
21317
  // eslint-disable-next-line react/no-string-refs
@@ -21453,7 +21459,7 @@ class InputNumber extends React__namespace.Component {
21453
21459
  return answerString;
21454
21460
  }
21455
21461
  }
21456
- const styles$x = aphrodite.StyleSheet.create({
21462
+ const styles$z = aphrodite.StyleSheet.create({
21457
21463
  default: {
21458
21464
  width: 80,
21459
21465
  height: "auto"
@@ -22451,7 +22457,7 @@ const FocusRing = function (props) {
22451
22457
  };
22452
22458
  return /*#__PURE__*/React__namespace.createElement("span", {
22453
22459
  "data-test-id": "focus-ring",
22454
- className: aphrodite.css(styles$w.ring),
22460
+ className: aphrodite.css(styles$y.ring),
22455
22461
  style: style
22456
22462
  }, children);
22457
22463
  };
@@ -22460,7 +22466,7 @@ FocusRing.defaultProps = {
22460
22466
  color: kaGreen$1,
22461
22467
  multipleSelect: false
22462
22468
  };
22463
- const styles$w = aphrodite.StyleSheet.create({
22469
+ const styles$y = aphrodite.StyleSheet.create({
22464
22470
  ring: {
22465
22471
  margin: "auto",
22466
22472
  display: "inline-block",
@@ -22497,7 +22503,7 @@ function getChoiceLetter(pos) {
22497
22503
  }
22498
22504
 
22499
22505
  const CHOICE_ICON_SIZE = 24;
22500
- const styles$v = aphrodite.StyleSheet.create({
22506
+ const styles$x = aphrodite.StyleSheet.create({
22501
22507
  iconWrapper: {
22502
22508
  display: "inline-block",
22503
22509
  position: "relative"
@@ -22520,7 +22526,7 @@ const CrossOutLine = function (props) {
22520
22526
  width: CROSS_OUT_LINE_SIZE,
22521
22527
  height: CROSS_OUT_LINE_SIZE,
22522
22528
  viewBox: `0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`,
22523
- className: aphrodite.css(styles$u.crossOutLine),
22529
+ className: aphrodite.css(styles$w.crossOutLine),
22524
22530
  "data-test-id": "choice-icon__cross-out-line"
22525
22531
  }, /*#__PURE__*/React__namespace.createElement("line", {
22526
22532
  // The line stretches from the bottom-left to top-right.
@@ -22535,7 +22541,7 @@ const CrossOutLine = function (props) {
22535
22541
  strokeLinecap: "round"
22536
22542
  }));
22537
22543
  };
22538
- const styles$u = aphrodite.StyleSheet.create({
22544
+ const styles$w = aphrodite.StyleSheet.create({
22539
22545
  crossOutLine: {
22540
22546
  // Center the icon within the container.
22541
22547
  position: "absolute",
@@ -22616,7 +22622,7 @@ const ChoiceIcon = function (props) {
22616
22622
  } = props;
22617
22623
  const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct);
22618
22624
  return /*#__PURE__*/React__namespace.createElement("div", {
22619
- className: aphrodite.css(styles$v.iconWrapper)
22625
+ className: aphrodite.css(styles$x.iconWrapper)
22620
22626
  }, /*#__PURE__*/React__namespace.createElement(FocusRing, {
22621
22627
  color: Color__default["default"].blue,
22622
22628
  visible: focused || hovered,
@@ -22625,13 +22631,13 @@ const ChoiceIcon = function (props) {
22625
22631
  // @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
22626
22632
  style: dynamicStyles,
22627
22633
  "data-test-id": "choice-icon__library-choice-icon",
22628
- className: aphrodite.css(styles$t.circle, showCorrectness && correct && styles$t.circleCorrect, showCorrectness && !correct && styles$t.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$t.circleIncorrectAnswered)
22634
+ className: aphrodite.css(styles$v.circle, showCorrectness && correct && styles$v.circleCorrect, showCorrectness && !correct && styles$v.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$v.circleIncorrectAnswered)
22629
22635
  // used in BaseRadio editMode to check
22630
22636
  // if we actually clicked on the radio icon
22631
22637
  ,
22632
22638
  "data-is-radio-icon": true
22633
22639
  }, /*#__PURE__*/React__namespace.createElement("div", {
22634
- className: aphrodite.css(styles$t.innerWrapper)
22640
+ className: aphrodite.css(styles$v.innerWrapper)
22635
22641
  }, /*#__PURE__*/React__namespace.createElement(ChoiceInner, {
22636
22642
  pos: pos,
22637
22643
  showCorrectness: showCorrectness,
@@ -22640,7 +22646,7 @@ const ChoiceIcon = function (props) {
22640
22646
  color: dynamicStyles.borderColor
22641
22647
  }));
22642
22648
  };
22643
- const styles$t = aphrodite.StyleSheet.create({
22649
+ const styles$v = aphrodite.StyleSheet.create({
22644
22650
  innerWrapper: {
22645
22651
  width: CHOICE_ICON_SIZE,
22646
22652
  height: CHOICE_ICON_SIZE,
@@ -22732,19 +22738,19 @@ const OptionStatus = function (props) {
22732
22738
  }
22733
22739
  let textStyle;
22734
22740
  if (correct) {
22735
- textStyle = styles$s.correct;
22741
+ textStyle = styles$u.correct;
22736
22742
  } else {
22737
22743
  if (checked || previouslyAnswered) {
22738
- textStyle = styles$s.incorrectAnswered;
22744
+ textStyle = styles$u.incorrectAnswered;
22739
22745
  } else {
22740
- textStyle = styles$s.incorrect;
22746
+ textStyle = styles$u.incorrect;
22741
22747
  }
22742
22748
  }
22743
22749
  return /*#__PURE__*/React__namespace.createElement("div", {
22744
- className: aphrodite.css(styles$s.text, textStyle)
22750
+ className: aphrodite.css(styles$u.text, textStyle)
22745
22751
  }, renderText(checked, correct, crossedOut));
22746
22752
  };
22747
- const styles$s = aphrodite.StyleSheet.create({
22753
+ const styles$u = aphrodite.StyleSheet.create({
22748
22754
  text: {
22749
22755
  alignItems: "center",
22750
22756
  display: "flex",
@@ -22818,8 +22824,8 @@ const Choice = function (props) {
22818
22824
  crossedOut: updatedCrossedOut
22819
22825
  });
22820
22826
  }
22821
- const descriptionClassName = classNames__default["default"]("description", aphrodite.css(styles$r.description));
22822
- const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$r.rationale));
22827
+ const descriptionClassName = classNames__default["default"]("description", aphrodite.css(styles$t.description));
22828
+ const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$t.rationale));
22823
22829
 
22824
22830
  // We want to show the choices as dimmed out when the choices are disabled.
22825
22831
  // However, we don't want to do this when we're in review mode in the
@@ -22986,7 +22992,7 @@ const Choice = function (props) {
22986
22992
  "data-test-id": `perseus-radio-rationale-content-${pos}`
22987
22993
  }, rationale));
22988
22994
  };
22989
- const styles$r = aphrodite.StyleSheet.create({
22995
+ const styles$t = aphrodite.StyleSheet.create({
22990
22996
  description: {
22991
22997
  display: "inline-block",
22992
22998
  width: "100%"
@@ -23178,10 +23184,10 @@ const BaseRadio = function (props) {
23178
23184
  const isMobile = apiOptions.isMobile;
23179
23185
  const firstChoiceHighlighted = choices[0].highlighted;
23180
23186
  const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
23181
- const className = classNames__default["default"]("perseus-widget-radio", !editMode && "perseus-rendered-radio", aphrodite.css(styles$q.radio, styles$q.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$q.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$q.radioContainerLastHighlighted));
23182
- const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$q.instructions, isMobile && styles$q.instructionsMobile));
23187
+ 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));
23188
+ const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$s.instructions, isMobile && styles$s.instructionsMobile));
23183
23189
  const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect);
23184
- const responsiveClassName = aphrodite.css(styles$q.responsiveFieldset);
23190
+ const responsiveClassName = aphrodite.css(styles$s.responsiveFieldset);
23185
23191
  const fieldset = /*#__PURE__*/React__namespace.createElement("fieldset", {
23186
23192
  className: `perseus-widget-radio-fieldset ${responsiveClassName}`
23187
23193
  }, /*#__PURE__*/React__namespace.createElement("legend", {
@@ -23232,7 +23238,7 @@ const BaseRadio = function (props) {
23232
23238
  const aphroditeClassName = checked => {
23233
23239
  // Whether or not to show correctness borders
23234
23240
  // for this choice and the next choice.
23235
- return aphrodite.css(sharedStyles.aboveScratchpad, styles$q.item, styles$q.responsiveItem, checked && styles$q.selectedItem, checked && choice.highlighted && styles$q.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$q.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$q.nextHighlighted);
23241
+ 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);
23236
23242
  };
23237
23243
 
23238
23244
  // HACK(abdulrahman): Preloads the selection-state
@@ -23306,14 +23312,14 @@ const BaseRadio = function (props) {
23306
23312
  // Allow for horizontal scrolling if content is too wide, which may be
23307
23313
  // an issue especially on phones.
23308
23314
  return /*#__PURE__*/React__namespace.createElement("div", {
23309
- className: aphrodite.css(styles$q.responsiveContainer)
23315
+ className: aphrodite.css(styles$s.responsiveContainer)
23310
23316
  }, fieldset);
23311
23317
  };
23312
23318
  BaseRadio.defaultProps = {
23313
23319
  editMode: false,
23314
23320
  multipleSelect: false
23315
23321
  };
23316
- const styles$q = aphrodite.StyleSheet.create({
23322
+ const styles$s = aphrodite.StyleSheet.create({
23317
23323
  instructions: {
23318
23324
  display: "block",
23319
23325
  color: gray17,
@@ -23972,9 +23978,9 @@ class Categorizer extends React__namespace.Component {
23972
23978
  indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
23973
23979
  }
23974
23980
  const table = /*#__PURE__*/React__namespace.createElement("table", {
23975
- className: "categorizer-table " + aphrodite.css(styles$p.mobileTable)
23981
+ className: "categorizer-table " + aphrodite.css(styles$r.mobileTable)
23976
23982
  }, /*#__PURE__*/React__namespace.createElement("thead", null, /*#__PURE__*/React__namespace.createElement("tr", null, /*#__PURE__*/React__namespace.createElement("td", {
23977
- className: aphrodite.css(styles$p.emptyHeaderCell)
23983
+ className: aphrodite.css(styles$r.emptyHeaderCell)
23978
23984
  }), this.props.categories.map((category, i) => {
23979
23985
  // Array index is the correct key here, as that's
23980
23986
  // how category grading actually works -- no way
@@ -23982,7 +23988,7 @@ class Categorizer extends React__namespace.Component {
23982
23988
  // middle. (If we later add that, this should be
23983
23989
  // fixed.)
23984
23990
  return /*#__PURE__*/React__namespace.createElement("th", {
23985
- className: aphrodite.css(styles$p.header),
23991
+ className: aphrodite.css(styles$r.header),
23986
23992
  key: i
23987
23993
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
23988
23994
  content: category,
@@ -24002,7 +24008,7 @@ class Categorizer extends React__namespace.Component {
24002
24008
  })), self.props.categories.map((catName, catNum) => {
24003
24009
  const selected = self.props.values[itemNum] === catNum;
24004
24010
  return /*#__PURE__*/React__namespace.createElement("td", {
24005
- className: "category " + aphrodite.css(styles$p.cell),
24011
+ className: "category " + aphrodite.css(styles$r.cell),
24006
24012
  key: catNum
24007
24013
  }, /*#__PURE__*/React__namespace.createElement("div", {
24008
24014
  className: ClassNames.INTERACTIVE,
@@ -24021,7 +24027,7 @@ class Categorizer extends React__namespace.Component {
24021
24027
  onChange: this.onChange.bind(this, itemNum, catNum),
24022
24028
  onClick: e => e.stopPropagation()
24023
24029
  }), !isMobile && /*#__PURE__*/React__namespace.createElement("span", {
24024
- className: aphrodite.css(styles$p.radioSpan, selected && styles$p.checkedRadioSpan, this.props.static && selected && styles$p.staticCheckedRadioSpan)
24030
+ className: aphrodite.css(styles$r.radioSpan, selected && styles$r.checkedRadioSpan, this.props.static && selected && styles$r.staticCheckedRadioSpan)
24025
24031
  }, selected ? /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircle) : /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircleThin))));
24026
24032
  }));
24027
24033
  /* eslint-enable max-len */
@@ -24033,7 +24039,7 @@ class Categorizer extends React__namespace.Component {
24033
24039
  "categorizer-container": true,
24034
24040
  "static-mode": this.props.static
24035
24041
  });
24036
- const inlineStyles = this.props.apiOptions.isMobile ? [styles$p.fullBleedContainer] : [];
24042
+ const inlineStyles = this.props.apiOptions.isMobile ? [styles$r.fullBleedContainer] : [];
24037
24043
  return /*#__PURE__*/React__namespace.createElement("div", {
24038
24044
  className: extraClassNames + " " + aphrodite.css(...inlineStyles)
24039
24045
  }, table);
@@ -24082,7 +24088,7 @@ const pageMargin = 16;
24082
24088
 
24083
24089
  // Stylesheets aren't directly testable
24084
24090
  /* c8 ignore next */
24085
- const styles$p = aphrodite.StyleSheet.create({
24091
+ const styles$r = aphrodite.StyleSheet.create({
24086
24092
  mobileTable: {
24087
24093
  [mediaQueries.smOrSmaller]: {
24088
24094
  minWidth: "auto"
@@ -24345,7 +24351,7 @@ class CSProgram extends React__namespace.Component {
24345
24351
  // creator "went wild".
24346
24352
  // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
24347
24353
  return /*#__PURE__*/React__namespace.createElement("div", {
24348
- className: aphrodite.css(styles$o.widthOverride, styleContainer && styles$o.container)
24354
+ className: aphrodite.css(styles$q.widthOverride, styleContainer && styles$q.container)
24349
24355
  }, /*#__PURE__*/React__namespace.createElement("iframe", {
24350
24356
  sandbox: sandboxOptions,
24351
24357
  src: url,
@@ -24355,7 +24361,7 @@ class CSProgram extends React__namespace.Component {
24355
24361
  }));
24356
24362
  }
24357
24363
  }
24358
- const styles$o = aphrodite.StyleSheet.create({
24364
+ const styles$q = aphrodite.StyleSheet.create({
24359
24365
  // Override the inherited width from the perseus paragraph class
24360
24366
  // 820 is minimum width for 420px editor and 400px canvas
24361
24367
  widthOverride: {
@@ -24403,7 +24409,7 @@ class Definition extends React__namespace.Component {
24403
24409
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.Popover, {
24404
24410
  content: /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
24405
24411
  color: "white",
24406
- style: styles$n.tooltipBody,
24412
+ style: styles$p.tooltipBody,
24407
24413
  closeButtonVisible: true
24408
24414
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
24409
24415
  apiOptions: this.props.apiOptions,
@@ -24434,7 +24440,7 @@ class Definition extends React__namespace.Component {
24434
24440
  });
24435
24441
  }
24436
24442
  }
24437
- const styles$n = {
24443
+ const styles$p = {
24438
24444
  tooltipBody: {
24439
24445
  color: Color__default["default"].offBlack,
24440
24446
  fontSize: 20,
@@ -24599,15 +24605,15 @@ class Explanation extends React__namespace.Component {
24599
24605
  const onClick = this._onClick;
24600
24606
  if (isMobile) {
24601
24607
  linkContainer = /*#__PURE__*/React__namespace.createElement("div", {
24602
- className: aphrodite.css(styles$m.linkContainer)
24608
+ className: aphrodite.css(styles$o.linkContainer)
24603
24609
  }, /*#__PURE__*/React__namespace.createElement("a", {
24604
- className: aphrodite.css(styles$m.mobileExplanationLink),
24610
+ className: aphrodite.css(styles$o.mobileExplanationLink),
24605
24611
  href: href,
24606
24612
  onClick: onClick,
24607
24613
  role: "button",
24608
24614
  "aria-expanded": this.state.expanded
24609
24615
  }, linkAnchor), this.state.expanded && /*#__PURE__*/React__namespace.createElement("svg", {
24610
- className: aphrodite.css(styles$m.disclosureArrow)
24616
+ className: aphrodite.css(styles$o.disclosureArrow)
24611
24617
  }, /*#__PURE__*/React__namespace.createElement("polygon", {
24612
24618
  style: {
24613
24619
  fill: backgroundColor
@@ -24620,13 +24626,13 @@ class Explanation extends React__namespace.Component {
24620
24626
  // an anchor tag (if rendered in a modal) or a Wonder Blocks
24621
24627
  // link component in lesson page, see: `article-page.jsx`.
24622
24628
  linkContainer = /*#__PURE__*/React__namespace.createElement("div", {
24623
- className: aphrodite.css(styles$m.linkContainer, styles$m.articleLink)
24629
+ className: aphrodite.css(styles$o.linkContainer, styles$o.articleLink)
24624
24630
  }, /*#__PURE__*/React__namespace.createElement(Link, {
24625
- className: aphrodite.css(styles$m.explanationLink)
24631
+ className: aphrodite.css(styles$o.explanationLink)
24626
24632
  // HACK(michaelpolyak): WB Link doesn't passthrough
24627
24633
  // class name.
24628
24634
  ,
24629
- style: styles$m.explanationLink,
24635
+ style: styles$o.explanationLink,
24630
24636
  href: href,
24631
24637
  onClick: onClick,
24632
24638
  role: "button",
@@ -24636,9 +24642,9 @@ class Explanation extends React__namespace.Component {
24636
24642
  // NOTE: For exercises, the baseElements `Link` is an
24637
24643
  // anchor tag, see: `perseus-api.jsx`.
24638
24644
  linkContainer = /*#__PURE__*/React__namespace.createElement("div", {
24639
- className: aphrodite.css(styles$m.linkContainer, styles$m.exerciseLink)
24645
+ className: aphrodite.css(styles$o.linkContainer, styles$o.exerciseLink)
24640
24646
  }, /*#__PURE__*/React__namespace.createElement(Link, {
24641
- className: aphrodite.css(styles$m.explanationLink),
24647
+ className: aphrodite.css(styles$o.explanationLink),
24642
24648
  href: href,
24643
24649
  onClick: onClick,
24644
24650
  role: "button",
@@ -24646,11 +24652,11 @@ class Explanation extends React__namespace.Component {
24646
24652
  }, `[${linkAnchor}]`));
24647
24653
  }
24648
24654
  }
24649
- const expandedStyle = isMobile ? styles$m.contentExpandedMobile : styles$m.contentExpanded;
24655
+ const expandedStyle = isMobile ? styles$o.contentExpandedMobile : styles$o.contentExpanded;
24650
24656
  return /*#__PURE__*/React__namespace.createElement("div", {
24651
- className: aphrodite.css(styles$m.container)
24657
+ className: aphrodite.css(styles$o.container)
24652
24658
  }, linkContainer, this.state.expanded && /*#__PURE__*/React__namespace.createElement("div", {
24653
- className: aphrodite.css(styles$m.content, isMobile && styles$m.contentMobile, this.state.expanded && expandedStyle)
24659
+ className: aphrodite.css(styles$o.content, isMobile && styles$o.contentMobile, this.state.expanded && expandedStyle)
24654
24660
  // eslint-disable-next-line react/no-string-refs
24655
24661
  ,
24656
24662
  ref: "content"
@@ -24673,7 +24679,7 @@ const verticalContentPadding = 10;
24673
24679
  const arrowWidth = 30;
24674
24680
  const arrowHeight = 14;
24675
24681
  const backgroundColor = gray95;
24676
- const styles$m = aphrodite.StyleSheet.create({
24682
+ const styles$o = aphrodite.StyleSheet.create({
24677
24683
  container: {
24678
24684
  display: "inline",
24679
24685
  position: "relative"
@@ -24779,15 +24785,15 @@ class GradedGroupAnswerBar extends React__namespace.Component {
24779
24785
  onNextQuestion
24780
24786
  } = this.props;
24781
24787
  const answerBarStyle = {
24782
- ...styles$l.answerBar,
24788
+ ...styles$n.answerBar,
24783
24789
  backgroundColor: answerBarState === "CORRECT" ? Color__default["default"].offWhite : "white",
24784
24790
  // Center the "Correct!" message only when there's no next question
24785
24791
  justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
24786
24792
  };
24787
24793
  const message = answerBarState === "INCORRECT" ? /*#__PURE__*/React__namespace.createElement("span", {
24788
- style: styles$l.text
24794
+ style: styles$n.text
24789
24795
  }, /*#__PURE__*/React__namespace.createElement("span", {
24790
- style: styles$l.tryAgainIcon
24796
+ style: styles$n.tryAgainIcon
24791
24797
  }, /*#__PURE__*/React__namespace.createElement(InlineIcon, iconTryAgain)), /*#__PURE__*/React__namespace.createElement("span", {
24792
24798
  style: {
24793
24799
  marginLeft: 8
@@ -24806,7 +24812,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
24806
24812
  return /*#__PURE__*/React__namespace.createElement("div", {
24807
24813
  style: answerBarStyle
24808
24814
  }, /*#__PURE__*/React__namespace.createElement("span", {
24809
- style: styles$l.text
24815
+ style: styles$n.text
24810
24816
  }, /*#__PURE__*/React__namespace.createElement("span", {
24811
24817
  style: {
24812
24818
  fontSize: 28,
@@ -24828,7 +24834,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
24828
24834
  }
24829
24835
  }
24830
24836
  const fontSize = 17;
24831
- const styles$l = {
24837
+ const styles$n = {
24832
24838
  answerBar: {
24833
24839
  display: "flex",
24834
24840
  alignItems: "center",
@@ -25028,7 +25034,7 @@ class GradedGroup extends React__namespace.Component {
25028
25034
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
25029
25035
  gradeStatus = i18n__namespace._("Incorrect");
25030
25036
  }
25031
- const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$k.gradedGroupInSet) : aphrodite.css(styles$k.gradedGroup);
25037
+ const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$m.gradedGroupInSet) : aphrodite.css(styles$m.gradedGroup);
25032
25038
  const classes = classNames__default["default"]({
25033
25039
  [mobileClass]: apiOptions.isMobile,
25034
25040
  "perseus-graded-group": true,
@@ -25047,7 +25053,7 @@ class GradedGroup extends React__namespace.Component {
25047
25053
  return /*#__PURE__*/React__namespace.createElement("div", {
25048
25054
  className: classes
25049
25055
  }, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
25050
- className: aphrodite.css(styles$k.title)
25056
+ className: aphrodite.css(styles$m.title)
25051
25057
  }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
25052
25058
  // eslint-disable-next-line react/no-string-refs
25053
25059
  ref: "renderer",
@@ -25080,7 +25086,7 @@ class GradedGroup extends React__namespace.Component {
25080
25086
  }, i18n__namespace._("Next question")), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("button", {
25081
25087
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
25082
25088
  tabIndex: "0",
25083
- className: aphrodite.css(styles$k.explanationTitle),
25089
+ className: aphrodite.css(styles$m.explanationTitle),
25084
25090
  onClick: () => this.setState({
25085
25091
  showHint: false
25086
25092
  }),
@@ -25112,7 +25118,7 @@ class GradedGroup extends React__namespace.Component {
25112
25118
  showHint: true
25113
25119
  });
25114
25120
  },
25115
- className: aphrodite.css(styles$k.showHintLink)
25121
+ className: aphrodite.css(styles$m.showHintLink)
25116
25122
  }, i18n__namespace._("Explain"))), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React__namespace.createElement(GradedGroupAnswerBar, {
25117
25123
  apiOptions: apiOptions,
25118
25124
  answerBarState: answerBarState,
@@ -25124,7 +25130,7 @@ class GradedGroup extends React__namespace.Component {
25124
25130
  const traverseChildWidgets$3 = function (props, traverseRenderer) {
25125
25131
  return ___default["default"].extend({}, props, traverseRenderer(props));
25126
25132
  };
25127
- const styles$k = aphrodite.StyleSheet.create({
25133
+ const styles$m = aphrodite.StyleSheet.create({
25128
25134
  gradedGroupInSet: {
25129
25135
  // Reset a few desktop-only styles that come from graded-group.less
25130
25136
  marginLeft: 0,
@@ -25194,20 +25200,20 @@ class Indicators extends React__namespace.Component {
25194
25200
  return /*#__PURE__*/React__namespace.createElement("ul", {
25195
25201
  // reduntantly add class name for use in .less files--
25196
25202
  // the styles object key gets hashed
25197
- className: classNames__default["default"](aphrodite.css(styles$j.indicatorContainer), "indicatorContainer")
25203
+ className: classNames__default["default"](aphrodite.css(styles$l.indicatorContainer), "indicatorContainer")
25198
25204
  }, this.props.gradedGroups.map((_ref, i) => {
25199
25205
  let {
25200
25206
  title
25201
25207
  } = _ref;
25202
25208
  return /*#__PURE__*/React__namespace.createElement("li", {
25203
- className: aphrodite.css(styles$j.indicator),
25209
+ className: aphrodite.css(styles$l.indicator),
25204
25210
  key: title
25205
25211
  }, /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
25206
25212
  role: "button",
25207
25213
  "aria-label": i18n__namespace._("Skip to %(title)s", {
25208
25214
  title
25209
25215
  }),
25210
- style: styles$j.indicatorButton,
25216
+ style: styles$l.indicatorButton,
25211
25217
  onClick: () => this.props.onChangeCurrentGroup(i),
25212
25218
  onKeyDown: e => this.handleKeyDown(e, i)
25213
25219
  }, _ref2 => {
@@ -25217,9 +25223,9 @@ class Indicators extends React__namespace.Component {
25217
25223
  pressed
25218
25224
  } = _ref2;
25219
25225
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25220
- style: [styles$j.indicatorDot, (hovered || focused || pressed) && styles$j.indicatorDotFocused]
25226
+ style: [styles$l.indicatorDot, (hovered || focused || pressed) && styles$l.indicatorDotFocused]
25221
25227
  }, i === this.props.currentGroup && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25222
- style: styles$j.indicatorDotActive
25228
+ style: styles$l.indicatorDotActive
25223
25229
  }, /*#__PURE__*/React__namespace.createElement("span", {
25224
25230
  className: aphrodite.css(a11y.srOnly)
25225
25231
  }, i18n__namespace._("Current"))));
@@ -25295,7 +25301,7 @@ class GradedGroupSet extends React__namespace.Component {
25295
25301
  } = getDependencies();
25296
25302
  if (JIPT.useJIPT && this.props.gradedGroups.length > 1) {
25297
25303
  return /*#__PURE__*/React__namespace.createElement("div", {
25298
- className: aphrodite.css(styles$j.container)
25304
+ className: aphrodite.css(styles$l.container)
25299
25305
  }, this.props.gradedGroups.map((gradedGroup, i) => {
25300
25306
  return (
25301
25307
  /*#__PURE__*/
@@ -25318,13 +25324,13 @@ class GradedGroupSet extends React__namespace.Component {
25318
25324
  const numGroups = this.props.gradedGroups.length;
25319
25325
  const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
25320
25326
  return /*#__PURE__*/React__namespace.createElement("div", {
25321
- className: aphrodite.css(styles$j.container)
25327
+ className: aphrodite.css(styles$l.container)
25322
25328
  }, /*#__PURE__*/React__namespace.createElement("div", {
25323
- className: aphrodite.css(styles$j.top)
25329
+ className: aphrodite.css(styles$l.top)
25324
25330
  }, /*#__PURE__*/React__namespace.createElement("div", {
25325
- className: aphrodite.css(styles$j.title)
25331
+ className: aphrodite.css(styles$l.title)
25326
25332
  }, currentGroup.title), /*#__PURE__*/React__namespace.createElement("div", {
25327
- className: aphrodite.css(styles$j.spacer)
25333
+ className: aphrodite.css(styles$l.spacer)
25328
25334
  }), /*#__PURE__*/React__namespace.createElement(Indicators, {
25329
25335
  currentGroup: this.state.currentGroup,
25330
25336
  gradedGroups: this.props.gradedGroups,
@@ -25362,7 +25368,7 @@ var GradedGroupSet$1 = {
25362
25368
  tracking: "all",
25363
25369
  isLintable: true
25364
25370
  };
25365
- const styles$j = aphrodite.StyleSheet.create({
25371
+ const styles$l = aphrodite.StyleSheet.create({
25366
25372
  top: {
25367
25373
  display: "flex",
25368
25374
  flexDirection: "row"
@@ -25467,7 +25473,7 @@ class ButtonGroup extends React__namespace.Component {
25467
25473
  id: "" + i,
25468
25474
  ref: "button" + i,
25469
25475
  key: "" + i,
25470
- className: aphrodite.css(styles$i.buttonStyle, button.value === value && styles$i.selectedStyle),
25476
+ className: aphrodite.css(styles$k.buttonStyle, button.value === value && styles$k.selectedStyle),
25471
25477
  onClick: () => this.toggleSelect(button.value)
25472
25478
  }, button.content || "" + button.value);
25473
25479
  });
@@ -25480,7 +25486,7 @@ class ButtonGroup extends React__namespace.Component {
25480
25486
  }, buttons);
25481
25487
  }
25482
25488
  }
25483
- const styles$i = aphrodite.StyleSheet.create({
25489
+ const styles$k = aphrodite.StyleSheet.create({
25484
25490
  buttonStyle: {
25485
25491
  backgroundColor: "white",
25486
25492
  border: "1px solid #ccc",
@@ -31008,7 +31014,7 @@ class AnswerChoice extends React__namespace.Component {
31008
31014
  checked
31009
31015
  } = this.props;
31010
31016
  return /*#__PURE__*/React__namespace.createElement("span", {
31011
- className: aphrodite.css(styles$h.checkmarkContainer)
31017
+ className: aphrodite.css(styles$j.checkmarkContainer)
31012
31018
  }, checked && /*#__PURE__*/React__namespace.createElement(Icon, {
31013
31019
  icon: iconCheckMedium,
31014
31020
  size: 16,
@@ -31028,11 +31034,11 @@ class AnswerChoice extends React__namespace.Component {
31028
31034
  } = this.state;
31029
31035
  const answerId = `${groupName}-${index}`;
31030
31036
  return /*#__PURE__*/React__namespace.createElement("div", {
31031
- className: aphrodite.css(styles$h.answerChoice, isInputFocused && styles$h.answerFocused),
31037
+ className: aphrodite.css(styles$j.answerChoice, isInputFocused && styles$j.answerFocused),
31032
31038
  onClick: e => this.handleClick(e),
31033
31039
  onKeyDown: e => this.handleKeyDown(e)
31034
31040
  }, /*#__PURE__*/React__namespace.createElement("span", {
31035
- className: aphrodite.css(styles$h.answerInputWrapper)
31041
+ className: aphrodite.css(styles$j.answerInputWrapper)
31036
31042
  }, /*#__PURE__*/React__namespace.createElement("input", {
31037
31043
  className: aphrodite.css(sharedStyles.perseusSrOnly),
31038
31044
  checked: checked,
@@ -31101,7 +31107,13 @@ class AnswerChoices extends React__namespace.Component {
31101
31107
  const {
31102
31108
  groupName
31103
31109
  } = this.state;
31104
- return /*#__PURE__*/React__namespace.createElement("fieldset", null, choices.map((choice, index) => /*#__PURE__*/React__namespace.createElement(AnswerChoice, _extends({}, choice, {
31110
+ return /*#__PURE__*/React__namespace.createElement("fieldset", {
31111
+ style: {
31112
+ border: "none",
31113
+ margin: 0,
31114
+ padding: 0
31115
+ }
31116
+ }, choices.map((choice, index) => /*#__PURE__*/React__namespace.createElement(AnswerChoice, _extends({}, choice, {
31105
31117
  key: choice.content,
31106
31118
  groupName: groupName,
31107
31119
  index: index,
@@ -31113,7 +31125,7 @@ class AnswerChoices extends React__namespace.Component {
31113
31125
  }))));
31114
31126
  }
31115
31127
  }
31116
- const styles$h = aphrodite.StyleSheet.create({
31128
+ const styles$j = aphrodite.StyleSheet.create({
31117
31129
  answerChoice: {
31118
31130
  display: "flex",
31119
31131
  alignItems: "center",
@@ -31144,44 +31156,125 @@ const styles$h = aphrodite.StyleSheet.create({
31144
31156
  }
31145
31157
  });
31146
31158
 
31159
+ const AnswerPill = props => {
31160
+ const {
31161
+ selectedAnswers,
31162
+ showCorrectness,
31163
+ markerRef,
31164
+ side,
31165
+ onClick,
31166
+ style
31167
+ } = props;
31168
+ const idFactory = wonderBlocksCore.useUniqueIdWithMock();
31169
+ const answerString = selectedAnswers.length > 1 ?
31170
+ // always need `ngettext` for variable numbers even if we don't use the singular, see https://khanacademy.slack.com/archives/C0918TZ5G/p1700163024293079
31171
+ i18n__namespace.ngettext("%(num)s answer", "%(num)s answers", selectedAnswers.length) : selectedAnswers[0];
31172
+
31173
+ // It should no longer be possible to interact with an answer after it
31174
+ // has been labeled (answered) correctly.
31175
+ const correct = showCorrectness === "correct";
31176
+ const incorrect = showCorrectness === "incorrect";
31177
+ const bringToFront = {
31178
+ boxShadow: `0 8px 8px ${Color__default["default"].offBlack64}`,
31179
+ zIndex: 1000
31180
+ };
31181
+ return /*#__PURE__*/React__namespace.createElement(reactPopper.Popper, {
31182
+ placement: side,
31183
+ referenceElement: markerRef,
31184
+ modifiers: [{
31185
+ name: "preventOverflow",
31186
+ options: {
31187
+ rootBoundary: "viewport"
31188
+ }
31189
+ }]
31190
+ }, _ref => {
31191
+ let {
31192
+ ref,
31193
+ style: popperStyle
31194
+ } = _ref;
31195
+ return /*#__PURE__*/React__namespace.createElement(Pill__default["default"], {
31196
+ size: "large",
31197
+ kind: "accent",
31198
+ id: idFactory.get("perseus-label-image-widget-answer-pill"),
31199
+ onClick: correct ? undefined : onClick,
31200
+ ref: ref,
31201
+ style: [style, popperStyle, {
31202
+ ":hover": bringToFront,
31203
+ ":focus": bringToFront
31204
+ }, correct && styles$i.correct, incorrect && styles$i.incorrect]
31205
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, {
31206
+ content: answerString
31207
+ }));
31208
+ });
31209
+ };
31210
+ const styles$i = aphrodite.StyleSheet.create({
31211
+ correct: {
31212
+ backgroundColor: "#00880b" // WB green darkened by 18%
31213
+ },
31214
+
31215
+ incorrect: {
31216
+ backgroundColor: Color__default["default"].offBlack64
31217
+ }
31218
+ });
31219
+
31220
+ const strings = {
31221
+ hideAnswersToggleLabel: i18n__namespace._("Hide answer choices")
31222
+ };
31223
+
31224
+ const HideAnswersToggle = props => {
31225
+ const idFactory = wonderBlocksCore.useUniqueIdWithMock();
31226
+ const switchId = idFactory.get("hide-answers-toggle");
31227
+ const labelId = idFactory.get("hide-answers-label");
31228
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31229
+ style: styles$h.switchWrapper
31230
+ }, /*#__PURE__*/React__namespace.createElement(Switch__default["default"], {
31231
+ id: switchId,
31232
+ checked: props.areAnswersHidden,
31233
+ onChange: props.onChange,
31234
+ "aria-labelledby": labelId
31235
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31236
+ id: labelId,
31237
+ htmlFor: switchId
31238
+ }, strings.hideAnswersToggleLabel));
31239
+ };
31240
+ const styles$h = aphrodite.StyleSheet.create({
31241
+ switchWrapper: {
31242
+ display: "flex",
31243
+ flexDirection: "row",
31244
+ flexWrap: "wrap-reverse",
31245
+ alignItems: "center",
31246
+ gap: "0.5em",
31247
+ marginTop: "1em"
31248
+ }
31249
+ });
31250
+
31147
31251
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
31252
+ function shouldReduceMotion() {
31253
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
31254
+ return !mediaQuery || mediaQuery.matches;
31255
+ }
31148
31256
  class Marker extends React__namespace.Component {
31149
31257
  // The marker icon element.
31150
31258
 
31151
31259
  static defaultProps = {
31152
31260
  selected: []
31153
31261
  };
31154
- state = {
31155
- isFocused: false
31156
- };
31157
- handleFocus() {
31158
- this.setState({
31159
- isFocused: true
31160
- });
31161
- }
31162
- handleBlur() {
31163
- this.setState({
31164
- isFocused: false
31165
- });
31166
- }
31167
31262
  renderIcon() {
31168
31263
  const {
31169
31264
  selected,
31170
31265
  showCorrectness,
31171
31266
  showSelected,
31172
- showPulsate
31267
+ showPulsate,
31268
+ focused
31173
31269
  } = this.props;
31174
- const {
31175
- isFocused
31176
- } = this.state;
31177
31270
 
31178
31271
  // Only a single marker may be "selected" at a time.
31179
31272
  // `showSelected` is a controlled prop, that may be set to `true` for
31180
31273
  // one marker at a time.
31181
- // `isFocused` is a controlled state, driven by focus events, and may
31274
+ // `focused` is a controlled state, driven by focus events, and may
31182
31275
  // only be `true` when there's no answer choices popup visible, and
31183
31276
  // keyboard focus is given to the marker.
31184
- const isSelected = showSelected || isFocused;
31277
+ const isSelected = showSelected || focused;
31185
31278
  let innerIcon;
31186
31279
  let iconStyles;
31187
31280
  if (showCorrectness) {
@@ -31191,17 +31284,28 @@ class Marker extends React__namespace.Component {
31191
31284
  color: Color__default["default"].white
31192
31285
  });
31193
31286
  if (showCorrectness === "correct") {
31194
- iconStyles = [styles$g.markerCorrect, isSelected && styles$g.markerCorrectSelected];
31287
+ iconStyles = [styles$g.markerGraded, styles$g.markerCorrect, isSelected && styles$g.markerSelected];
31195
31288
  } else {
31196
- iconStyles = [styles$g.markerIncorrect, isSelected && styles$g.markerIncorrectSelected];
31289
+ iconStyles = [styles$g.markerGraded, styles$g.markerIncorrect, isSelected && styles$g.markerSelected];
31197
31290
  }
31291
+ } else if (focused) {
31292
+ iconStyles = [styles$g.markerFocused, selected && selected.length > 0 && styles$g.markerFilled, showSelected && styles$g.markerSelected];
31198
31293
  } else if (selected && selected.length > 0) {
31199
- iconStyles = [styles$g.markerFilled, isSelected && styles$g.markerFilledSelected];
31294
+ iconStyles = [styles$g.markerFilled, isSelected && styles$g.markerSelected];
31295
+ } else if (isSelected) {
31296
+ iconStyles = [styles$g.markerSelected];
31200
31297
  } else {
31201
- iconStyles = [isSelected ? styles$g.markerUnfilledSelected : showPulsate && styles$g.markerUnfilledPulsate];
31298
+ iconStyles = [styles$g.markerPulsateBase, shouldReduceMotion() ? showPulsate && styles$g.markerUnfilledPulsateOnce : showPulsate && styles$g.markerUnfilledPulsateInfinite];
31202
31299
  }
31203
- return /*#__PURE__*/React__namespace.createElement("div", {
31204
- className: aphrodite.css(styles$g.markerIcon, ...iconStyles),
31300
+ if (isSelected && selected && selected.length === 0) {
31301
+ innerIcon = /*#__PURE__*/React__namespace.createElement(Icon, {
31302
+ icon: iconChevronDown,
31303
+ size: 8,
31304
+ color: Color__default["default"].white
31305
+ });
31306
+ }
31307
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31308
+ style: [styles$g.markerIcon, iconStyles],
31205
31309
  ref: node => this._icon = node
31206
31310
  }, innerIcon);
31207
31311
  }
@@ -31224,26 +31328,18 @@ class Marker extends React__namespace.Component {
31224
31328
  top: `${y}%`
31225
31329
  },
31226
31330
  tabIndex: isDisabled ? -1 : 0,
31227
- onFocus: () => this.handleFocus(),
31228
- onBlur: () => this.handleBlur()
31229
- // @ts-expect-error - TS2345 - Argument of type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to parameter of type 'MouseEvent'.
31230
- ,
31231
- onClick: e => this.props.onClick(e)
31232
- // @ts-expect-error - TS2345 - Argument of type 'KeyboardEvent<HTMLButtonElement>' is not assignable to parameter of type 'KeyboardEvent'.
31233
- ,
31331
+ onFocus: this.props.onFocus,
31332
+ onBlur: this.props.onBlur,
31333
+ onClick: e => {
31334
+ e.preventDefault();
31335
+ this.props.onClick(e);
31336
+ },
31234
31337
  onKeyDown: e => this.props.onKeyDown(e)
31235
31338
  }, this.renderIcon());
31236
31339
  }
31237
31340
  }
31238
- const markerColor = "#1865f2";
31239
- const selectedColor = "#2552b0";
31240
- const activeColor = selectedColor;
31241
- const correctColor = "#00a60e";
31242
- const correctActiveColor = "#167b1f";
31243
- const incorrectColor = "#909195";
31244
- const incorrectActiveColor = "#6c6e73";
31245
- const markerShadowColor = "rgba(33, 36, 44, 0.32)";
31246
31341
  const lightShadowColor = "rgba(33, 36, 44, 0.16)";
31342
+ const markerSize = 20;
31247
31343
  const styles$g = aphrodite.StyleSheet.create({
31248
31344
  unstyledButton: {
31249
31345
  padding: 0,
@@ -31256,9 +31352,12 @@ const styles$g = aphrodite.StyleSheet.create({
31256
31352
  },
31257
31353
  marker: {
31258
31354
  position: "absolute",
31355
+ outline: "none",
31259
31356
  // Center marker position based on it's maximum size.
31260
- width: 30,
31261
- height: 30,
31357
+ width: markerSize,
31358
+ height: markerSize,
31359
+ backgroundColor: Color__default["default"].white,
31360
+ borderRadius: 30,
31262
31361
  marginLeft: -15,
31263
31362
  marginTop: -15
31264
31363
  },
@@ -31268,136 +31367,92 @@ const styles$g = aphrodite.StyleSheet.create({
31268
31367
  // The base and unfilled marker style.
31269
31368
  markerIcon: {
31270
31369
  display: "flex",
31271
- position: "relative",
31370
+ alignItems: "center",
31371
+ justifyContent: "center",
31272
31372
  boxSizing: "content-box",
31273
- width: 16,
31274
- height: 16,
31275
- // Center icon within marker.
31276
- marginLeft: 5,
31373
+ width: markerSize,
31374
+ height: markerSize,
31375
+ marginLeft: 2,
31277
31376
  cursor: "pointer",
31278
- backgroundColor: markerColor,
31279
- border: "solid 2px #ffffff",
31280
- borderRadius: 16,
31281
- boxShadow: `0 2px 6px 0 ${markerShadowColor}`
31377
+ border: `2px solid ${Color__default["default"].offBlack64}`,
31378
+ borderRadius: markerSize,
31379
+ boxShadow: `0 8px 8px ${Color__default["default"].offBlack8}`,
31380
+ ":hover": {
31381
+ outline: `2px solid ${Color__default["default"].blue}`,
31382
+ outlineOffset: 2
31383
+ }
31282
31384
  },
31283
- markerUnfilledPulsate: {
31385
+ // The animation that presents the marker to the learner
31386
+ markerPulsateBase: {
31284
31387
  animationName: {
31285
31388
  "0%": {
31286
- transform: "scale(1)"
31389
+ transform: "scale(1)",
31390
+ backgroundColor: Color__default["default"].blue
31287
31391
  },
31288
31392
  "100%": {
31289
- transform: "scale(1.5)"
31393
+ transform: "scale(1.3)",
31394
+ backgroundColor: Color__default["default"].blue
31290
31395
  }
31291
31396
  },
31292
31397
  animationDirection: "alternate",
31293
31398
  animationDuration: "0.8s",
31294
- animationIterationCount: "infinite",
31295
31399
  animationTimingFunction: "ease-in",
31296
- transformOrigin: "50% 50%"
31400
+ transformOrigin: "50% 50%",
31401
+ animationIterationCount: "0"
31297
31402
  },
31298
- markerUnfilledSelected: {
31299
- "::before": {
31300
- content: "''",
31301
- display: "inline-block",
31302
- position: "absolute",
31303
- width: 20,
31304
- height: 20,
31305
- marginLeft: -4,
31306
- marginTop: -4,
31307
- border: `solid 2px ${selectedColor}`,
31308
- borderRadius: 20
31309
- },
31310
- ":active": {
31311
- backgroundColor: activeColor,
31312
- boxShadow: "none",
31313
- "::before": {
31314
- display: "none"
31315
- }
31403
+ markerUnfilledPulsateInfinite: {
31404
+ animationIterationCount: "infinite",
31405
+ ":hover": {
31406
+ outline: `0px solid ${Color__default["default"].blue}`
31316
31407
  }
31317
31408
  },
31318
- markerFilled: {
31319
- width: 8,
31320
- height: 8,
31321
- // Center icon within marker.
31322
- marginLeft: 9,
31323
- borderRadius: 8,
31324
- boxShadow: `0 1px 1px 0 ${lightShadowColor}`
31409
+ markerUnfilledPulsateOnce: {
31410
+ // Doing the animation twice lets it ease-in and ease-out
31411
+ animationIterationCount: "2"
31325
31412
  },
31326
- markerFilledSelected: {
31327
- "::before": {
31328
- content: "''",
31329
- display: "inline-block",
31330
- position: "absolute",
31331
- width: 12,
31332
- height: 12,
31333
- marginLeft: -4,
31334
- marginTop: -4,
31335
- border: `solid 2px ${selectedColor}`,
31336
- borderRadius: 12
31337
- },
31338
- ":active": {
31339
- backgroundColor: activeColor,
31340
- boxShadow: "none",
31341
- "::before": {
31342
- display: "none"
31343
- }
31413
+ markerFocused: {
31414
+ outline: `2px solid ${Color__default["default"].blue}`,
31415
+ outlineOffset: 2
31416
+ },
31417
+ // The learner is making an initial selection
31418
+ markerSelected: {
31419
+ boxShadow: `0 8px 8px ${Color__default["default"].offBlack8}`,
31420
+ border: `solid 4px ${Color__default["default"].white}`,
31421
+ backgroundColor: Color__default["default"].blue,
31422
+ borderRadius: markerSize,
31423
+ transform: "rotate(180deg)",
31424
+ ":hover": {
31425
+ outline: `2px solid ${Color__default["default"].blue}`,
31426
+ outlineOffset: -2
31344
31427
  }
31345
31428
  },
31346
- markerCorrect: {
31347
- width: 24,
31348
- height: 24,
31349
- marginLeft: 1,
31350
- marginTop: 1,
31351
- justifyContent: "center",
31352
- alignItems: "center",
31353
- background: correctColor,
31429
+ // The learner has made a selection
31430
+ markerFilled: {
31431
+ width: markerSize - 2,
31432
+ height: markerSize - 2,
31433
+ borderRadius: markerSize - 2,
31434
+ backgroundColor: "#ECF3FE",
31435
+ marginLeft: 3,
31436
+ marginTop: 0,
31437
+ border: `4px solid ${Color__default["default"].blue}`,
31354
31438
  boxShadow: `0 1px 1px 0 ${lightShadowColor}`
31355
31439
  },
31356
- markerCorrectSelected: {
31357
- "::before": {
31358
- content: "''",
31359
- display: "inline-block",
31360
- position: "absolute",
31361
- width: 28,
31362
- height: 28,
31363
- border: `solid 2px ${selectedColor}`,
31364
- borderRadius: 28
31365
- },
31366
- ":active": {
31367
- backgroundColor: correctActiveColor,
31368
- boxShadow: "none",
31369
- "::before": {
31370
- display: "none"
31371
- }
31372
- }
31373
- },
31374
- markerIncorrect: {
31375
- width: 24,
31376
- height: 24,
31440
+ markerGraded: {
31441
+ width: markerSize,
31442
+ height: markerSize,
31377
31443
  marginLeft: 1,
31378
31444
  marginTop: 1,
31379
31445
  justifyContent: "center",
31380
31446
  alignItems: "center",
31381
- background: incorrectColor,
31382
- boxShadow: `0 1px 1px 0 ${lightShadowColor}`
31447
+ border: `2px solid ${Color__default["default"].white}`,
31448
+ boxShadow: `0 8px 8px ${Color__default["default"].offBlack8}`
31383
31449
  },
31384
- markerIncorrectSelected: {
31385
- "::before": {
31386
- content: "''",
31387
- display: "inline-block",
31388
- position: "absolute",
31389
- width: 28,
31390
- height: 28,
31391
- border: `solid 2px ${selectedColor}`,
31392
- borderRadius: 28
31393
- },
31394
- ":active": {
31395
- backgroundColor: incorrectActiveColor,
31396
- boxShadow: "none",
31397
- "::before": {
31398
- display: "none"
31399
- }
31400
- }
31450
+ markerCorrect: {
31451
+ background: "#00880b" // WB green darkened by 18%
31452
+ },
31453
+
31454
+ markerIncorrect: {
31455
+ background: Color__default["default"].offBlack64
31401
31456
  }
31402
31457
  });
31403
31458
 
@@ -31481,7 +31536,20 @@ class LabelImage extends React__namespace.Component {
31481
31536
  /**
31482
31537
  * Determine the image side given a marker position (as percent of size).
31483
31538
  */
31484
- static imageSideForMarkerPosition(x, y) {
31539
+ static imageSideForMarkerPosition(x, y, preferredDirection) {
31540
+ // Special handling for preferred size
31541
+ if (preferredDirection && preferredDirection !== "NONE") {
31542
+ if (preferredDirection === "LEFT" && x > 20) {
31543
+ return "right";
31544
+ } else if (preferredDirection === "RIGHT" && x < 80) {
31545
+ return "left";
31546
+ } else if (preferredDirection === "UP" && y > 20) {
31547
+ return "bottom";
31548
+ } else if (preferredDirection === "DOWN" && y < 80) {
31549
+ return "top";
31550
+ }
31551
+ }
31552
+
31485
31553
  // Special handling for when marker is positioned near the horizontal
31486
31554
  // edges of the image. We want to ensure the returned side would not
31487
31555
  // result in a popup rendering that may overflow outside the page.
@@ -31632,18 +31700,12 @@ class LabelImage extends React__namespace.Component {
31632
31700
  super(props);
31633
31701
  this._markers = [];
31634
31702
  this.state = {
31635
- selectedMarkerIndex: -1,
31636
- markersInteracted: false
31703
+ activeMarkerIndex: -1,
31704
+ focusedMarkerIndex: -1,
31705
+ markersInteracted: false,
31706
+ hideAnswers: false
31637
31707
  };
31638
31708
  }
31639
- componentDidMount() {
31640
- document.addEventListener("click", this.handleDocumentClick, true);
31641
- document.addEventListener("keydown", this.handleDocumentKeyDown, true);
31642
- }
31643
- componentWillUnmount() {
31644
- document.removeEventListener("click", this.handleDocumentClick, true);
31645
- document.removeEventListener("keydown", this.handleDocumentKeyDown, true);
31646
- }
31647
31709
  simpleValidate(rubric) {
31648
31710
  return LabelImage.validate(this.getUserInput(), rubric);
31649
31711
  }
@@ -31680,17 +31742,17 @@ class LabelImage extends React__namespace.Component {
31680
31742
  }
31681
31743
  dismissMarkerPopup() {
31682
31744
  const {
31683
- selectedMarkerIndex: index
31745
+ activeMarkerIndex
31684
31746
  } = this.state;
31685
31747
 
31686
31748
  // No popup should be open if there's no selected marker.
31687
- if (index === -1) {
31749
+ if (activeMarkerIndex === -1) {
31688
31750
  return;
31689
31751
  }
31690
31752
  this.setState({
31691
- selectedMarkerIndex: -1
31753
+ activeMarkerIndex: -1
31692
31754
  }, () => {
31693
- const marker = this._markers[index];
31755
+ const marker = this._markers[activeMarkerIndex];
31694
31756
  // Set focus on the just-deselected-marker, to enable to resume
31695
31757
  // navigating between the markers using the keyboard.
31696
31758
  if (marker) {
@@ -31698,40 +31760,6 @@ class LabelImage extends React__namespace.Component {
31698
31760
  }
31699
31761
  });
31700
31762
  }
31701
- handleDocumentClick = e => {
31702
- // Dismiss open popup with answer choices if user clicks outside it.
31703
- if (this._selectedMarkerPopup && this._answerChoices) {
31704
- const answerChoices = ReactDOM__namespace.findDOMNode(this._answerChoices);
31705
- const targetElement = e.target;
31706
- // HACK(michaelpolyak): We want to determine if the click target is
31707
- // contained within the popup layer. As there's no public interface
31708
- // to get this layer from the popup, we traverse several levels of
31709
- // answer choices parents to test whether the click target is
31710
- // contained within.
31711
- const containsEventTarget = function (element) {
31712
- let depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
31713
- return element && (element.contains(targetElement) || depth > 0 && containsEventTarget(element.parentElement, depth - 1));
31714
- };
31715
- if (!containsEventTarget(answerChoices)) {
31716
- // Close popup and set focus back to the marker for which it was
31717
- // open.
31718
- this.dismissMarkerPopup();
31719
- }
31720
- }
31721
- };
31722
- handleDocumentKeyDown = e => {
31723
- // Dismiss open popup with answer choices if user presses Escape key.
31724
- if (this._selectedMarkerPopup && e.keyCode === 27) {
31725
- // Ensure other listeners are not triggered on key down event that
31726
- // closes the popup, as this will also dismiss the modal that the
31727
- // widget may be rendered within.
31728
- e.stopPropagation();
31729
-
31730
- // Close popup and set focus back to the marker for which it was
31731
- // open.
31732
- this.dismissMarkerPopup();
31733
- }
31734
- };
31735
31763
  handleMarkerChange(index, marker) {
31736
31764
  const {
31737
31765
  markers,
@@ -31751,16 +31779,15 @@ class LabelImage extends React__namespace.Component {
31751
31779
  markers: updatedMarkers
31752
31780
  });
31753
31781
  }
31754
- handleMarkerClick(index, e) {
31782
+ activateMarker(index) {
31755
31783
  const {
31756
- selectedMarkerIndex
31784
+ activeMarkerIndex
31757
31785
  } = this.state;
31758
- e.preventDefault();
31759
31786
 
31760
31787
  // Select the marker, revealing answer choices.
31761
- if (selectedMarkerIndex !== index) {
31788
+ if (activeMarkerIndex !== index) {
31762
31789
  this.setState({
31763
- selectedMarkerIndex: index,
31790
+ activeMarkerIndex: index,
31764
31791
  markersInteracted: true
31765
31792
  }, () => {
31766
31793
  // A delay is required to allow answer choices to first render
@@ -31830,10 +31857,10 @@ class LabelImage extends React__namespace.Component {
31830
31857
  } = this.props;
31831
31858
 
31832
31859
  // Compile the user selected answer choices.
31833
- const selected = choices.filter((choice, index) => selection[index]);
31860
+ const selected = choices.filter((_, index) => selection[index]);
31834
31861
  this.handleMarkerChange(index, {
31835
31862
  ...markers[index],
31836
- selected
31863
+ selected: selected.length ? selected : undefined
31837
31864
  });
31838
31865
  if (!multipleAnswers) {
31839
31866
  this.dismissMarkerPopup();
@@ -31855,50 +31882,80 @@ class LabelImage extends React__namespace.Component {
31855
31882
  checked: selected ? selected.includes(choice) : false
31856
31883
  })),
31857
31884
  multipleSelect: multipleAnswers,
31858
- onChange: selection => this.handleAnswerChoicesChangeForMarker(index, selection),
31885
+ onChange: selection => {
31886
+ this.props.analytics?.onAnalyticsEvent({
31887
+ type: "perseus:label-image:choiced-interacted-with",
31888
+ payload: null
31889
+ });
31890
+ this.handleAnswerChoicesChangeForMarker(index, selection);
31891
+ },
31859
31892
  ref: node => this._answerChoices = node
31860
31893
  }));
31861
31894
  }
31862
31895
  renderMarkers() {
31863
31896
  const {
31864
31897
  markers,
31865
- questionCompleted
31898
+ questionCompleted,
31899
+ preferredPopoverDirection
31866
31900
  } = this.props;
31867
31901
  const {
31868
- selectedMarkerIndex,
31902
+ activeMarkerIndex,
31869
31903
  markersInteracted
31870
31904
  } = this.state;
31871
31905
 
31872
31906
  // Render all markers for widget.
31873
31907
  return markers.map((marker, index) => {
31874
31908
  const score = LabelImage.gradeMarker(marker);
31909
+ // Once the question is answered, show markers
31910
+ // with correct answers, otherwise passthrough
31911
+ // the correctness state.
31912
+ const showCorrectness = questionCompleted && score.hasAnswers && score.isCorrect ? "correct" : marker.showCorrectness;
31875
31913
  const element = /*#__PURE__*/React__namespace.createElement(Marker, _extends({}, marker, {
31876
- // Once the question is answered, show markers
31877
- // with correct answers, otherwise passthrough
31878
- // the correctness state.
31879
- showCorrectness: questionCompleted && score.hasAnswers && score.isCorrect ? "correct" : marker.showCorrectness,
31880
- showSelected: index === selectedMarkerIndex,
31914
+ showCorrectness: showCorrectness,
31915
+ showSelected: index === activeMarkerIndex,
31881
31916
  showPulsate: !markersInteracted,
31882
31917
  key: `${marker.x}.${marker.y}`,
31883
- onClick: e => this.handleMarkerClick(index, e),
31918
+ onClick: () => {
31919
+ this.props.analytics?.onAnalyticsEvent({
31920
+ type: "perseus:label-image:marker-interacted-with",
31921
+ payload: null
31922
+ });
31923
+ this.activateMarker(index);
31924
+ },
31884
31925
  onKeyDown: e => this.handleMarkerKeyDown(index, e),
31885
- ref: node => this._markers[index] = node
31926
+ ref: node => this._markers[index] = node,
31927
+ focused: index === this.state.focusedMarkerIndex,
31928
+ onFocus: () => {
31929
+ this.setState({
31930
+ focusedMarkerIndex: index
31931
+ });
31932
+ },
31933
+ onBlur: () => {
31934
+ if (index === this.state.focusedMarkerIndex) {
31935
+ this.setState({
31936
+ focusedMarkerIndex: -1
31937
+ });
31938
+ }
31939
+ }
31886
31940
  }));
31887
31941
 
31888
- // The user selected marker is wrapped with a popup that shows its
31889
- // answer choices, otherwise it's returned as is.
31890
- if (index !== selectedMarkerIndex) {
31891
- return element;
31892
- }
31893
-
31894
31942
  // Determine whether page is rendered in a narrow browser window.
31895
31943
  const isNarrowPage = window.matchMedia(mediaQueries.xsOrSmaller.replace("@media ", "")).matches;
31944
+
31945
+ // Determine whether the image is wider than it is tall.
31946
+ const isWideImage = this.props.imageWidth / 2 > this.props.imageHeight;
31896
31947
  let side;
31948
+ let markerPosition;
31897
31949
  // Position popup closest to the center, preferring it renders
31898
31950
  // entirely within the image area.
31899
- if (isNarrowPage) {
31951
+ if (isNarrowPage || isWideImage) {
31900
31952
  side = marker.y > 50 ? "top" : "bottom";
31953
+ markerPosition = marker.y > 50 ? "bottom" : "top";
31901
31954
  } else {
31955
+ markerPosition = LabelImage.imageSideForMarkerPosition(marker.x, marker.y, preferredPopoverDirection);
31956
+ if (markerPosition === "center") {
31957
+ markerPosition = "bottom";
31958
+ }
31902
31959
  // This mirrors the calculated side of where the marker is
31903
31960
  // located within the image, so that popup appears closer to
31904
31961
  // the image center.
@@ -31906,24 +31963,44 @@ class LabelImage extends React__namespace.Component {
31906
31963
  left: "right",
31907
31964
  top: "bottom",
31908
31965
  right: "left",
31909
- bottom: "top",
31910
- center: "top"
31911
- }[LabelImage.imageSideForMarkerPosition(marker.x, marker.y)];
31966
+ bottom: "top"
31967
+ }[markerPosition];
31912
31968
  }
31969
+ const answerChoicesActive = index === activeMarkerIndex;
31970
+ const adjustPopoverDistance = {
31971
+ [side]: 15 // move the popover closer to the marker
31972
+ };
31913
31973
 
31914
- // TODO(michaelpolyak): Ideally we would always render markers
31915
- // wrapped in the popover. Setting `opened={false}` for those
31916
- // markers that are unselected (to hide their popup), this would
31917
- // keep the React tree more stable.
31918
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.Popover, {
31974
+ const adjustPillDistance = {
31975
+ [`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 5 // move pill further from marker
31976
+ };
31977
+
31978
+ const showAnswerChoice = !answerChoicesActive && !this.state.hideAnswers;
31979
+ return /*#__PURE__*/React__namespace.createElement("div", {
31980
+ key: `answers-${marker.x}.${marker.y}`
31981
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.Popover, {
31919
31982
  content: () => /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
31920
- style: styles$f.choicesPopover
31983
+ style: [adjustPopoverDistance, styles$f.choicesPopover]
31921
31984
  }, this.renderAnswerChoicesForMarker(index, marker)),
31922
31985
  placement: side,
31923
- opened: true,
31924
- key: `${marker.x}.${marker.y}`,
31925
- ref: node => this._selectedMarkerPopup = node
31926
- }, element);
31986
+ onClose: () => this.dismissMarkerPopup(),
31987
+ opened: answerChoicesActive,
31988
+ ref: node => this._selectedMarkerPopup = node,
31989
+ showTail: false,
31990
+ dismissEnabled: true
31991
+ }, element), !!marker.selected && showAnswerChoice && /*#__PURE__*/React__namespace.createElement(AnswerPill, {
31992
+ selectedAnswers: marker.selected,
31993
+ showCorrectness: showCorrectness,
31994
+ markerRef:
31995
+ // this throws a warning because it's inadvisable to
31996
+ // call ReactDOM fns in render. we aren't storing
31997
+ // actual refs in this array, so not much I can do
31998
+ // outside of a refactor.
31999
+ ReactDOM__namespace.findDOMNode(this._markers[index]),
32000
+ side: side,
32001
+ onClick: () => this.activateMarker(index),
32002
+ style: adjustPillDistance
32003
+ }));
31927
32004
  });
31928
32005
  }
31929
32006
  renderInstructions() {
@@ -31958,7 +32035,7 @@ class LabelImage extends React__namespace.Component {
31958
32035
  imageHeight
31959
32036
  } = this.props;
31960
32037
  const {
31961
- selectedMarkerIndex
32038
+ activeMarkerIndex
31962
32039
  } = this.state;
31963
32040
  return /*#__PURE__*/React__namespace.createElement("div", null, this.renderInstructions(), /*#__PURE__*/React__namespace.createElement("div", {
31964
32041
  className: aphrodite.css(styles$f.markersCanvas),
@@ -31974,7 +32051,7 @@ class LabelImage extends React__namespace.Component {
31974
32051
  // dismiss the popup. If the image is larger in size
31975
32052
  // than its rendered in the widget, this would
31976
32053
  // result in a zoom of the image.
31977
- selectedMarkerIndex !== -1 && styles$f.imageInteractionDisabled)
32054
+ activeMarkerIndex !== -1 && styles$f.imageInteractionDisabled)
31978
32055
  }, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref => {
31979
32056
  let {
31980
32057
  setAssetStatus
@@ -31986,7 +32063,18 @@ class LabelImage extends React__namespace.Component {
31986
32063
  height: imageHeight,
31987
32064
  setAssetStatus: setAssetStatus
31988
32065
  });
31989
- })), this.renderMarkers()));
32066
+ })), this.renderMarkers()), /*#__PURE__*/React__namespace.createElement(HideAnswersToggle, {
32067
+ areAnswersHidden: this.state.hideAnswers,
32068
+ onChange: hideAnswers => {
32069
+ this.props.analytics?.onAnalyticsEvent({
32070
+ type: "perseus:label-image:toggle-answers-hidden",
32071
+ payload: null
32072
+ });
32073
+ this.setState({
32074
+ hideAnswers
32075
+ });
32076
+ }
32077
+ }));
31990
32078
  }
31991
32079
  }
31992
32080
  const styles$f = aphrodite.StyleSheet.create({
@@ -32044,10 +32132,26 @@ const styles$f = aphrodite.StyleSheet.create({
32044
32132
  padding: 0
32045
32133
  }
32046
32134
  });
32135
+ const LabelImageWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
32136
+ const deps = useDependencies();
32137
+ return /*#__PURE__*/React__namespace.createElement(LabelImage, _extends({
32138
+ ref: ref,
32139
+ analytics: deps.analytics
32140
+ }, props));
32141
+ });
32142
+
32143
+ // HACK: Propogate "static" methods onto our wrapper component.
32144
+ // In the future we should adjust client apps to not depend on these static
32145
+ // methods and instead adjust Peresus to provide these facilities through
32146
+ // instance methods on our Renderers.
32147
+ // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
32148
+ LabelImageWithDependencies.validate = LabelImage.validate;
32149
+ // @ts-expect-error - TS2339 - Property 'gradeMarker' does not exist on type
32150
+ LabelImageWithDependencies.gradeMarker = LabelImage.gradeMarker;
32047
32151
  var LabelImage$1 = {
32048
32152
  name: "label-image",
32049
32153
  displayName: "Label Image",
32050
- widget: LabelImage,
32154
+ widget: LabelImageWithDependencies,
32051
32155
  accessible: true,
32052
32156
  isLintable: true
32053
32157
  };
@@ -44216,7 +44320,7 @@ const init = function (options) {
44216
44320
 
44217
44321
  // This file is processed by a Rollup plugin (replace) to inject the production
44218
44322
  const libName = "@khanacademy/perseus";
44219
- const libVersion = "17.0.0";
44323
+ const libVersion = "17.1.0";
44220
44324
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44221
44325
 
44222
44326
  const apiVersion = {