@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.
- package/CHANGELOG.md +59 -0
- package/dist/dependencies.d.ts +1 -0
- package/dist/es/index.css +20 -0
- package/dist/es/index.js +443 -338
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +20 -0
- package/dist/index.js +444 -340
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +1 -0
- package/dist/strings.js +1 -0
- package/dist/widgets/label-image/answer-pill.d.ts +10 -0
- package/dist/widgets/label-image/hide-answers-toggle.d.ts +6 -0
- package/dist/widgets/label-image/marker.d.ts +6 -9
- package/dist/widgets/label-image/types.d.ts +1 -0
- package/dist/widgets/label-image.d.ts +101 -12
- package/package.json +30 -22
- package/src/__tests__/__snapshots__/renderer.test.tsx.snap +3 -3
- package/src/dependencies.ts +4 -0
- package/src/perseus-types.ts +2 -0
- package/src/styles/widgets/label-image.less +32 -0
- package/src/widgets/__stories__/label-image.stories.tsx +73 -3
- package/src/widgets/__testdata__/label-image.testdata.ts +118 -1
- package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +2 -2
- package/src/widgets/label-image/__stories__/answer-pill.stories.tsx +62 -0
- package/src/widgets/label-image/__tests__/answer-pill.test.tsx +83 -0
- package/src/widgets/label-image/__tests__/hide-answers-toggle.test.tsx +39 -0
- package/src/widgets/label-image/answer-choices.tsx +7 -1
- package/src/widgets/label-image/answer-pill.tsx +89 -0
- package/src/widgets/label-image/hide-answers-toggle.tsx +40 -0
- package/src/widgets/label-image/marker.tsx +116 -174
- package/src/widgets/label-image/types.ts +1 -0
- package/src/widgets/label-image.test.ts +142 -52
- package/src/widgets/label-image.tsx +212 -135
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/es/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
|
|
|
2
2
|
import { keyTranslator, CursorContext, createMathField, mathQuillInstance, getCursorContext, DesktopKeypad, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
3
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
4
4
|
import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
|
|
5
|
-
import { View, RenderStateRoot, Text } from '@khanacademy/wonder-blocks-core';
|
|
5
|
+
import { View, RenderStateRoot, useUniqueIdWithMock, Text } from '@khanacademy/wonder-blocks-core';
|
|
6
6
|
import * as i18n from '@khanacademy/wonder-blocks-i18n';
|
|
7
7
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
8
8
|
import classNames from 'classnames';
|
|
@@ -27,6 +27,10 @@ import Button from '@khanacademy/wonder-blocks-button';
|
|
|
27
27
|
import { Strut } from '@khanacademy/wonder-blocks-layout';
|
|
28
28
|
import { OptionItem, SingleSelect } from '@khanacademy/wonder-blocks-dropdown';
|
|
29
29
|
import debounce$1 from 'lodash.debounce';
|
|
30
|
+
import Pill from '@khanacademy/wonder-blocks-pill';
|
|
31
|
+
import { Popper } from 'react-popper';
|
|
32
|
+
import Switch from '@khanacademy/wonder-blocks-switch';
|
|
33
|
+
import { LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
30
34
|
import createReactClass from 'create-react-class';
|
|
31
35
|
import Link from '@khanacademy/wonder-blocks-link';
|
|
32
36
|
import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
@@ -238,7 +242,7 @@ class MathInput extends React.Component {
|
|
|
238
242
|
className = className + " " + this.props.className;
|
|
239
243
|
}
|
|
240
244
|
return /*#__PURE__*/React.createElement(View, {
|
|
241
|
-
style: [styles$
|
|
245
|
+
style: [styles$C.outerWrapper, this.state.focused && styles$C.wrapperFocused, this.props.hasError && styles$C.wrapperError]
|
|
242
246
|
}, /*#__PURE__*/React.createElement("div", {
|
|
243
247
|
style: {
|
|
244
248
|
display: "flex",
|
|
@@ -267,7 +271,7 @@ class MathInput extends React.Component {
|
|
|
267
271
|
var _this$props$keypadBut, _this$props;
|
|
268
272
|
return /*#__PURE__*/React.createElement(PopoverContentCore, {
|
|
269
273
|
closeButtonVisible: true,
|
|
270
|
-
style: styles$
|
|
274
|
+
style: styles$C.popoverContent
|
|
271
275
|
}, /*#__PURE__*/React.createElement(DesktopKeypad, _extends({
|
|
272
276
|
onAnalyticsEvent: this.props.analytics.onAnalyticsEvent,
|
|
273
277
|
extraKeys: this.props.extraKeys,
|
|
@@ -311,9 +315,9 @@ const MathInputIcon = ({
|
|
|
311
315
|
color = Color.offBlack;
|
|
312
316
|
break;
|
|
313
317
|
}
|
|
314
|
-
const dynamicClass = active || focused ? styles$
|
|
318
|
+
const dynamicClass = active || focused ? styles$C.iconActive : styles$C.iconInactive;
|
|
315
319
|
return /*#__PURE__*/React.createElement(View, {
|
|
316
|
-
style: [styles$
|
|
320
|
+
style: [styles$C.iconContainer, dynamicClass]
|
|
317
321
|
}, /*#__PURE__*/React.createElement("svg", {
|
|
318
322
|
xmlns: "http://www.w3.org/2000/svg",
|
|
319
323
|
width: "16",
|
|
@@ -358,7 +362,7 @@ const inputFocused = {
|
|
|
358
362
|
borderColor: Color.blue,
|
|
359
363
|
margin: -1
|
|
360
364
|
};
|
|
361
|
-
const styles$
|
|
365
|
+
const styles$C = StyleSheet.create({
|
|
362
366
|
iconContainer: {
|
|
363
367
|
display: "flex",
|
|
364
368
|
justifyContent: "center",
|
|
@@ -17553,33 +17557,33 @@ class Lint extends React.Component {
|
|
|
17553
17557
|
let warningText;
|
|
17554
17558
|
let warningTextStyle;
|
|
17555
17559
|
if (this.props.severity === Severity.Error) {
|
|
17556
|
-
severityStyle = styles$
|
|
17560
|
+
severityStyle = styles$B.indicatorError;
|
|
17557
17561
|
warningText = "Error";
|
|
17558
|
-
warningTextStyle = styles$
|
|
17562
|
+
warningTextStyle = styles$B.publishBlockingError;
|
|
17559
17563
|
} else if (this.props.severity === Severity.Warning) {
|
|
17560
|
-
severityStyle = styles$
|
|
17564
|
+
severityStyle = styles$B.indicatorWarning;
|
|
17561
17565
|
warningText = "Warning";
|
|
17562
|
-
warningTextStyle = styles$
|
|
17566
|
+
warningTextStyle = styles$B.warning;
|
|
17563
17567
|
} else {
|
|
17564
|
-
severityStyle = styles$
|
|
17568
|
+
severityStyle = styles$B.indicatorGuideline;
|
|
17565
17569
|
warningText = "Recommendation";
|
|
17566
|
-
warningTextStyle = styles$
|
|
17570
|
+
warningTextStyle = styles$B.warning;
|
|
17567
17571
|
}
|
|
17568
17572
|
return /*#__PURE__*/React.createElement("a", {
|
|
17569
17573
|
href: `https://khanacademy.org/r/linter-rules#${this.props.ruleName}`,
|
|
17570
17574
|
target: "lint-help-window",
|
|
17571
17575
|
className: css(style)
|
|
17572
17576
|
}, /*#__PURE__*/React.createElement("span", {
|
|
17573
|
-
className: css(styles$
|
|
17577
|
+
className: css(styles$B.indicator, severityStyle)
|
|
17574
17578
|
}, this.props.severity === 1 && /*#__PURE__*/React.createElement(InlineIcon, exclamationIcon)), /*#__PURE__*/React.createElement("div", {
|
|
17575
|
-
className: css(styles$
|
|
17579
|
+
className: css(styles$B.tooltip, tooltipAbove && styles$B.tooltipAbove)
|
|
17576
17580
|
}, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React.createElement("p", {
|
|
17577
17581
|
key: i,
|
|
17578
|
-
className: css(styles$
|
|
17582
|
+
className: css(styles$B.tooltipParagraph)
|
|
17579
17583
|
}, /*#__PURE__*/React.createElement("span", {
|
|
17580
17584
|
className: css(warningTextStyle)
|
|
17581
17585
|
}, warningText, ":", " "), m)), /*#__PURE__*/React.createElement("div", {
|
|
17582
|
-
className: css(styles$
|
|
17586
|
+
className: css(styles$B.tail, tooltipAbove && styles$B.tailAbove)
|
|
17583
17587
|
})));
|
|
17584
17588
|
};
|
|
17585
17589
|
}
|
|
@@ -17617,20 +17621,20 @@ class Lint extends React.Component {
|
|
|
17617
17621
|
}
|
|
17618
17622
|
if (blockHighlight) {
|
|
17619
17623
|
return /*#__PURE__*/React.createElement("span", {
|
|
17620
|
-
className: css(styles$
|
|
17621
|
-
}, this.renderLink(styles$
|
|
17624
|
+
className: css(styles$B.lintContainer, styles$B.lintContainerBlock)
|
|
17625
|
+
}, this.renderLink(styles$B.radioWidgetHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
|
|
17622
17626
|
}
|
|
17623
17627
|
if (inline) {
|
|
17624
17628
|
return /*#__PURE__*/React.createElement("span", {
|
|
17625
|
-
className: css(styles$
|
|
17626
|
-
}, this.renderLink(styles$
|
|
17629
|
+
className: css(styles$B.lintContainer)
|
|
17630
|
+
}, this.renderLink(styles$B.inlineHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
|
|
17627
17631
|
}
|
|
17628
17632
|
return /*#__PURE__*/React.createElement("div", {
|
|
17629
|
-
className: css(styles$
|
|
17630
|
-
}, this.renderLink(styles$
|
|
17633
|
+
className: css(styles$B.lintContainer)
|
|
17634
|
+
}, this.renderLink(styles$B.hoverTarget), /*#__PURE__*/React.createElement("div", null, children));
|
|
17631
17635
|
}
|
|
17632
17636
|
}
|
|
17633
|
-
const styles$
|
|
17637
|
+
const styles$B = StyleSheet.create({
|
|
17634
17638
|
// This is the class of the outermost element.
|
|
17635
17639
|
// We use relative positioning so that the lint indicator can be
|
|
17636
17640
|
// positioned absolutely relative to the position of the linty container.
|
|
@@ -19479,7 +19483,7 @@ class Renderer extends React.Component {
|
|
|
19479
19483
|
paddingLeft: margin,
|
|
19480
19484
|
paddingRight: margin
|
|
19481
19485
|
};
|
|
19482
|
-
const mobileInnerStyle = _extends({}, innerStyle, styles$
|
|
19486
|
+
const mobileInnerStyle = _extends({}, innerStyle, styles$A.mobileZoomableParentFix);
|
|
19483
19487
|
return /*#__PURE__*/React.createElement("div", {
|
|
19484
19488
|
key: state.key,
|
|
19485
19489
|
className: "perseus-block-math",
|
|
@@ -19600,7 +19604,7 @@ class Renderer extends React.Component {
|
|
|
19600
19604
|
paddingLeft: 0,
|
|
19601
19605
|
paddingRight: 0
|
|
19602
19606
|
};
|
|
19603
|
-
const mobileInnerStyle = _extends({}, innerStyle, styles$
|
|
19607
|
+
const mobileInnerStyle = _extends({}, innerStyle, styles$A.mobileZoomableParentFix);
|
|
19604
19608
|
const wrappedOutput = /*#__PURE__*/React.createElement("div", {
|
|
19605
19609
|
style: _extends({}, mobileInnerStyle, {
|
|
19606
19610
|
overflowX: "auto"
|
|
@@ -20245,7 +20249,7 @@ Renderer.defaultProps = {
|
|
|
20245
20249
|
onSerializedStateUpdated: () => {},
|
|
20246
20250
|
linterContext: PerseusLinter.linterContextDefault
|
|
20247
20251
|
};
|
|
20248
|
-
const styles$
|
|
20252
|
+
const styles$A = {
|
|
20249
20253
|
mobileZoomableParentFix: {
|
|
20250
20254
|
// NOTE(abdul): There is an issue where transform animations will
|
|
20251
20255
|
// cause the Zoomable component to disappear when running on the
|
|
@@ -21262,10 +21266,10 @@ class InputNumber extends React.Component {
|
|
|
21262
21266
|
// Note: This is _very_ similar to what `numeric-input.jsx` does. If
|
|
21263
21267
|
// you modify this, double-check if you also need to modify that
|
|
21264
21268
|
// component.
|
|
21265
|
-
const inputStyles = [styles$
|
|
21269
|
+
const inputStyles = [styles$z.default, this.props.size === "small" ? styles$z.small : null, this.props.rightAlign ? styles$z.rightAlign : styles$z.leftAlign];
|
|
21266
21270
|
// Unanswered
|
|
21267
21271
|
if (rubric && !this.props.currentValue) {
|
|
21268
|
-
inputStyles.push(styles$
|
|
21272
|
+
inputStyles.push(styles$z.answerStateUnanswered);
|
|
21269
21273
|
}
|
|
21270
21274
|
return /*#__PURE__*/React.createElement(InputWithExamples
|
|
21271
21275
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -21350,7 +21354,7 @@ InputNumber.defaultProps = {
|
|
|
21350
21354
|
apiOptions: ApiOptions.defaults,
|
|
21351
21355
|
linterContext: linterContextDefault
|
|
21352
21356
|
};
|
|
21353
|
-
const styles$
|
|
21357
|
+
const styles$z = StyleSheet.create({
|
|
21354
21358
|
default: {
|
|
21355
21359
|
width: 80,
|
|
21356
21360
|
height: "auto"
|
|
@@ -22351,7 +22355,7 @@ const FocusRing = function FocusRing(props) {
|
|
|
22351
22355
|
};
|
|
22352
22356
|
return /*#__PURE__*/React.createElement("span", {
|
|
22353
22357
|
"data-test-id": "focus-ring",
|
|
22354
|
-
className: css(styles$
|
|
22358
|
+
className: css(styles$y.ring),
|
|
22355
22359
|
style: style
|
|
22356
22360
|
}, children);
|
|
22357
22361
|
};
|
|
@@ -22360,7 +22364,7 @@ FocusRing.defaultProps = {
|
|
|
22360
22364
|
color: kaGreen$1,
|
|
22361
22365
|
multipleSelect: false
|
|
22362
22366
|
};
|
|
22363
|
-
const styles$
|
|
22367
|
+
const styles$y = StyleSheet.create({
|
|
22364
22368
|
ring: {
|
|
22365
22369
|
margin: "auto",
|
|
22366
22370
|
display: "inline-block",
|
|
@@ -22397,7 +22401,7 @@ function getChoiceLetter(pos) {
|
|
|
22397
22401
|
}
|
|
22398
22402
|
|
|
22399
22403
|
const CHOICE_ICON_SIZE = 24;
|
|
22400
|
-
const styles$
|
|
22404
|
+
const styles$x = StyleSheet.create({
|
|
22401
22405
|
iconWrapper: {
|
|
22402
22406
|
display: "inline-block",
|
|
22403
22407
|
position: "relative"
|
|
@@ -22420,7 +22424,7 @@ const CrossOutLine = function CrossOutLine(props) {
|
|
|
22420
22424
|
width: CROSS_OUT_LINE_SIZE,
|
|
22421
22425
|
height: CROSS_OUT_LINE_SIZE,
|
|
22422
22426
|
viewBox: `0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`,
|
|
22423
|
-
className: css(styles$
|
|
22427
|
+
className: css(styles$w.crossOutLine),
|
|
22424
22428
|
"data-test-id": "choice-icon__cross-out-line"
|
|
22425
22429
|
}, /*#__PURE__*/React.createElement("line", {
|
|
22426
22430
|
// The line stretches from the bottom-left to top-right.
|
|
@@ -22435,7 +22439,7 @@ const CrossOutLine = function CrossOutLine(props) {
|
|
|
22435
22439
|
strokeLinecap: "round"
|
|
22436
22440
|
}));
|
|
22437
22441
|
};
|
|
22438
|
-
const styles$
|
|
22442
|
+
const styles$w = StyleSheet.create({
|
|
22439
22443
|
crossOutLine: {
|
|
22440
22444
|
// Center the icon within the container.
|
|
22441
22445
|
position: "absolute",
|
|
@@ -22516,7 +22520,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
22516
22520
|
} = props;
|
|
22517
22521
|
const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct);
|
|
22518
22522
|
return /*#__PURE__*/React.createElement("div", {
|
|
22519
|
-
className: css(styles$
|
|
22523
|
+
className: css(styles$x.iconWrapper)
|
|
22520
22524
|
}, /*#__PURE__*/React.createElement(FocusRing, {
|
|
22521
22525
|
color: Color.blue,
|
|
22522
22526
|
visible: focused || hovered,
|
|
@@ -22525,13 +22529,13 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
22525
22529
|
// @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
|
|
22526
22530
|
style: dynamicStyles,
|
|
22527
22531
|
"data-test-id": "choice-icon__library-choice-icon",
|
|
22528
|
-
className: css(styles$
|
|
22532
|
+
className: css(styles$v.circle, showCorrectness && correct && styles$v.circleCorrect, showCorrectness && !correct && styles$v.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$v.circleIncorrectAnswered)
|
|
22529
22533
|
// used in BaseRadio editMode to check
|
|
22530
22534
|
// if we actually clicked on the radio icon
|
|
22531
22535
|
,
|
|
22532
22536
|
"data-is-radio-icon": true
|
|
22533
22537
|
}, /*#__PURE__*/React.createElement("div", {
|
|
22534
|
-
className: css(styles$
|
|
22538
|
+
className: css(styles$v.innerWrapper)
|
|
22535
22539
|
}, /*#__PURE__*/React.createElement(ChoiceInner, {
|
|
22536
22540
|
pos: pos,
|
|
22537
22541
|
showCorrectness: showCorrectness,
|
|
@@ -22540,7 +22544,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
22540
22544
|
color: dynamicStyles.borderColor
|
|
22541
22545
|
}));
|
|
22542
22546
|
};
|
|
22543
|
-
const styles$
|
|
22547
|
+
const styles$v = StyleSheet.create({
|
|
22544
22548
|
innerWrapper: {
|
|
22545
22549
|
width: CHOICE_ICON_SIZE,
|
|
22546
22550
|
height: CHOICE_ICON_SIZE,
|
|
@@ -22632,19 +22636,19 @@ const OptionStatus = function OptionStatus(props) {
|
|
|
22632
22636
|
}
|
|
22633
22637
|
let textStyle;
|
|
22634
22638
|
if (correct) {
|
|
22635
|
-
textStyle = styles$
|
|
22639
|
+
textStyle = styles$u.correct;
|
|
22636
22640
|
} else {
|
|
22637
22641
|
if (checked || previouslyAnswered) {
|
|
22638
|
-
textStyle = styles$
|
|
22642
|
+
textStyle = styles$u.incorrectAnswered;
|
|
22639
22643
|
} else {
|
|
22640
|
-
textStyle = styles$
|
|
22644
|
+
textStyle = styles$u.incorrect;
|
|
22641
22645
|
}
|
|
22642
22646
|
}
|
|
22643
22647
|
return /*#__PURE__*/React.createElement("div", {
|
|
22644
|
-
className: css(styles$
|
|
22648
|
+
className: css(styles$u.text, textStyle)
|
|
22645
22649
|
}, renderText(checked, correct, crossedOut));
|
|
22646
22650
|
};
|
|
22647
|
-
const styles$
|
|
22651
|
+
const styles$u = StyleSheet.create({
|
|
22648
22652
|
text: {
|
|
22649
22653
|
alignItems: "center",
|
|
22650
22654
|
display: "flex",
|
|
@@ -22719,8 +22723,8 @@ const Choice = function Choice(props) {
|
|
|
22719
22723
|
crossedOut: updatedCrossedOut
|
|
22720
22724
|
});
|
|
22721
22725
|
}
|
|
22722
|
-
const descriptionClassName = classNames("description", css(styles$
|
|
22723
|
-
const rationaleClassName = classNames("perseus-radio-rationale-content", css(styles$
|
|
22726
|
+
const descriptionClassName = classNames("description", css(styles$t.description));
|
|
22727
|
+
const rationaleClassName = classNames("perseus-radio-rationale-content", css(styles$t.rationale));
|
|
22724
22728
|
|
|
22725
22729
|
// We want to show the choices as dimmed out when the choices are disabled.
|
|
22726
22730
|
// However, we don't want to do this when we're in review mode in the
|
|
@@ -22880,7 +22884,7 @@ const Choice = function Choice(props) {
|
|
|
22880
22884
|
"data-test-id": `perseus-radio-rationale-content-${pos}`
|
|
22881
22885
|
}, rationale));
|
|
22882
22886
|
};
|
|
22883
|
-
const styles$
|
|
22887
|
+
const styles$t = StyleSheet.create({
|
|
22884
22888
|
description: {
|
|
22885
22889
|
display: "inline-block",
|
|
22886
22890
|
width: "100%"
|
|
@@ -23072,10 +23076,10 @@ const BaseRadio = function BaseRadio(props) {
|
|
|
23072
23076
|
const isMobile = apiOptions.isMobile;
|
|
23073
23077
|
const firstChoiceHighlighted = choices[0].highlighted;
|
|
23074
23078
|
const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
|
|
23075
|
-
const className = classNames("perseus-widget-radio", !editMode && "perseus-rendered-radio", css(styles$
|
|
23076
|
-
const instructionsClassName = classNames("instructions", css(styles$
|
|
23079
|
+
const className = classNames("perseus-widget-radio", !editMode && "perseus-rendered-radio", css(styles$s.radio, styles$s.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$s.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$s.radioContainerLastHighlighted));
|
|
23080
|
+
const instructionsClassName = classNames("instructions", css(styles$s.instructions, isMobile && styles$s.instructionsMobile));
|
|
23077
23081
|
const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect);
|
|
23078
|
-
const responsiveClassName = css(styles$
|
|
23082
|
+
const responsiveClassName = css(styles$s.responsiveFieldset);
|
|
23079
23083
|
const fieldset = /*#__PURE__*/React.createElement("fieldset", {
|
|
23080
23084
|
className: `perseus-widget-radio-fieldset ${responsiveClassName}`
|
|
23081
23085
|
}, /*#__PURE__*/React.createElement("legend", {
|
|
@@ -23126,7 +23130,7 @@ const BaseRadio = function BaseRadio(props) {
|
|
|
23126
23130
|
const aphroditeClassName = checked => {
|
|
23127
23131
|
// Whether or not to show correctness borders
|
|
23128
23132
|
// for this choice and the next choice.
|
|
23129
|
-
return css(sharedStyles.aboveScratchpad, styles$
|
|
23133
|
+
return 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);
|
|
23130
23134
|
};
|
|
23131
23135
|
|
|
23132
23136
|
// HACK(abdulrahman): Preloads the selection-state
|
|
@@ -23200,14 +23204,14 @@ const BaseRadio = function BaseRadio(props) {
|
|
|
23200
23204
|
// Allow for horizontal scrolling if content is too wide, which may be
|
|
23201
23205
|
// an issue especially on phones.
|
|
23202
23206
|
return /*#__PURE__*/React.createElement("div", {
|
|
23203
|
-
className: css(styles$
|
|
23207
|
+
className: css(styles$s.responsiveContainer)
|
|
23204
23208
|
}, fieldset);
|
|
23205
23209
|
};
|
|
23206
23210
|
BaseRadio.defaultProps = {
|
|
23207
23211
|
editMode: false,
|
|
23208
23212
|
multipleSelect: false
|
|
23209
23213
|
};
|
|
23210
|
-
const styles$
|
|
23214
|
+
const styles$s = StyleSheet.create({
|
|
23211
23215
|
instructions: {
|
|
23212
23216
|
display: "block",
|
|
23213
23217
|
color: gray17,
|
|
@@ -23855,9 +23859,9 @@ class Categorizer extends React.Component {
|
|
|
23855
23859
|
indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
|
|
23856
23860
|
}
|
|
23857
23861
|
const table = /*#__PURE__*/React.createElement("table", {
|
|
23858
|
-
className: "categorizer-table " + css(styles$
|
|
23862
|
+
className: "categorizer-table " + css(styles$r.mobileTable)
|
|
23859
23863
|
}, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
|
|
23860
|
-
className: css(styles$
|
|
23864
|
+
className: css(styles$r.emptyHeaderCell)
|
|
23861
23865
|
}), this.props.categories.map((category, i) => {
|
|
23862
23866
|
// Array index is the correct key here, as that's
|
|
23863
23867
|
// how category grading actually works -- no way
|
|
@@ -23865,7 +23869,7 @@ class Categorizer extends React.Component {
|
|
|
23865
23869
|
// middle. (If we later add that, this should be
|
|
23866
23870
|
// fixed.)
|
|
23867
23871
|
return /*#__PURE__*/React.createElement("th", {
|
|
23868
|
-
className: css(styles$
|
|
23872
|
+
className: css(styles$r.header),
|
|
23869
23873
|
key: i
|
|
23870
23874
|
}, /*#__PURE__*/React.createElement(Renderer, {
|
|
23871
23875
|
content: category,
|
|
@@ -23885,7 +23889,7 @@ class Categorizer extends React.Component {
|
|
|
23885
23889
|
})), self.props.categories.map((catName, catNum) => {
|
|
23886
23890
|
const selected = self.props.values[itemNum] === catNum;
|
|
23887
23891
|
return /*#__PURE__*/React.createElement("td", {
|
|
23888
|
-
className: "category " + css(styles$
|
|
23892
|
+
className: "category " + css(styles$r.cell),
|
|
23889
23893
|
key: catNum
|
|
23890
23894
|
}, /*#__PURE__*/React.createElement("div", {
|
|
23891
23895
|
className: ClassNames.INTERACTIVE,
|
|
@@ -23904,7 +23908,7 @@ class Categorizer extends React.Component {
|
|
|
23904
23908
|
onChange: this.onChange.bind(this, itemNum, catNum),
|
|
23905
23909
|
onClick: e => e.stopPropagation()
|
|
23906
23910
|
}), !isMobile && /*#__PURE__*/React.createElement("span", {
|
|
23907
|
-
className: css(styles$
|
|
23911
|
+
className: css(styles$r.radioSpan, selected && styles$r.checkedRadioSpan, this.props.static && selected && styles$r.staticCheckedRadioSpan)
|
|
23908
23912
|
}, selected ? /*#__PURE__*/React.createElement(InlineIcon, iconCircle) : /*#__PURE__*/React.createElement(InlineIcon, iconCircleThin))));
|
|
23909
23913
|
}));
|
|
23910
23914
|
/* eslint-enable max-len */
|
|
@@ -23916,7 +23920,7 @@ class Categorizer extends React.Component {
|
|
|
23916
23920
|
"categorizer-container": true,
|
|
23917
23921
|
"static-mode": this.props.static
|
|
23918
23922
|
});
|
|
23919
|
-
const inlineStyles = this.props.apiOptions.isMobile ? [styles$
|
|
23923
|
+
const inlineStyles = this.props.apiOptions.isMobile ? [styles$r.fullBleedContainer] : [];
|
|
23920
23924
|
return /*#__PURE__*/React.createElement("div", {
|
|
23921
23925
|
className: extraClassNames + " " + css(...inlineStyles)
|
|
23922
23926
|
}, table);
|
|
@@ -23961,7 +23965,7 @@ const pageMargin = 16;
|
|
|
23961
23965
|
|
|
23962
23966
|
// Stylesheets aren't directly testable
|
|
23963
23967
|
/* c8 ignore next */
|
|
23964
|
-
const styles$
|
|
23968
|
+
const styles$r = StyleSheet.create({
|
|
23965
23969
|
mobileTable: {
|
|
23966
23970
|
[mediaQueries.smOrSmaller]: {
|
|
23967
23971
|
minWidth: "auto"
|
|
@@ -24197,7 +24201,7 @@ class CSProgram extends React.Component {
|
|
|
24197
24201
|
// creator "went wild".
|
|
24198
24202
|
// http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
|
|
24199
24203
|
return /*#__PURE__*/React.createElement("div", {
|
|
24200
|
-
className: css(styles$
|
|
24204
|
+
className: css(styles$q.widthOverride, styleContainer && styles$q.container)
|
|
24201
24205
|
}, /*#__PURE__*/React.createElement("iframe", {
|
|
24202
24206
|
sandbox: sandboxOptions,
|
|
24203
24207
|
src: url,
|
|
@@ -24226,7 +24230,7 @@ CSProgram.defaultProps = {
|
|
|
24226
24230
|
// optional message
|
|
24227
24231
|
message: null
|
|
24228
24232
|
};
|
|
24229
|
-
const styles$
|
|
24233
|
+
const styles$q = StyleSheet.create({
|
|
24230
24234
|
// Override the inherited width from the perseus paragraph class
|
|
24231
24235
|
// 820 is minimum width for 420px editor and 400px canvas
|
|
24232
24236
|
widthOverride: {
|
|
@@ -24271,7 +24275,7 @@ class Definition extends React.Component {
|
|
|
24271
24275
|
}) => /*#__PURE__*/React.createElement(Popover, {
|
|
24272
24276
|
content: /*#__PURE__*/React.createElement(PopoverContentCore, {
|
|
24273
24277
|
color: "white",
|
|
24274
|
-
style: styles$
|
|
24278
|
+
style: styles$p.tooltipBody,
|
|
24275
24279
|
closeButtonVisible: true
|
|
24276
24280
|
}, /*#__PURE__*/React.createElement(Renderer, {
|
|
24277
24281
|
apiOptions: this.props.apiOptions,
|
|
@@ -24302,7 +24306,7 @@ Definition.defaultProps = {
|
|
|
24302
24306
|
togglePrompt: "define me",
|
|
24303
24307
|
definition: "definition goes here"
|
|
24304
24308
|
};
|
|
24305
|
-
const styles$
|
|
24309
|
+
const styles$p = {
|
|
24306
24310
|
tooltipBody: {
|
|
24307
24311
|
color: Color.offBlack,
|
|
24308
24312
|
fontSize: 20,
|
|
@@ -24466,15 +24470,15 @@ class Explanation extends React.Component {
|
|
|
24466
24470
|
const onClick = this._onClick;
|
|
24467
24471
|
if (isMobile) {
|
|
24468
24472
|
linkContainer = /*#__PURE__*/React.createElement("div", {
|
|
24469
|
-
className: css(styles$
|
|
24473
|
+
className: css(styles$o.linkContainer)
|
|
24470
24474
|
}, /*#__PURE__*/React.createElement("a", {
|
|
24471
|
-
className: css(styles$
|
|
24475
|
+
className: css(styles$o.mobileExplanationLink),
|
|
24472
24476
|
href: href,
|
|
24473
24477
|
onClick: onClick,
|
|
24474
24478
|
role: "button",
|
|
24475
24479
|
"aria-expanded": this.state.expanded
|
|
24476
24480
|
}, linkAnchor), this.state.expanded && /*#__PURE__*/React.createElement("svg", {
|
|
24477
|
-
className: css(styles$
|
|
24481
|
+
className: css(styles$o.disclosureArrow)
|
|
24478
24482
|
}, /*#__PURE__*/React.createElement("polygon", {
|
|
24479
24483
|
style: {
|
|
24480
24484
|
fill: backgroundColor
|
|
@@ -24487,13 +24491,13 @@ class Explanation extends React.Component {
|
|
|
24487
24491
|
// an anchor tag (if rendered in a modal) or a Wonder Blocks
|
|
24488
24492
|
// link component in lesson page, see: `article-page.jsx`.
|
|
24489
24493
|
linkContainer = /*#__PURE__*/React.createElement("div", {
|
|
24490
|
-
className: css(styles$
|
|
24494
|
+
className: css(styles$o.linkContainer, styles$o.articleLink)
|
|
24491
24495
|
}, /*#__PURE__*/React.createElement(Link, {
|
|
24492
|
-
className: css(styles$
|
|
24496
|
+
className: css(styles$o.explanationLink)
|
|
24493
24497
|
// HACK(michaelpolyak): WB Link doesn't passthrough
|
|
24494
24498
|
// class name.
|
|
24495
24499
|
,
|
|
24496
|
-
style: styles$
|
|
24500
|
+
style: styles$o.explanationLink,
|
|
24497
24501
|
href: href,
|
|
24498
24502
|
onClick: onClick,
|
|
24499
24503
|
role: "button",
|
|
@@ -24503,9 +24507,9 @@ class Explanation extends React.Component {
|
|
|
24503
24507
|
// NOTE: For exercises, the baseElements `Link` is an
|
|
24504
24508
|
// anchor tag, see: `perseus-api.jsx`.
|
|
24505
24509
|
linkContainer = /*#__PURE__*/React.createElement("div", {
|
|
24506
|
-
className: css(styles$
|
|
24510
|
+
className: css(styles$o.linkContainer, styles$o.exerciseLink)
|
|
24507
24511
|
}, /*#__PURE__*/React.createElement(Link, {
|
|
24508
|
-
className: css(styles$
|
|
24512
|
+
className: css(styles$o.explanationLink),
|
|
24509
24513
|
href: href,
|
|
24510
24514
|
onClick: onClick,
|
|
24511
24515
|
role: "button",
|
|
@@ -24513,11 +24517,11 @@ class Explanation extends React.Component {
|
|
|
24513
24517
|
}, `[${linkAnchor}]`));
|
|
24514
24518
|
}
|
|
24515
24519
|
}
|
|
24516
|
-
const expandedStyle = isMobile ? styles$
|
|
24520
|
+
const expandedStyle = isMobile ? styles$o.contentExpandedMobile : styles$o.contentExpanded;
|
|
24517
24521
|
return /*#__PURE__*/React.createElement("div", {
|
|
24518
|
-
className: css(styles$
|
|
24522
|
+
className: css(styles$o.container)
|
|
24519
24523
|
}, linkContainer, this.state.expanded && /*#__PURE__*/React.createElement("div", {
|
|
24520
|
-
className: css(styles$
|
|
24524
|
+
className: css(styles$o.content, isMobile && styles$o.contentMobile, this.state.expanded && expandedStyle)
|
|
24521
24525
|
// eslint-disable-next-line react/no-string-refs
|
|
24522
24526
|
,
|
|
24523
24527
|
ref: "content"
|
|
@@ -24541,7 +24545,7 @@ const verticalContentPadding = 10;
|
|
|
24541
24545
|
const arrowWidth = 30;
|
|
24542
24546
|
const arrowHeight = 14;
|
|
24543
24547
|
const backgroundColor = gray95;
|
|
24544
|
-
const styles$
|
|
24548
|
+
const styles$o = StyleSheet.create({
|
|
24545
24549
|
container: {
|
|
24546
24550
|
display: "inline",
|
|
24547
24551
|
position: "relative"
|
|
@@ -24646,15 +24650,15 @@ class GradedGroupAnswerBar extends React.Component {
|
|
|
24646
24650
|
onCheckAnswer,
|
|
24647
24651
|
onNextQuestion
|
|
24648
24652
|
} = this.props;
|
|
24649
|
-
const answerBarStyle = _extends({}, styles$
|
|
24653
|
+
const answerBarStyle = _extends({}, styles$n.answerBar, {
|
|
24650
24654
|
backgroundColor: answerBarState === "CORRECT" ? Color.offWhite : "white",
|
|
24651
24655
|
// Center the "Correct!" message only when there's no next question
|
|
24652
24656
|
justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
|
|
24653
24657
|
});
|
|
24654
24658
|
const message = answerBarState === "INCORRECT" ? /*#__PURE__*/React.createElement("span", {
|
|
24655
|
-
style: styles$
|
|
24659
|
+
style: styles$n.text
|
|
24656
24660
|
}, /*#__PURE__*/React.createElement("span", {
|
|
24657
|
-
style: styles$
|
|
24661
|
+
style: styles$n.tryAgainIcon
|
|
24658
24662
|
}, /*#__PURE__*/React.createElement(InlineIcon, iconTryAgain)), /*#__PURE__*/React.createElement("span", {
|
|
24659
24663
|
style: {
|
|
24660
24664
|
marginLeft: 8
|
|
@@ -24673,7 +24677,7 @@ class GradedGroupAnswerBar extends React.Component {
|
|
|
24673
24677
|
return /*#__PURE__*/React.createElement("div", {
|
|
24674
24678
|
style: answerBarStyle
|
|
24675
24679
|
}, /*#__PURE__*/React.createElement("span", {
|
|
24676
|
-
style: styles$
|
|
24680
|
+
style: styles$n.text
|
|
24677
24681
|
}, /*#__PURE__*/React.createElement("span", {
|
|
24678
24682
|
style: {
|
|
24679
24683
|
fontSize: 28,
|
|
@@ -24695,7 +24699,7 @@ class GradedGroupAnswerBar extends React.Component {
|
|
|
24695
24699
|
}
|
|
24696
24700
|
}
|
|
24697
24701
|
const fontSize = 17;
|
|
24698
|
-
const styles$
|
|
24702
|
+
const styles$n = {
|
|
24699
24703
|
answerBar: {
|
|
24700
24704
|
display: "flex",
|
|
24701
24705
|
alignItems: "center",
|
|
@@ -24881,7 +24885,7 @@ class GradedGroup extends React.Component {
|
|
|
24881
24885
|
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
24882
24886
|
gradeStatus = i18n._("Incorrect");
|
|
24883
24887
|
}
|
|
24884
|
-
const mobileClass = this.props.inGradedGroupSet ? css(styles$
|
|
24888
|
+
const mobileClass = this.props.inGradedGroupSet ? css(styles$m.gradedGroupInSet) : css(styles$m.gradedGroup);
|
|
24885
24889
|
const classes = classNames({
|
|
24886
24890
|
[mobileClass]: apiOptions.isMobile,
|
|
24887
24891
|
"perseus-graded-group": true,
|
|
@@ -24900,7 +24904,7 @@ class GradedGroup extends React.Component {
|
|
|
24900
24904
|
return /*#__PURE__*/React.createElement("div", {
|
|
24901
24905
|
className: classes
|
|
24902
24906
|
}, !!this.props.title && /*#__PURE__*/React.createElement("div", {
|
|
24903
|
-
className: css(styles$
|
|
24907
|
+
className: css(styles$m.title)
|
|
24904
24908
|
}, this.props.title), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props, {
|
|
24905
24909
|
// eslint-disable-next-line react/no-string-refs
|
|
24906
24910
|
ref: "renderer",
|
|
@@ -24932,7 +24936,7 @@ class GradedGroup extends React.Component {
|
|
|
24932
24936
|
}, i18n._("Next question")), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
|
|
24933
24937
|
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
|
|
24934
24938
|
tabIndex: "0",
|
|
24935
|
-
className: css(styles$
|
|
24939
|
+
className: css(styles$m.explanationTitle),
|
|
24936
24940
|
onClick: () => this.setState({
|
|
24937
24941
|
showHint: false
|
|
24938
24942
|
}),
|
|
@@ -24964,7 +24968,7 @@ class GradedGroup extends React.Component {
|
|
|
24964
24968
|
showHint: true
|
|
24965
24969
|
});
|
|
24966
24970
|
},
|
|
24967
|
-
className: css(styles$
|
|
24971
|
+
className: css(styles$m.showHintLink)
|
|
24968
24972
|
}, i18n._("Explain"))), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React.createElement(GradedGroupAnswerBar, {
|
|
24969
24973
|
apiOptions: apiOptions,
|
|
24970
24974
|
answerBarState: answerBarState,
|
|
@@ -24985,7 +24989,7 @@ GradedGroup.defaultProps = {
|
|
|
24985
24989
|
const traverseChildWidgets$3 = function traverseChildWidgets(props, traverseRenderer) {
|
|
24986
24990
|
return _.extend({}, props, traverseRenderer(props));
|
|
24987
24991
|
};
|
|
24988
|
-
const styles$
|
|
24992
|
+
const styles$m = StyleSheet.create({
|
|
24989
24993
|
gradedGroupInSet: {
|
|
24990
24994
|
// Reset a few desktop-only styles that come from graded-group.less
|
|
24991
24995
|
marginLeft: 0,
|
|
@@ -25058,18 +25062,18 @@ class Indicators extends React.Component {
|
|
|
25058
25062
|
return /*#__PURE__*/React.createElement("ul", {
|
|
25059
25063
|
// reduntantly add class name for use in .less files--
|
|
25060
25064
|
// the styles object key gets hashed
|
|
25061
|
-
className: classNames(css(styles$
|
|
25065
|
+
className: classNames(css(styles$l.indicatorContainer), "indicatorContainer")
|
|
25062
25066
|
}, this.props.gradedGroups.map(({
|
|
25063
25067
|
title
|
|
25064
25068
|
}, i) => /*#__PURE__*/React.createElement("li", {
|
|
25065
|
-
className: css(styles$
|
|
25069
|
+
className: css(styles$l.indicator),
|
|
25066
25070
|
key: title
|
|
25067
25071
|
}, /*#__PURE__*/React.createElement(Clickable, {
|
|
25068
25072
|
role: "button",
|
|
25069
25073
|
"aria-label": i18n._("Skip to %(title)s", {
|
|
25070
25074
|
title
|
|
25071
25075
|
}),
|
|
25072
|
-
style: styles$
|
|
25076
|
+
style: styles$l.indicatorButton,
|
|
25073
25077
|
onClick: () => this.props.onChangeCurrentGroup(i),
|
|
25074
25078
|
onKeyDown: e => this.handleKeyDown(e, i)
|
|
25075
25079
|
}, ({
|
|
@@ -25077,9 +25081,9 @@ class Indicators extends React.Component {
|
|
|
25077
25081
|
focused,
|
|
25078
25082
|
pressed
|
|
25079
25083
|
}) => /*#__PURE__*/React.createElement(View, {
|
|
25080
|
-
style: [styles$
|
|
25084
|
+
style: [styles$l.indicatorDot, (hovered || focused || pressed) && styles$l.indicatorDotFocused]
|
|
25081
25085
|
}, i === this.props.currentGroup && /*#__PURE__*/React.createElement(View, {
|
|
25082
|
-
style: styles$
|
|
25086
|
+
style: styles$l.indicatorDotActive
|
|
25083
25087
|
}, /*#__PURE__*/React.createElement("span", {
|
|
25084
25088
|
className: css(a11y.srOnly)
|
|
25085
25089
|
}, i18n._("Current"))))))));
|
|
@@ -25145,7 +25149,7 @@ class GradedGroupSet extends React.Component {
|
|
|
25145
25149
|
} = getDependencies();
|
|
25146
25150
|
if (JIPT.useJIPT && this.props.gradedGroups.length > 1) {
|
|
25147
25151
|
return /*#__PURE__*/React.createElement("div", {
|
|
25148
|
-
className: css(styles$
|
|
25152
|
+
className: css(styles$l.container)
|
|
25149
25153
|
}, this.props.gradedGroups.map((gradedGroup, i) => {
|
|
25150
25154
|
return (
|
|
25151
25155
|
/*#__PURE__*/
|
|
@@ -25168,13 +25172,13 @@ class GradedGroupSet extends React.Component {
|
|
|
25168
25172
|
const numGroups = this.props.gradedGroups.length;
|
|
25169
25173
|
const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
|
|
25170
25174
|
return /*#__PURE__*/React.createElement("div", {
|
|
25171
|
-
className: css(styles$
|
|
25175
|
+
className: css(styles$l.container)
|
|
25172
25176
|
}, /*#__PURE__*/React.createElement("div", {
|
|
25173
|
-
className: css(styles$
|
|
25177
|
+
className: css(styles$l.top)
|
|
25174
25178
|
}, /*#__PURE__*/React.createElement("div", {
|
|
25175
|
-
className: css(styles$
|
|
25179
|
+
className: css(styles$l.title)
|
|
25176
25180
|
}, currentGroup.title), /*#__PURE__*/React.createElement("div", {
|
|
25177
|
-
className: css(styles$
|
|
25181
|
+
className: css(styles$l.spacer)
|
|
25178
25182
|
}), /*#__PURE__*/React.createElement(Indicators, {
|
|
25179
25183
|
currentGroup: this.state.currentGroup,
|
|
25180
25184
|
gradedGroups: this.props.gradedGroups,
|
|
@@ -25216,7 +25220,7 @@ var GradedGroupSet$1 = {
|
|
|
25216
25220
|
tracking: "all",
|
|
25217
25221
|
isLintable: true
|
|
25218
25222
|
};
|
|
25219
|
-
const styles$
|
|
25223
|
+
const styles$l = StyleSheet.create({
|
|
25220
25224
|
top: {
|
|
25221
25225
|
display: "flex",
|
|
25222
25226
|
flexDirection: "row"
|
|
@@ -25321,7 +25325,7 @@ class ButtonGroup extends React.Component {
|
|
|
25321
25325
|
id: "" + i,
|
|
25322
25326
|
ref: "button" + i,
|
|
25323
25327
|
key: "" + i,
|
|
25324
|
-
className: css(styles$
|
|
25328
|
+
className: css(styles$k.buttonStyle, button.value === value && styles$k.selectedStyle),
|
|
25325
25329
|
onClick: () => this.toggleSelect(button.value)
|
|
25326
25330
|
}, button.content || "" + button.value);
|
|
25327
25331
|
});
|
|
@@ -25338,7 +25342,7 @@ ButtonGroup.defaultProps = {
|
|
|
25338
25342
|
value: null,
|
|
25339
25343
|
allowEmpty: true
|
|
25340
25344
|
};
|
|
25341
|
-
const styles$
|
|
25345
|
+
const styles$k = StyleSheet.create({
|
|
25342
25346
|
buttonStyle: {
|
|
25343
25347
|
backgroundColor: "white",
|
|
25344
25348
|
border: "1px solid #ccc",
|
|
@@ -30885,7 +30889,7 @@ class AnswerChoice extends React.Component {
|
|
|
30885
30889
|
checked
|
|
30886
30890
|
} = this.props;
|
|
30887
30891
|
return /*#__PURE__*/React.createElement("span", {
|
|
30888
|
-
className: css(styles$
|
|
30892
|
+
className: css(styles$j.checkmarkContainer)
|
|
30889
30893
|
}, checked && /*#__PURE__*/React.createElement(Icon, {
|
|
30890
30894
|
icon: iconCheckMedium,
|
|
30891
30895
|
size: 16,
|
|
@@ -30905,11 +30909,11 @@ class AnswerChoice extends React.Component {
|
|
|
30905
30909
|
} = this.state;
|
|
30906
30910
|
const answerId = `${groupName}-${index}`;
|
|
30907
30911
|
return /*#__PURE__*/React.createElement("div", {
|
|
30908
|
-
className: css(styles$
|
|
30912
|
+
className: css(styles$j.answerChoice, isInputFocused && styles$j.answerFocused),
|
|
30909
30913
|
onClick: e => this.handleClick(e),
|
|
30910
30914
|
onKeyDown: e => this.handleKeyDown(e)
|
|
30911
30915
|
}, /*#__PURE__*/React.createElement("span", {
|
|
30912
|
-
className: css(styles$
|
|
30916
|
+
className: css(styles$j.answerInputWrapper)
|
|
30913
30917
|
}, /*#__PURE__*/React.createElement("input", {
|
|
30914
30918
|
className: css(sharedStyles.perseusSrOnly),
|
|
30915
30919
|
checked: checked,
|
|
@@ -30977,7 +30981,13 @@ class AnswerChoices extends React.Component {
|
|
|
30977
30981
|
const {
|
|
30978
30982
|
groupName
|
|
30979
30983
|
} = this.state;
|
|
30980
|
-
return /*#__PURE__*/React.createElement("fieldset",
|
|
30984
|
+
return /*#__PURE__*/React.createElement("fieldset", {
|
|
30985
|
+
style: {
|
|
30986
|
+
border: "none",
|
|
30987
|
+
margin: 0,
|
|
30988
|
+
padding: 0
|
|
30989
|
+
}
|
|
30990
|
+
}, choices.map((choice, index) => /*#__PURE__*/React.createElement(AnswerChoice, _extends({}, choice, {
|
|
30981
30991
|
key: choice.content,
|
|
30982
30992
|
groupName: groupName,
|
|
30983
30993
|
index: index,
|
|
@@ -30989,7 +30999,7 @@ class AnswerChoices extends React.Component {
|
|
|
30989
30999
|
}))));
|
|
30990
31000
|
}
|
|
30991
31001
|
}
|
|
30992
|
-
const styles$
|
|
31002
|
+
const styles$j = StyleSheet.create({
|
|
30993
31003
|
answerChoice: {
|
|
30994
31004
|
display: "flex",
|
|
30995
31005
|
alignItems: "center",
|
|
@@ -31020,44 +31030,122 @@ const styles$h = StyleSheet.create({
|
|
|
31020
31030
|
}
|
|
31021
31031
|
});
|
|
31022
31032
|
|
|
31033
|
+
const AnswerPill = props => {
|
|
31034
|
+
const {
|
|
31035
|
+
selectedAnswers,
|
|
31036
|
+
showCorrectness,
|
|
31037
|
+
markerRef,
|
|
31038
|
+
side,
|
|
31039
|
+
onClick,
|
|
31040
|
+
style
|
|
31041
|
+
} = props;
|
|
31042
|
+
const idFactory = useUniqueIdWithMock();
|
|
31043
|
+
const answerString = selectedAnswers.length > 1 ?
|
|
31044
|
+
// always need `ngettext` for variable numbers even if we don't use the singular, see https://khanacademy.slack.com/archives/C0918TZ5G/p1700163024293079
|
|
31045
|
+
i18n.ngettext("%(num)s answer", "%(num)s answers", selectedAnswers.length) : selectedAnswers[0];
|
|
31046
|
+
|
|
31047
|
+
// It should no longer be possible to interact with an answer after it
|
|
31048
|
+
// has been labeled (answered) correctly.
|
|
31049
|
+
const correct = showCorrectness === "correct";
|
|
31050
|
+
const incorrect = showCorrectness === "incorrect";
|
|
31051
|
+
const bringToFront = {
|
|
31052
|
+
boxShadow: `0 8px 8px ${Color.offBlack64}`,
|
|
31053
|
+
zIndex: 1000
|
|
31054
|
+
};
|
|
31055
|
+
return /*#__PURE__*/React.createElement(Popper, {
|
|
31056
|
+
placement: side,
|
|
31057
|
+
referenceElement: markerRef,
|
|
31058
|
+
modifiers: [{
|
|
31059
|
+
name: "preventOverflow",
|
|
31060
|
+
options: {
|
|
31061
|
+
rootBoundary: "viewport"
|
|
31062
|
+
}
|
|
31063
|
+
}]
|
|
31064
|
+
}, ({
|
|
31065
|
+
ref,
|
|
31066
|
+
style: popperStyle
|
|
31067
|
+
}) => /*#__PURE__*/React.createElement(Pill, {
|
|
31068
|
+
size: "large",
|
|
31069
|
+
kind: "accent",
|
|
31070
|
+
id: idFactory.get("perseus-label-image-widget-answer-pill"),
|
|
31071
|
+
onClick: correct ? undefined : onClick,
|
|
31072
|
+
ref: ref,
|
|
31073
|
+
style: [style, popperStyle, {
|
|
31074
|
+
":hover": bringToFront,
|
|
31075
|
+
":focus": bringToFront
|
|
31076
|
+
}, correct && styles$i.correct, incorrect && styles$i.incorrect]
|
|
31077
|
+
}, /*#__PURE__*/React.createElement(Renderer, {
|
|
31078
|
+
content: answerString
|
|
31079
|
+
})));
|
|
31080
|
+
};
|
|
31081
|
+
const styles$i = StyleSheet.create({
|
|
31082
|
+
correct: {
|
|
31083
|
+
backgroundColor: "#00880b" // WB green darkened by 18%
|
|
31084
|
+
},
|
|
31085
|
+
|
|
31086
|
+
incorrect: {
|
|
31087
|
+
backgroundColor: Color.offBlack64
|
|
31088
|
+
}
|
|
31089
|
+
});
|
|
31090
|
+
|
|
31091
|
+
const strings = {
|
|
31092
|
+
hideAnswersToggleLabel: i18n._("Hide answer choices")
|
|
31093
|
+
};
|
|
31094
|
+
|
|
31095
|
+
const HideAnswersToggle = props => {
|
|
31096
|
+
const idFactory = useUniqueIdWithMock();
|
|
31097
|
+
const switchId = idFactory.get("hide-answers-toggle");
|
|
31098
|
+
const labelId = idFactory.get("hide-answers-label");
|
|
31099
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
31100
|
+
style: styles$h.switchWrapper
|
|
31101
|
+
}, /*#__PURE__*/React.createElement(Switch, {
|
|
31102
|
+
id: switchId,
|
|
31103
|
+
checked: props.areAnswersHidden,
|
|
31104
|
+
onChange: props.onChange,
|
|
31105
|
+
"aria-labelledby": labelId
|
|
31106
|
+
}), /*#__PURE__*/React.createElement(LabelMedium, {
|
|
31107
|
+
id: labelId,
|
|
31108
|
+
htmlFor: switchId
|
|
31109
|
+
}, strings.hideAnswersToggleLabel));
|
|
31110
|
+
};
|
|
31111
|
+
const styles$h = StyleSheet.create({
|
|
31112
|
+
switchWrapper: {
|
|
31113
|
+
display: "flex",
|
|
31114
|
+
flexDirection: "row",
|
|
31115
|
+
flexWrap: "wrap-reverse",
|
|
31116
|
+
alignItems: "center",
|
|
31117
|
+
gap: "0.5em",
|
|
31118
|
+
marginTop: "1em"
|
|
31119
|
+
}
|
|
31120
|
+
});
|
|
31121
|
+
|
|
31023
31122
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
31123
|
+
function shouldReduceMotion() {
|
|
31124
|
+
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
31125
|
+
return !mediaQuery || mediaQuery.matches;
|
|
31126
|
+
}
|
|
31024
31127
|
class Marker extends React.Component {
|
|
31025
31128
|
constructor(...args) {
|
|
31026
31129
|
super(...args);
|
|
31027
31130
|
// The marker icon element.
|
|
31028
31131
|
this._icon = void 0;
|
|
31029
|
-
this.state = {
|
|
31030
|
-
isFocused: false
|
|
31031
|
-
};
|
|
31032
|
-
}
|
|
31033
|
-
handleFocus() {
|
|
31034
|
-
this.setState({
|
|
31035
|
-
isFocused: true
|
|
31036
|
-
});
|
|
31037
|
-
}
|
|
31038
|
-
handleBlur() {
|
|
31039
|
-
this.setState({
|
|
31040
|
-
isFocused: false
|
|
31041
|
-
});
|
|
31042
31132
|
}
|
|
31043
31133
|
renderIcon() {
|
|
31044
31134
|
const {
|
|
31045
31135
|
selected,
|
|
31046
31136
|
showCorrectness,
|
|
31047
31137
|
showSelected,
|
|
31048
|
-
showPulsate
|
|
31138
|
+
showPulsate,
|
|
31139
|
+
focused
|
|
31049
31140
|
} = this.props;
|
|
31050
|
-
const {
|
|
31051
|
-
isFocused
|
|
31052
|
-
} = this.state;
|
|
31053
31141
|
|
|
31054
31142
|
// Only a single marker may be "selected" at a time.
|
|
31055
31143
|
// `showSelected` is a controlled prop, that may be set to `true` for
|
|
31056
31144
|
// one marker at a time.
|
|
31057
|
-
// `
|
|
31145
|
+
// `focused` is a controlled state, driven by focus events, and may
|
|
31058
31146
|
// only be `true` when there's no answer choices popup visible, and
|
|
31059
31147
|
// keyboard focus is given to the marker.
|
|
31060
|
-
const isSelected = showSelected ||
|
|
31148
|
+
const isSelected = showSelected || focused;
|
|
31061
31149
|
let innerIcon;
|
|
31062
31150
|
let iconStyles;
|
|
31063
31151
|
if (showCorrectness) {
|
|
@@ -31067,17 +31155,28 @@ class Marker extends React.Component {
|
|
|
31067
31155
|
color: Color.white
|
|
31068
31156
|
});
|
|
31069
31157
|
if (showCorrectness === "correct") {
|
|
31070
|
-
iconStyles = [styles$g.markerCorrect, isSelected && styles$g.
|
|
31158
|
+
iconStyles = [styles$g.markerGraded, styles$g.markerCorrect, isSelected && styles$g.markerSelected];
|
|
31071
31159
|
} else {
|
|
31072
|
-
iconStyles = [styles$g.markerIncorrect, isSelected && styles$g.
|
|
31160
|
+
iconStyles = [styles$g.markerGraded, styles$g.markerIncorrect, isSelected && styles$g.markerSelected];
|
|
31073
31161
|
}
|
|
31162
|
+
} else if (focused) {
|
|
31163
|
+
iconStyles = [styles$g.markerFocused, selected && selected.length > 0 && styles$g.markerFilled, showSelected && styles$g.markerSelected];
|
|
31074
31164
|
} else if (selected && selected.length > 0) {
|
|
31075
|
-
iconStyles = [styles$g.markerFilled, isSelected && styles$g.
|
|
31165
|
+
iconStyles = [styles$g.markerFilled, isSelected && styles$g.markerSelected];
|
|
31166
|
+
} else if (isSelected) {
|
|
31167
|
+
iconStyles = [styles$g.markerSelected];
|
|
31076
31168
|
} else {
|
|
31077
|
-
iconStyles = [
|
|
31169
|
+
iconStyles = [styles$g.markerPulsateBase, shouldReduceMotion() ? showPulsate && styles$g.markerUnfilledPulsateOnce : showPulsate && styles$g.markerUnfilledPulsateInfinite];
|
|
31078
31170
|
}
|
|
31079
|
-
|
|
31080
|
-
|
|
31171
|
+
if (isSelected && selected && selected.length === 0) {
|
|
31172
|
+
innerIcon = /*#__PURE__*/React.createElement(Icon, {
|
|
31173
|
+
icon: iconChevronDown,
|
|
31174
|
+
size: 8,
|
|
31175
|
+
color: Color.white
|
|
31176
|
+
});
|
|
31177
|
+
}
|
|
31178
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
31179
|
+
style: [styles$g.markerIcon, iconStyles],
|
|
31081
31180
|
ref: node => this._icon = node
|
|
31082
31181
|
}, innerIcon);
|
|
31083
31182
|
}
|
|
@@ -31100,13 +31199,12 @@ class Marker extends React.Component {
|
|
|
31100
31199
|
top: `${y}%`
|
|
31101
31200
|
},
|
|
31102
31201
|
tabIndex: isDisabled ? -1 : 0,
|
|
31103
|
-
onFocus:
|
|
31104
|
-
onBlur:
|
|
31105
|
-
|
|
31106
|
-
|
|
31107
|
-
|
|
31108
|
-
|
|
31109
|
-
,
|
|
31202
|
+
onFocus: this.props.onFocus,
|
|
31203
|
+
onBlur: this.props.onBlur,
|
|
31204
|
+
onClick: e => {
|
|
31205
|
+
e.preventDefault();
|
|
31206
|
+
this.props.onClick(e);
|
|
31207
|
+
},
|
|
31110
31208
|
onKeyDown: e => this.props.onKeyDown(e)
|
|
31111
31209
|
}, this.renderIcon());
|
|
31112
31210
|
}
|
|
@@ -31114,15 +31212,8 @@ class Marker extends React.Component {
|
|
|
31114
31212
|
Marker.defaultProps = {
|
|
31115
31213
|
selected: []
|
|
31116
31214
|
};
|
|
31117
|
-
const markerColor = "#1865f2";
|
|
31118
|
-
const selectedColor = "#2552b0";
|
|
31119
|
-
const activeColor = selectedColor;
|
|
31120
|
-
const correctColor = "#00a60e";
|
|
31121
|
-
const correctActiveColor = "#167b1f";
|
|
31122
|
-
const incorrectColor = "#909195";
|
|
31123
|
-
const incorrectActiveColor = "#6c6e73";
|
|
31124
|
-
const markerShadowColor = "rgba(33, 36, 44, 0.32)";
|
|
31125
31215
|
const lightShadowColor = "rgba(33, 36, 44, 0.16)";
|
|
31216
|
+
const markerSize = 20;
|
|
31126
31217
|
const styles$g = StyleSheet.create({
|
|
31127
31218
|
unstyledButton: {
|
|
31128
31219
|
padding: 0,
|
|
@@ -31135,9 +31226,12 @@ const styles$g = StyleSheet.create({
|
|
|
31135
31226
|
},
|
|
31136
31227
|
marker: {
|
|
31137
31228
|
position: "absolute",
|
|
31229
|
+
outline: "none",
|
|
31138
31230
|
// Center marker position based on it's maximum size.
|
|
31139
|
-
width:
|
|
31140
|
-
height:
|
|
31231
|
+
width: markerSize,
|
|
31232
|
+
height: markerSize,
|
|
31233
|
+
backgroundColor: Color.white,
|
|
31234
|
+
borderRadius: 30,
|
|
31141
31235
|
marginLeft: -15,
|
|
31142
31236
|
marginTop: -15
|
|
31143
31237
|
},
|
|
@@ -31147,136 +31241,92 @@ const styles$g = StyleSheet.create({
|
|
|
31147
31241
|
// The base and unfilled marker style.
|
|
31148
31242
|
markerIcon: {
|
|
31149
31243
|
display: "flex",
|
|
31150
|
-
|
|
31244
|
+
alignItems: "center",
|
|
31245
|
+
justifyContent: "center",
|
|
31151
31246
|
boxSizing: "content-box",
|
|
31152
|
-
width:
|
|
31153
|
-
height:
|
|
31154
|
-
|
|
31155
|
-
marginLeft: 5,
|
|
31247
|
+
width: markerSize,
|
|
31248
|
+
height: markerSize,
|
|
31249
|
+
marginLeft: 2,
|
|
31156
31250
|
cursor: "pointer",
|
|
31157
|
-
|
|
31158
|
-
|
|
31159
|
-
|
|
31160
|
-
|
|
31251
|
+
border: `2px solid ${Color.offBlack64}`,
|
|
31252
|
+
borderRadius: markerSize,
|
|
31253
|
+
boxShadow: `0 8px 8px ${Color.offBlack8}`,
|
|
31254
|
+
":hover": {
|
|
31255
|
+
outline: `2px solid ${Color.blue}`,
|
|
31256
|
+
outlineOffset: 2
|
|
31257
|
+
}
|
|
31161
31258
|
},
|
|
31162
|
-
|
|
31259
|
+
// The animation that presents the marker to the learner
|
|
31260
|
+
markerPulsateBase: {
|
|
31163
31261
|
animationName: {
|
|
31164
31262
|
"0%": {
|
|
31165
|
-
transform: "scale(1)"
|
|
31263
|
+
transform: "scale(1)",
|
|
31264
|
+
backgroundColor: Color.blue
|
|
31166
31265
|
},
|
|
31167
31266
|
"100%": {
|
|
31168
|
-
transform: "scale(1.
|
|
31267
|
+
transform: "scale(1.3)",
|
|
31268
|
+
backgroundColor: Color.blue
|
|
31169
31269
|
}
|
|
31170
31270
|
},
|
|
31171
31271
|
animationDirection: "alternate",
|
|
31172
31272
|
animationDuration: "0.8s",
|
|
31173
|
-
animationIterationCount: "infinite",
|
|
31174
31273
|
animationTimingFunction: "ease-in",
|
|
31175
|
-
transformOrigin: "50% 50%"
|
|
31274
|
+
transformOrigin: "50% 50%",
|
|
31275
|
+
animationIterationCount: "0"
|
|
31176
31276
|
},
|
|
31177
|
-
|
|
31178
|
-
"
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
position: "absolute",
|
|
31182
|
-
width: 20,
|
|
31183
|
-
height: 20,
|
|
31184
|
-
marginLeft: -4,
|
|
31185
|
-
marginTop: -4,
|
|
31186
|
-
border: `solid 2px ${selectedColor}`,
|
|
31187
|
-
borderRadius: 20
|
|
31188
|
-
},
|
|
31189
|
-
":active": {
|
|
31190
|
-
backgroundColor: activeColor,
|
|
31191
|
-
boxShadow: "none",
|
|
31192
|
-
"::before": {
|
|
31193
|
-
display: "none"
|
|
31194
|
-
}
|
|
31277
|
+
markerUnfilledPulsateInfinite: {
|
|
31278
|
+
animationIterationCount: "infinite",
|
|
31279
|
+
":hover": {
|
|
31280
|
+
outline: `0px solid ${Color.blue}`
|
|
31195
31281
|
}
|
|
31196
31282
|
},
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
|
|
31200
|
-
// Center icon within marker.
|
|
31201
|
-
marginLeft: 9,
|
|
31202
|
-
borderRadius: 8,
|
|
31203
|
-
boxShadow: `0 1px 1px 0 ${lightShadowColor}`
|
|
31283
|
+
markerUnfilledPulsateOnce: {
|
|
31284
|
+
// Doing the animation twice lets it ease-in and ease-out
|
|
31285
|
+
animationIterationCount: "2"
|
|
31204
31286
|
},
|
|
31205
|
-
|
|
31206
|
-
|
|
31207
|
-
|
|
31208
|
-
|
|
31209
|
-
|
|
31210
|
-
|
|
31211
|
-
|
|
31212
|
-
|
|
31213
|
-
|
|
31214
|
-
|
|
31215
|
-
|
|
31216
|
-
|
|
31217
|
-
|
|
31218
|
-
|
|
31219
|
-
boxShadow: "none",
|
|
31220
|
-
"::before": {
|
|
31221
|
-
display: "none"
|
|
31222
|
-
}
|
|
31287
|
+
markerFocused: {
|
|
31288
|
+
outline: `2px solid ${Color.blue}`,
|
|
31289
|
+
outlineOffset: 2
|
|
31290
|
+
},
|
|
31291
|
+
// The learner is making an initial selection
|
|
31292
|
+
markerSelected: {
|
|
31293
|
+
boxShadow: `0 8px 8px ${Color.offBlack8}`,
|
|
31294
|
+
border: `solid 4px ${Color.white}`,
|
|
31295
|
+
backgroundColor: Color.blue,
|
|
31296
|
+
borderRadius: markerSize,
|
|
31297
|
+
transform: "rotate(180deg)",
|
|
31298
|
+
":hover": {
|
|
31299
|
+
outline: `2px solid ${Color.blue}`,
|
|
31300
|
+
outlineOffset: -2
|
|
31223
31301
|
}
|
|
31224
31302
|
},
|
|
31225
|
-
|
|
31226
|
-
|
|
31227
|
-
|
|
31228
|
-
|
|
31229
|
-
|
|
31230
|
-
|
|
31231
|
-
|
|
31232
|
-
|
|
31303
|
+
// The learner has made a selection
|
|
31304
|
+
markerFilled: {
|
|
31305
|
+
width: markerSize - 2,
|
|
31306
|
+
height: markerSize - 2,
|
|
31307
|
+
borderRadius: markerSize - 2,
|
|
31308
|
+
backgroundColor: "#ECF3FE",
|
|
31309
|
+
marginLeft: 3,
|
|
31310
|
+
marginTop: 0,
|
|
31311
|
+
border: `4px solid ${Color.blue}`,
|
|
31233
31312
|
boxShadow: `0 1px 1px 0 ${lightShadowColor}`
|
|
31234
31313
|
},
|
|
31235
|
-
|
|
31236
|
-
|
|
31237
|
-
|
|
31238
|
-
display: "inline-block",
|
|
31239
|
-
position: "absolute",
|
|
31240
|
-
width: 28,
|
|
31241
|
-
height: 28,
|
|
31242
|
-
border: `solid 2px ${selectedColor}`,
|
|
31243
|
-
borderRadius: 28
|
|
31244
|
-
},
|
|
31245
|
-
":active": {
|
|
31246
|
-
backgroundColor: correctActiveColor,
|
|
31247
|
-
boxShadow: "none",
|
|
31248
|
-
"::before": {
|
|
31249
|
-
display: "none"
|
|
31250
|
-
}
|
|
31251
|
-
}
|
|
31252
|
-
},
|
|
31253
|
-
markerIncorrect: {
|
|
31254
|
-
width: 24,
|
|
31255
|
-
height: 24,
|
|
31314
|
+
markerGraded: {
|
|
31315
|
+
width: markerSize,
|
|
31316
|
+
height: markerSize,
|
|
31256
31317
|
marginLeft: 1,
|
|
31257
31318
|
marginTop: 1,
|
|
31258
31319
|
justifyContent: "center",
|
|
31259
31320
|
alignItems: "center",
|
|
31260
|
-
|
|
31261
|
-
boxShadow: `0
|
|
31321
|
+
border: `2px solid ${Color.white}`,
|
|
31322
|
+
boxShadow: `0 8px 8px ${Color.offBlack8}`
|
|
31262
31323
|
},
|
|
31263
|
-
|
|
31264
|
-
"
|
|
31265
|
-
|
|
31266
|
-
|
|
31267
|
-
|
|
31268
|
-
|
|
31269
|
-
height: 28,
|
|
31270
|
-
border: `solid 2px ${selectedColor}`,
|
|
31271
|
-
borderRadius: 28
|
|
31272
|
-
},
|
|
31273
|
-
":active": {
|
|
31274
|
-
backgroundColor: incorrectActiveColor,
|
|
31275
|
-
boxShadow: "none",
|
|
31276
|
-
"::before": {
|
|
31277
|
-
display: "none"
|
|
31278
|
-
}
|
|
31279
|
-
}
|
|
31324
|
+
markerCorrect: {
|
|
31325
|
+
background: "#00880b" // WB green darkened by 18%
|
|
31326
|
+
},
|
|
31327
|
+
|
|
31328
|
+
markerIncorrect: {
|
|
31329
|
+
background: Color.offBlack64
|
|
31280
31330
|
}
|
|
31281
31331
|
});
|
|
31282
31332
|
|
|
@@ -31354,7 +31404,20 @@ class LabelImage extends React.Component {
|
|
|
31354
31404
|
/**
|
|
31355
31405
|
* Determine the image side given a marker position (as percent of size).
|
|
31356
31406
|
*/
|
|
31357
|
-
static imageSideForMarkerPosition(x, y) {
|
|
31407
|
+
static imageSideForMarkerPosition(x, y, preferredDirection) {
|
|
31408
|
+
// Special handling for preferred size
|
|
31409
|
+
if (preferredDirection && preferredDirection !== "NONE") {
|
|
31410
|
+
if (preferredDirection === "LEFT" && x > 20) {
|
|
31411
|
+
return "right";
|
|
31412
|
+
} else if (preferredDirection === "RIGHT" && x < 80) {
|
|
31413
|
+
return "left";
|
|
31414
|
+
} else if (preferredDirection === "UP" && y > 20) {
|
|
31415
|
+
return "bottom";
|
|
31416
|
+
} else if (preferredDirection === "DOWN" && y < 80) {
|
|
31417
|
+
return "top";
|
|
31418
|
+
}
|
|
31419
|
+
}
|
|
31420
|
+
|
|
31358
31421
|
// Special handling for when marker is positioned near the horizontal
|
|
31359
31422
|
// edges of the image. We want to ensure the returned side would not
|
|
31360
31423
|
// result in a popup rendering that may overflow outside the page.
|
|
@@ -31509,51 +31572,14 @@ class LabelImage extends React.Component {
|
|
|
31509
31572
|
this._selectedMarkerPopup = void 0;
|
|
31510
31573
|
// The rendered list of answer choices for the currently selected marker.
|
|
31511
31574
|
this._answerChoices = void 0;
|
|
31512
|
-
this.handleDocumentClick = e => {
|
|
31513
|
-
// Dismiss open popup with answer choices if user clicks outside it.
|
|
31514
|
-
if (this._selectedMarkerPopup && this._answerChoices) {
|
|
31515
|
-
const answerChoices = ReactDOM.findDOMNode(this._answerChoices);
|
|
31516
|
-
const targetElement = e.target;
|
|
31517
|
-
// HACK(michaelpolyak): We want to determine if the click target is
|
|
31518
|
-
// contained within the popup layer. As there's no public interface
|
|
31519
|
-
// to get this layer from the popup, we traverse several levels of
|
|
31520
|
-
// answer choices parents to test whether the click target is
|
|
31521
|
-
// contained within.
|
|
31522
|
-
const containsEventTarget = (element, depth = 3) => element && (element.contains(targetElement) || depth > 0 && containsEventTarget(element.parentElement, depth - 1));
|
|
31523
|
-
if (!containsEventTarget(answerChoices)) {
|
|
31524
|
-
// Close popup and set focus back to the marker for which it was
|
|
31525
|
-
// open.
|
|
31526
|
-
this.dismissMarkerPopup();
|
|
31527
|
-
}
|
|
31528
|
-
}
|
|
31529
|
-
};
|
|
31530
|
-
this.handleDocumentKeyDown = e => {
|
|
31531
|
-
// Dismiss open popup with answer choices if user presses Escape key.
|
|
31532
|
-
if (this._selectedMarkerPopup && e.keyCode === 27) {
|
|
31533
|
-
// Ensure other listeners are not triggered on key down event that
|
|
31534
|
-
// closes the popup, as this will also dismiss the modal that the
|
|
31535
|
-
// widget may be rendered within.
|
|
31536
|
-
e.stopPropagation();
|
|
31537
|
-
|
|
31538
|
-
// Close popup and set focus back to the marker for which it was
|
|
31539
|
-
// open.
|
|
31540
|
-
this.dismissMarkerPopup();
|
|
31541
|
-
}
|
|
31542
|
-
};
|
|
31543
31575
|
this._markers = [];
|
|
31544
31576
|
this.state = {
|
|
31545
|
-
|
|
31546
|
-
|
|
31577
|
+
activeMarkerIndex: -1,
|
|
31578
|
+
focusedMarkerIndex: -1,
|
|
31579
|
+
markersInteracted: false,
|
|
31580
|
+
hideAnswers: false
|
|
31547
31581
|
};
|
|
31548
31582
|
}
|
|
31549
|
-
componentDidMount() {
|
|
31550
|
-
document.addEventListener("click", this.handleDocumentClick, true);
|
|
31551
|
-
document.addEventListener("keydown", this.handleDocumentKeyDown, true);
|
|
31552
|
-
}
|
|
31553
|
-
componentWillUnmount() {
|
|
31554
|
-
document.removeEventListener("click", this.handleDocumentClick, true);
|
|
31555
|
-
document.removeEventListener("keydown", this.handleDocumentKeyDown, true);
|
|
31556
|
-
}
|
|
31557
31583
|
simpleValidate(rubric) {
|
|
31558
31584
|
return LabelImage.validate(this.getUserInput(), rubric);
|
|
31559
31585
|
}
|
|
@@ -31589,17 +31615,17 @@ class LabelImage extends React.Component {
|
|
|
31589
31615
|
}
|
|
31590
31616
|
dismissMarkerPopup() {
|
|
31591
31617
|
const {
|
|
31592
|
-
|
|
31618
|
+
activeMarkerIndex
|
|
31593
31619
|
} = this.state;
|
|
31594
31620
|
|
|
31595
31621
|
// No popup should be open if there's no selected marker.
|
|
31596
|
-
if (
|
|
31622
|
+
if (activeMarkerIndex === -1) {
|
|
31597
31623
|
return;
|
|
31598
31624
|
}
|
|
31599
31625
|
this.setState({
|
|
31600
|
-
|
|
31626
|
+
activeMarkerIndex: -1
|
|
31601
31627
|
}, () => {
|
|
31602
|
-
const marker = this._markers[
|
|
31628
|
+
const marker = this._markers[activeMarkerIndex];
|
|
31603
31629
|
// Set focus on the just-deselected-marker, to enable to resume
|
|
31604
31630
|
// navigating between the markers using the keyboard.
|
|
31605
31631
|
if (marker) {
|
|
@@ -31625,16 +31651,15 @@ class LabelImage extends React.Component {
|
|
|
31625
31651
|
markers: updatedMarkers
|
|
31626
31652
|
});
|
|
31627
31653
|
}
|
|
31628
|
-
|
|
31654
|
+
activateMarker(index) {
|
|
31629
31655
|
const {
|
|
31630
|
-
|
|
31656
|
+
activeMarkerIndex
|
|
31631
31657
|
} = this.state;
|
|
31632
|
-
e.preventDefault();
|
|
31633
31658
|
|
|
31634
31659
|
// Select the marker, revealing answer choices.
|
|
31635
|
-
if (
|
|
31660
|
+
if (activeMarkerIndex !== index) {
|
|
31636
31661
|
this.setState({
|
|
31637
|
-
|
|
31662
|
+
activeMarkerIndex: index,
|
|
31638
31663
|
markersInteracted: true
|
|
31639
31664
|
}, () => {
|
|
31640
31665
|
// A delay is required to allow answer choices to first render
|
|
@@ -31704,9 +31729,9 @@ class LabelImage extends React.Component {
|
|
|
31704
31729
|
} = this.props;
|
|
31705
31730
|
|
|
31706
31731
|
// Compile the user selected answer choices.
|
|
31707
|
-
const selected = choices.filter((
|
|
31732
|
+
const selected = choices.filter((_, index) => selection[index]);
|
|
31708
31733
|
this.handleMarkerChange(index, _extends({}, markers[index], {
|
|
31709
|
-
selected
|
|
31734
|
+
selected: selected.length ? selected : undefined
|
|
31710
31735
|
}));
|
|
31711
31736
|
if (!multipleAnswers) {
|
|
31712
31737
|
this.dismissMarkerPopup();
|
|
@@ -31728,50 +31753,82 @@ class LabelImage extends React.Component {
|
|
|
31728
31753
|
checked: selected ? selected.includes(choice) : false
|
|
31729
31754
|
})),
|
|
31730
31755
|
multipleSelect: multipleAnswers,
|
|
31731
|
-
onChange: selection =>
|
|
31756
|
+
onChange: selection => {
|
|
31757
|
+
var _this$props$analytics;
|
|
31758
|
+
(_this$props$analytics = this.props.analytics) == null || _this$props$analytics.onAnalyticsEvent({
|
|
31759
|
+
type: "perseus:label-image:choiced-interacted-with",
|
|
31760
|
+
payload: null
|
|
31761
|
+
});
|
|
31762
|
+
this.handleAnswerChoicesChangeForMarker(index, selection);
|
|
31763
|
+
},
|
|
31732
31764
|
ref: node => this._answerChoices = node
|
|
31733
31765
|
}));
|
|
31734
31766
|
}
|
|
31735
31767
|
renderMarkers() {
|
|
31736
31768
|
const {
|
|
31737
31769
|
markers,
|
|
31738
|
-
questionCompleted
|
|
31770
|
+
questionCompleted,
|
|
31771
|
+
preferredPopoverDirection
|
|
31739
31772
|
} = this.props;
|
|
31740
31773
|
const {
|
|
31741
|
-
|
|
31774
|
+
activeMarkerIndex,
|
|
31742
31775
|
markersInteracted
|
|
31743
31776
|
} = this.state;
|
|
31744
31777
|
|
|
31745
31778
|
// Render all markers for widget.
|
|
31746
31779
|
return markers.map((marker, index) => {
|
|
31747
31780
|
const score = LabelImage.gradeMarker(marker);
|
|
31781
|
+
// Once the question is answered, show markers
|
|
31782
|
+
// with correct answers, otherwise passthrough
|
|
31783
|
+
// the correctness state.
|
|
31784
|
+
const showCorrectness = questionCompleted && score.hasAnswers && score.isCorrect ? "correct" : marker.showCorrectness;
|
|
31748
31785
|
const element = /*#__PURE__*/React.createElement(Marker, _extends({}, marker, {
|
|
31749
|
-
|
|
31750
|
-
|
|
31751
|
-
// the correctness state.
|
|
31752
|
-
showCorrectness: questionCompleted && score.hasAnswers && score.isCorrect ? "correct" : marker.showCorrectness,
|
|
31753
|
-
showSelected: index === selectedMarkerIndex,
|
|
31786
|
+
showCorrectness: showCorrectness,
|
|
31787
|
+
showSelected: index === activeMarkerIndex,
|
|
31754
31788
|
showPulsate: !markersInteracted,
|
|
31755
31789
|
key: `${marker.x}.${marker.y}`,
|
|
31756
|
-
onClick:
|
|
31790
|
+
onClick: () => {
|
|
31791
|
+
var _this$props$analytics2;
|
|
31792
|
+
(_this$props$analytics2 = this.props.analytics) == null || _this$props$analytics2.onAnalyticsEvent({
|
|
31793
|
+
type: "perseus:label-image:marker-interacted-with",
|
|
31794
|
+
payload: null
|
|
31795
|
+
});
|
|
31796
|
+
this.activateMarker(index);
|
|
31797
|
+
},
|
|
31757
31798
|
onKeyDown: e => this.handleMarkerKeyDown(index, e),
|
|
31758
|
-
ref: node => this._markers[index] = node
|
|
31799
|
+
ref: node => this._markers[index] = node,
|
|
31800
|
+
focused: index === this.state.focusedMarkerIndex,
|
|
31801
|
+
onFocus: () => {
|
|
31802
|
+
this.setState({
|
|
31803
|
+
focusedMarkerIndex: index
|
|
31804
|
+
});
|
|
31805
|
+
},
|
|
31806
|
+
onBlur: () => {
|
|
31807
|
+
if (index === this.state.focusedMarkerIndex) {
|
|
31808
|
+
this.setState({
|
|
31809
|
+
focusedMarkerIndex: -1
|
|
31810
|
+
});
|
|
31811
|
+
}
|
|
31812
|
+
}
|
|
31759
31813
|
}));
|
|
31760
31814
|
|
|
31761
|
-
// The user selected marker is wrapped with a popup that shows its
|
|
31762
|
-
// answer choices, otherwise it's returned as is.
|
|
31763
|
-
if (index !== selectedMarkerIndex) {
|
|
31764
|
-
return element;
|
|
31765
|
-
}
|
|
31766
|
-
|
|
31767
31815
|
// Determine whether page is rendered in a narrow browser window.
|
|
31768
31816
|
const isNarrowPage = window.matchMedia(mediaQueries.xsOrSmaller.replace("@media ", "")).matches;
|
|
31817
|
+
|
|
31818
|
+
// Determine whether the image is wider than it is tall.
|
|
31819
|
+
const isWideImage = this.props.imageWidth / 2 > this.props.imageHeight;
|
|
31769
31820
|
let side;
|
|
31821
|
+
let markerPosition;
|
|
31770
31822
|
// Position popup closest to the center, preferring it renders
|
|
31771
31823
|
// entirely within the image area.
|
|
31772
|
-
if (isNarrowPage) {
|
|
31824
|
+
if (isNarrowPage || isWideImage) {
|
|
31773
31825
|
side = marker.y > 50 ? "top" : "bottom";
|
|
31826
|
+
markerPosition = marker.y > 50 ? "bottom" : "top";
|
|
31774
31827
|
} else {
|
|
31828
|
+
markerPosition = LabelImage.imageSideForMarkerPosition(marker.x, marker.y, preferredPopoverDirection);
|
|
31829
|
+
if (markerPosition === "center") {
|
|
31830
|
+
markerPosition = "bottom";
|
|
31831
|
+
}
|
|
31775
31832
|
// This mirrors the calculated side of where the marker is
|
|
31776
31833
|
// located within the image, so that popup appears closer to
|
|
31777
31834
|
// the image center.
|
|
@@ -31779,24 +31836,44 @@ class LabelImage extends React.Component {
|
|
|
31779
31836
|
left: "right",
|
|
31780
31837
|
top: "bottom",
|
|
31781
31838
|
right: "left",
|
|
31782
|
-
bottom: "top"
|
|
31783
|
-
|
|
31784
|
-
}[LabelImage.imageSideForMarkerPosition(marker.x, marker.y)];
|
|
31839
|
+
bottom: "top"
|
|
31840
|
+
}[markerPosition];
|
|
31785
31841
|
}
|
|
31842
|
+
const answerChoicesActive = index === activeMarkerIndex;
|
|
31843
|
+
const adjustPopoverDistance = {
|
|
31844
|
+
[side]: 15 // move the popover closer to the marker
|
|
31845
|
+
};
|
|
31786
31846
|
|
|
31787
|
-
|
|
31788
|
-
|
|
31789
|
-
|
|
31790
|
-
|
|
31791
|
-
|
|
31847
|
+
const adjustPillDistance = {
|
|
31848
|
+
[`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 5 // move pill further from marker
|
|
31849
|
+
};
|
|
31850
|
+
|
|
31851
|
+
const showAnswerChoice = !answerChoicesActive && !this.state.hideAnswers;
|
|
31852
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
31853
|
+
key: `answers-${marker.x}.${marker.y}`
|
|
31854
|
+
}, /*#__PURE__*/React.createElement(Popover, {
|
|
31792
31855
|
content: () => /*#__PURE__*/React.createElement(PopoverContentCore, {
|
|
31793
|
-
style: styles$f.choicesPopover
|
|
31856
|
+
style: [adjustPopoverDistance, styles$f.choicesPopover]
|
|
31794
31857
|
}, this.renderAnswerChoicesForMarker(index, marker)),
|
|
31795
31858
|
placement: side,
|
|
31796
|
-
|
|
31797
|
-
|
|
31798
|
-
ref: node => this._selectedMarkerPopup = node
|
|
31799
|
-
|
|
31859
|
+
onClose: () => this.dismissMarkerPopup(),
|
|
31860
|
+
opened: answerChoicesActive,
|
|
31861
|
+
ref: node => this._selectedMarkerPopup = node,
|
|
31862
|
+
showTail: false,
|
|
31863
|
+
dismissEnabled: true
|
|
31864
|
+
}, element), !!marker.selected && showAnswerChoice && /*#__PURE__*/React.createElement(AnswerPill, {
|
|
31865
|
+
selectedAnswers: marker.selected,
|
|
31866
|
+
showCorrectness: showCorrectness,
|
|
31867
|
+
markerRef:
|
|
31868
|
+
// this throws a warning because it's inadvisable to
|
|
31869
|
+
// call ReactDOM fns in render. we aren't storing
|
|
31870
|
+
// actual refs in this array, so not much I can do
|
|
31871
|
+
// outside of a refactor.
|
|
31872
|
+
ReactDOM.findDOMNode(this._markers[index]),
|
|
31873
|
+
side: side,
|
|
31874
|
+
onClick: () => this.activateMarker(index),
|
|
31875
|
+
style: adjustPillDistance
|
|
31876
|
+
}));
|
|
31800
31877
|
});
|
|
31801
31878
|
}
|
|
31802
31879
|
renderInstructions() {
|
|
@@ -31831,7 +31908,7 @@ class LabelImage extends React.Component {
|
|
|
31831
31908
|
imageHeight
|
|
31832
31909
|
} = this.props;
|
|
31833
31910
|
const {
|
|
31834
|
-
|
|
31911
|
+
activeMarkerIndex
|
|
31835
31912
|
} = this.state;
|
|
31836
31913
|
return /*#__PURE__*/React.createElement("div", null, this.renderInstructions(), /*#__PURE__*/React.createElement("div", {
|
|
31837
31914
|
className: css(styles$f.markersCanvas),
|
|
@@ -31847,7 +31924,7 @@ class LabelImage extends React.Component {
|
|
|
31847
31924
|
// dismiss the popup. If the image is larger in size
|
|
31848
31925
|
// than its rendered in the widget, this would
|
|
31849
31926
|
// result in a zoom of the image.
|
|
31850
|
-
|
|
31927
|
+
activeMarkerIndex !== -1 && styles$f.imageInteractionDisabled)
|
|
31851
31928
|
}, /*#__PURE__*/React.createElement(context$1.Consumer, null, ({
|
|
31852
31929
|
setAssetStatus
|
|
31853
31930
|
}) => /*#__PURE__*/React.createElement(SvgImage, {
|
|
@@ -31856,7 +31933,19 @@ class LabelImage extends React.Component {
|
|
|
31856
31933
|
width: imageWidth,
|
|
31857
31934
|
height: imageHeight,
|
|
31858
31935
|
setAssetStatus: setAssetStatus
|
|
31859
|
-
}))), this.renderMarkers())
|
|
31936
|
+
}))), this.renderMarkers()), /*#__PURE__*/React.createElement(HideAnswersToggle, {
|
|
31937
|
+
areAnswersHidden: this.state.hideAnswers,
|
|
31938
|
+
onChange: hideAnswers => {
|
|
31939
|
+
var _this$props$analytics3;
|
|
31940
|
+
(_this$props$analytics3 = this.props.analytics) == null || _this$props$analytics3.onAnalyticsEvent({
|
|
31941
|
+
type: "perseus:label-image:toggle-answers-hidden",
|
|
31942
|
+
payload: null
|
|
31943
|
+
});
|
|
31944
|
+
this.setState({
|
|
31945
|
+
hideAnswers
|
|
31946
|
+
});
|
|
31947
|
+
}
|
|
31948
|
+
}));
|
|
31860
31949
|
}
|
|
31861
31950
|
}
|
|
31862
31951
|
const styles$f = StyleSheet.create({
|
|
@@ -31913,10 +32002,26 @@ const styles$f = StyleSheet.create({
|
|
|
31913
32002
|
padding: 0
|
|
31914
32003
|
}
|
|
31915
32004
|
});
|
|
32005
|
+
const LabelImageWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
32006
|
+
const deps = useDependencies();
|
|
32007
|
+
return /*#__PURE__*/React.createElement(LabelImage, _extends({
|
|
32008
|
+
ref: ref,
|
|
32009
|
+
analytics: deps.analytics
|
|
32010
|
+
}, props));
|
|
32011
|
+
});
|
|
32012
|
+
|
|
32013
|
+
// HACK: Propogate "static" methods onto our wrapper component.
|
|
32014
|
+
// In the future we should adjust client apps to not depend on these static
|
|
32015
|
+
// methods and instead adjust Peresus to provide these facilities through
|
|
32016
|
+
// instance methods on our Renderers.
|
|
32017
|
+
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
32018
|
+
LabelImageWithDependencies.validate = LabelImage.validate;
|
|
32019
|
+
// @ts-expect-error - TS2339 - Property 'gradeMarker' does not exist on type
|
|
32020
|
+
LabelImageWithDependencies.gradeMarker = LabelImage.gradeMarker;
|
|
31916
32021
|
var LabelImage$1 = {
|
|
31917
32022
|
name: "label-image",
|
|
31918
32023
|
displayName: "Label Image",
|
|
31919
|
-
widget:
|
|
32024
|
+
widget: LabelImageWithDependencies,
|
|
31920
32025
|
accessible: true,
|
|
31921
32026
|
isLintable: true
|
|
31922
32027
|
};
|
|
@@ -44079,7 +44184,7 @@ const init = function init(options) {
|
|
|
44079
44184
|
|
|
44080
44185
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44081
44186
|
const libName = "@khanacademy/perseus";
|
|
44082
|
-
const libVersion = "17.
|
|
44187
|
+
const libVersion = "17.1.0";
|
|
44083
44188
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44084
44189
|
|
|
44085
44190
|
const apiVersion = {
|