@khanacademy/perseus 1.2.0 → 1.3.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 +11 -0
- package/dist/es/index.css +3 -3
- package/dist/es/index.js +138 -167
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +3 -3
- package/dist/index.js +136 -165
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/styles/widgets/passage.less +7 -3
- package/src/widgets/__stories__/passage-ref.stories.jsx +6 -2
- package/src/widgets/__stories__/passage.stories.jsx +9 -1
- package/src/widgets/__stories__/radio.stories.jsx +11 -0
- package/src/widgets/__testdata__/passage-ref_testdata.js +62 -0
- package/src/widgets/__testdata__/passage_testdata.js +25 -0
- package/src/widgets/__testdata__/radio_testdata.js +52 -0
- package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +18 -18
- package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +36 -36
- package/src/widgets/__tests__/passage_test.jsx +6 -8
- package/src/widgets/__tests__/radio_test.jsx +3 -1
- package/src/widgets/passage.jsx +12 -41
- package/src/widgets/radio/widget.jsx +128 -129
- package/src/widgets/passage/get-line-height-for-node.js +0 -9
package/dist/index.css
CHANGED
|
@@ -3070,11 +3070,11 @@ body.mobile .perseus-matrix .math-output {
|
|
|
3070
3070
|
margin: 0 0 10px;
|
|
3071
3071
|
}
|
|
3072
3072
|
.perseus-widget-passage-container .passage-text div.paragraph {
|
|
3073
|
-
font-family: "Noto Serif", serif;
|
|
3074
3073
|
font-weight: 400;
|
|
3075
|
-
font-size:
|
|
3076
|
-
line-height:
|
|
3074
|
+
font-size: 16px;
|
|
3075
|
+
line-height: 20px;
|
|
3077
3076
|
text-indent: 20px;
|
|
3077
|
+
margin: 0;
|
|
3078
3078
|
}
|
|
3079
3079
|
.perseus-widget-passage-container .passage-text div.paragraph span {
|
|
3080
3080
|
text-indent: 0;
|
package/dist/index.js
CHANGED
|
@@ -25242,10 +25242,6 @@ const styles$w = aphrodite.StyleSheet.create({
|
|
|
25242
25242
|
}
|
|
25243
25243
|
});
|
|
25244
25244
|
|
|
25245
|
-
const getLineHeightForNode = (line1, line2) => {
|
|
25246
|
-
return $__default["default"](line2).offset().top - $__default["default"](line1).offset().top;
|
|
25247
|
-
};
|
|
25248
|
-
|
|
25249
25245
|
function getInitialParseState() {
|
|
25250
25246
|
return {
|
|
25251
25247
|
currentRef: [],
|
|
@@ -25613,65 +25609,40 @@ var PassageMarkdown = {
|
|
|
25613
25609
|
getInitialParseState
|
|
25614
25610
|
};
|
|
25615
25611
|
|
|
25616
|
-
// A fake paragraph to measure the line height of the passage
|
|
25617
|
-
//
|
|
25618
|
-
// the line height often ends up being a fractional number of pixels close to
|
|
25619
|
-
// 22 pixels.
|
|
25612
|
+
// A fake paragraph to measure the line height of the passage,
|
|
25613
|
+
// so we can adapt to browser zoom
|
|
25620
25614
|
class LineHeightMeasurer extends React__namespace.Component {
|
|
25621
25615
|
constructor() {
|
|
25622
25616
|
super(...arguments);
|
|
25623
25617
|
|
|
25624
|
-
_defineProperty(this, "
|
|
25625
|
-
|
|
25626
|
-
_defineProperty(this, "body", void 0);
|
|
25627
|
-
|
|
25628
|
-
_defineProperty(this, "end", void 0);
|
|
25618
|
+
_defineProperty(this, "_line", void 0);
|
|
25629
25619
|
}
|
|
25630
25620
|
|
|
25631
25621
|
measureLineHeight() {
|
|
25632
|
-
if (
|
|
25633
|
-
|
|
25622
|
+
if (!this._line) {
|
|
25623
|
+
return 0;
|
|
25634
25624
|
}
|
|
25635
25625
|
|
|
25636
|
-
return this.
|
|
25637
|
-
}
|
|
25638
|
-
|
|
25639
|
-
forceMeasureLineHeight() {
|
|
25640
|
-
const body = this.body;
|
|
25641
|
-
const end = this.end;
|
|
25642
|
-
|
|
25643
|
-
if (!body || !end) {
|
|
25644
|
-
return;
|
|
25645
|
-
} // Add some text which magically fills an entire line.
|
|
25646
|
-
|
|
25647
|
-
|
|
25648
|
-
body.textContent = " \u0080"; // Now, the line height is the difference between the top of the
|
|
25649
|
-
// second line and the top of the first line.
|
|
25650
|
-
|
|
25651
|
-
this._cachedLineHeight = getLineHeightForNode(body, end); // Clear out the first line so it doesn't overlap the passage.
|
|
25652
|
-
|
|
25653
|
-
body.textContent = "";
|
|
25626
|
+
return this._line.clientHeight;
|
|
25654
25627
|
}
|
|
25655
25628
|
|
|
25656
25629
|
render() {
|
|
25657
25630
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
25658
|
-
className: aphrodite.css(styles$v.measurer)
|
|
25659
|
-
|
|
25660
|
-
|
|
25631
|
+
className: aphrodite.css(styles$v.measurer),
|
|
25632
|
+
"aria-hidden": "true"
|
|
25633
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
25634
|
+
ref: ref => this._line = ref,
|
|
25661
25635
|
className: "paragraph"
|
|
25662
|
-
}
|
|
25663
|
-
ref: ref => this.end = ref
|
|
25664
|
-
})));
|
|
25636
|
+
}, "Line Height Measurement"));
|
|
25665
25637
|
}
|
|
25666
25638
|
|
|
25667
25639
|
}
|
|
25668
|
-
|
|
25669
25640
|
const styles$v = aphrodite.StyleSheet.create({
|
|
25670
25641
|
measurer: {
|
|
25671
25642
|
position: "absolute",
|
|
25672
|
-
width: "100%",
|
|
25673
25643
|
top: 0,
|
|
25674
25644
|
left: 0,
|
|
25645
|
+
visibility: "hidden",
|
|
25675
25646
|
// keep from blocking text selection
|
|
25676
25647
|
zIndex: -1
|
|
25677
25648
|
}
|
|
@@ -25718,7 +25689,7 @@ class Passage extends React__namespace.Component {
|
|
|
25718
25689
|
// listen for resize events.
|
|
25719
25690
|
|
|
25720
25691
|
|
|
25721
|
-
(_this$_lineHeightMeas = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas === void 0 ? void 0 : _this$_lineHeightMeas.
|
|
25692
|
+
(_this$_lineHeightMeas = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas === void 0 ? void 0 : _this$_lineHeightMeas.measureLineHeight();
|
|
25722
25693
|
|
|
25723
25694
|
this._updateState();
|
|
25724
25695
|
}, 500);
|
|
@@ -28046,6 +28017,129 @@ class Radio$1 extends React__namespace.Component {
|
|
|
28046
28017
|
});
|
|
28047
28018
|
}
|
|
28048
28019
|
|
|
28020
|
+
static validate(userInput, rubric) {
|
|
28021
|
+
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
28022
|
+
return sum + (selected ? 1 : 0);
|
|
28023
|
+
}, 0);
|
|
28024
|
+
|
|
28025
|
+
if (numSelected === 0) {
|
|
28026
|
+
return {
|
|
28027
|
+
type: "invalid",
|
|
28028
|
+
message: null
|
|
28029
|
+
};
|
|
28030
|
+
}
|
|
28031
|
+
|
|
28032
|
+
if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
28033
|
+
return {
|
|
28034
|
+
type: "invalid",
|
|
28035
|
+
message: i18n__namespace._("Please choose the correct number of answers.")
|
|
28036
|
+
}; // If NOTA and some other answer are checked, ...
|
|
28037
|
+
}
|
|
28038
|
+
|
|
28039
|
+
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
28040
|
+
return {
|
|
28041
|
+
type: "invalid",
|
|
28042
|
+
message: i18n__namespace._("'None of the above' may not be selected " + "when other answers are selected.")
|
|
28043
|
+
};
|
|
28044
|
+
}
|
|
28045
|
+
|
|
28046
|
+
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
28047
|
+
let isCorrect;
|
|
28048
|
+
|
|
28049
|
+
if (userInput.noneOfTheAboveIndex === i) {
|
|
28050
|
+
isCorrect = rubric.choices.every((choice, j) => {
|
|
28051
|
+
return i === j || !choice.correct;
|
|
28052
|
+
});
|
|
28053
|
+
} else {
|
|
28054
|
+
isCorrect = !!rubric.choices[i].correct;
|
|
28055
|
+
}
|
|
28056
|
+
|
|
28057
|
+
return isCorrect === selected;
|
|
28058
|
+
});
|
|
28059
|
+
return {
|
|
28060
|
+
type: "points",
|
|
28061
|
+
earned: correct ? 1 : 0,
|
|
28062
|
+
total: 1,
|
|
28063
|
+
message: null
|
|
28064
|
+
};
|
|
28065
|
+
}
|
|
28066
|
+
|
|
28067
|
+
static getUserInputFromProps(props) {
|
|
28068
|
+
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
28069
|
+
// future timeline implementers: this used to be {value: i} before
|
|
28070
|
+
// multiple select was added)
|
|
28071
|
+
if (props.choiceStates) {
|
|
28072
|
+
let noneOfTheAboveIndex = null;
|
|
28073
|
+
let noneOfTheAboveSelected = false;
|
|
28074
|
+
const choiceStates = props.choiceStates;
|
|
28075
|
+
const choicesSelected = choiceStates.map(() => false);
|
|
28076
|
+
const countChoices = props.countChoices;
|
|
28077
|
+
const numCorrect = props.numCorrect;
|
|
28078
|
+
|
|
28079
|
+
for (let i = 0; i < choicesSelected.length; i++) {
|
|
28080
|
+
const index = props.choices[i].originalIndex;
|
|
28081
|
+
choicesSelected[index] = choiceStates[i].selected;
|
|
28082
|
+
|
|
28083
|
+
if (props.choices[i].isNoneOfTheAbove) {
|
|
28084
|
+
noneOfTheAboveIndex = index;
|
|
28085
|
+
|
|
28086
|
+
if (choicesSelected[i]) {
|
|
28087
|
+
noneOfTheAboveSelected = true;
|
|
28088
|
+
}
|
|
28089
|
+
}
|
|
28090
|
+
}
|
|
28091
|
+
|
|
28092
|
+
return {
|
|
28093
|
+
countChoices,
|
|
28094
|
+
choicesSelected,
|
|
28095
|
+
numCorrect,
|
|
28096
|
+
noneOfTheAboveIndex,
|
|
28097
|
+
noneOfTheAboveSelected
|
|
28098
|
+
}; // Support legacy choiceState implementation
|
|
28099
|
+
}
|
|
28100
|
+
/* istanbul ignore if - props.values is deprecated */
|
|
28101
|
+
|
|
28102
|
+
|
|
28103
|
+
const {
|
|
28104
|
+
values
|
|
28105
|
+
} = props;
|
|
28106
|
+
|
|
28107
|
+
if (values) {
|
|
28108
|
+
let noneOfTheAboveIndex = null;
|
|
28109
|
+
let noneOfTheAboveSelected = false;
|
|
28110
|
+
const choicesSelected = [...values];
|
|
28111
|
+
const countChoices = props.countChoices;
|
|
28112
|
+
const numCorrect = props.numCorrect;
|
|
28113
|
+
const valuesLength = values.length;
|
|
28114
|
+
|
|
28115
|
+
for (let i = 0; i < valuesLength; i++) {
|
|
28116
|
+
const index = props.choices[i].originalIndex;
|
|
28117
|
+
choicesSelected[index] = values[i];
|
|
28118
|
+
|
|
28119
|
+
if (props.choices[i].isNoneOfTheAbove) {
|
|
28120
|
+
noneOfTheAboveIndex = index;
|
|
28121
|
+
|
|
28122
|
+
if (choicesSelected[i]) {
|
|
28123
|
+
noneOfTheAboveSelected = true;
|
|
28124
|
+
}
|
|
28125
|
+
}
|
|
28126
|
+
}
|
|
28127
|
+
|
|
28128
|
+
return {
|
|
28129
|
+
choicesSelected,
|
|
28130
|
+
noneOfTheAboveIndex,
|
|
28131
|
+
noneOfTheAboveSelected,
|
|
28132
|
+
countChoices,
|
|
28133
|
+
numCorrect
|
|
28134
|
+
};
|
|
28135
|
+
} // Nothing checked
|
|
28136
|
+
|
|
28137
|
+
|
|
28138
|
+
return {
|
|
28139
|
+
choicesSelected: props.choices.map(() => false)
|
|
28140
|
+
};
|
|
28141
|
+
}
|
|
28142
|
+
|
|
28049
28143
|
// TODO(LP-10672): I think this might be unused right now. I can't find anywhere
|
|
28050
28144
|
// that we pass a value to `.focus()` and it seems to have been used for
|
|
28051
28145
|
// adding hints when editing.
|
|
@@ -28176,129 +28270,6 @@ class Radio$1 extends React__namespace.Component {
|
|
|
28176
28270
|
});
|
|
28177
28271
|
}
|
|
28178
28272
|
|
|
28179
|
-
static validate(userInput, rubric) {
|
|
28180
|
-
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
28181
|
-
return sum + (selected ? 1 : 0);
|
|
28182
|
-
}, 0);
|
|
28183
|
-
|
|
28184
|
-
if (numSelected === 0) {
|
|
28185
|
-
return {
|
|
28186
|
-
type: "invalid",
|
|
28187
|
-
message: null
|
|
28188
|
-
};
|
|
28189
|
-
}
|
|
28190
|
-
|
|
28191
|
-
if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
28192
|
-
return {
|
|
28193
|
-
type: "invalid",
|
|
28194
|
-
message: i18n__namespace._("Please choose the correct number of answers.")
|
|
28195
|
-
}; // If NOTA and some other answer are checked, ...
|
|
28196
|
-
}
|
|
28197
|
-
|
|
28198
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
28199
|
-
return {
|
|
28200
|
-
type: "invalid",
|
|
28201
|
-
message: i18n__namespace._("'None of the above' may not be selected " + "when other answers are selected.")
|
|
28202
|
-
};
|
|
28203
|
-
}
|
|
28204
|
-
|
|
28205
|
-
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
28206
|
-
let isCorrect;
|
|
28207
|
-
|
|
28208
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
28209
|
-
isCorrect = rubric.choices.every((choice, j) => {
|
|
28210
|
-
return i === j || !choice.correct;
|
|
28211
|
-
});
|
|
28212
|
-
} else {
|
|
28213
|
-
isCorrect = !!rubric.choices[i].correct;
|
|
28214
|
-
}
|
|
28215
|
-
|
|
28216
|
-
return isCorrect === selected;
|
|
28217
|
-
});
|
|
28218
|
-
return {
|
|
28219
|
-
type: "points",
|
|
28220
|
-
earned: correct ? 1 : 0,
|
|
28221
|
-
total: 1,
|
|
28222
|
-
message: null
|
|
28223
|
-
};
|
|
28224
|
-
}
|
|
28225
|
-
|
|
28226
|
-
static getUserInputFromProps(props) {
|
|
28227
|
-
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
28228
|
-
// future timeline implementers: this used to be {value: i} before
|
|
28229
|
-
// multiple select was added)
|
|
28230
|
-
if (props.choiceStates) {
|
|
28231
|
-
let noneOfTheAboveIndex = null;
|
|
28232
|
-
let noneOfTheAboveSelected = false;
|
|
28233
|
-
const choiceStates = props.choiceStates;
|
|
28234
|
-
const choicesSelected = choiceStates.map(() => false);
|
|
28235
|
-
const countChoices = props.countChoices;
|
|
28236
|
-
const numCorrect = props.numCorrect;
|
|
28237
|
-
|
|
28238
|
-
for (let i = 0; i < choicesSelected.length; i++) {
|
|
28239
|
-
const index = props.choices[i].originalIndex;
|
|
28240
|
-
choicesSelected[index] = choiceStates[i].selected;
|
|
28241
|
-
|
|
28242
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
28243
|
-
noneOfTheAboveIndex = index;
|
|
28244
|
-
|
|
28245
|
-
if (choicesSelected[i]) {
|
|
28246
|
-
noneOfTheAboveSelected = true;
|
|
28247
|
-
}
|
|
28248
|
-
}
|
|
28249
|
-
}
|
|
28250
|
-
|
|
28251
|
-
return {
|
|
28252
|
-
countChoices,
|
|
28253
|
-
choicesSelected,
|
|
28254
|
-
numCorrect,
|
|
28255
|
-
noneOfTheAboveIndex,
|
|
28256
|
-
noneOfTheAboveSelected
|
|
28257
|
-
}; // Support legacy choiceState implementation
|
|
28258
|
-
}
|
|
28259
|
-
/* istanbul ignore if - props.values is deprecated */
|
|
28260
|
-
|
|
28261
|
-
|
|
28262
|
-
const {
|
|
28263
|
-
values
|
|
28264
|
-
} = props;
|
|
28265
|
-
|
|
28266
|
-
if (values) {
|
|
28267
|
-
let noneOfTheAboveIndex = null;
|
|
28268
|
-
let noneOfTheAboveSelected = false;
|
|
28269
|
-
const choicesSelected = [...values];
|
|
28270
|
-
const countChoices = props.countChoices;
|
|
28271
|
-
const numCorrect = props.numCorrect;
|
|
28272
|
-
const valuesLength = values.length;
|
|
28273
|
-
|
|
28274
|
-
for (let i = 0; i < valuesLength; i++) {
|
|
28275
|
-
const index = props.choices[i].originalIndex;
|
|
28276
|
-
choicesSelected[index] = values[i];
|
|
28277
|
-
|
|
28278
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
28279
|
-
noneOfTheAboveIndex = index;
|
|
28280
|
-
|
|
28281
|
-
if (choicesSelected[i]) {
|
|
28282
|
-
noneOfTheAboveSelected = true;
|
|
28283
|
-
}
|
|
28284
|
-
}
|
|
28285
|
-
}
|
|
28286
|
-
|
|
28287
|
-
return {
|
|
28288
|
-
choicesSelected,
|
|
28289
|
-
noneOfTheAboveIndex,
|
|
28290
|
-
noneOfTheAboveSelected,
|
|
28291
|
-
countChoices,
|
|
28292
|
-
numCorrect
|
|
28293
|
-
};
|
|
28294
|
-
} // Nothing checked
|
|
28295
|
-
|
|
28296
|
-
|
|
28297
|
-
return {
|
|
28298
|
-
choicesSelected: props.choices.map(() => false)
|
|
28299
|
-
};
|
|
28300
|
-
}
|
|
28301
|
-
|
|
28302
28273
|
}
|
|
28303
28274
|
|
|
28304
28275
|
_defineProperty(Radio$1, "defaultProps", {
|