@khanacademy/perseus 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/es/index.css +3 -3
  3. package/dist/es/index.js +145 -179
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +3 -3
  6. package/dist/index.js +143 -177
  7. package/dist/index.js.map +1 -1
  8. package/package.json +2 -2
  9. package/src/index.js +1 -0
  10. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +21 -11
  11. package/src/styles/widgets/passage.less +7 -3
  12. package/src/widgets/__stories__/passage-ref.stories.jsx +6 -2
  13. package/src/widgets/__stories__/passage.stories.jsx +9 -1
  14. package/src/widgets/__stories__/radio.stories.jsx +11 -0
  15. package/src/widgets/__testdata__/passage-ref_testdata.js +62 -0
  16. package/src/widgets/__testdata__/passage_testdata.js +25 -0
  17. package/src/widgets/__testdata__/radio_testdata.js +52 -0
  18. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +21 -11
  19. package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +18 -18
  20. package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +36 -36
  21. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +221 -117
  22. package/src/widgets/__tests__/passage_test.jsx +6 -8
  23. package/src/widgets/__tests__/radio_test.jsx +3 -1
  24. package/src/widgets/passage.jsx +12 -41
  25. package/src/widgets/radio/__stories__/sat-styles.stories.jsx +191 -0
  26. package/src/widgets/radio/base-radio.jsx +1 -2
  27. package/src/widgets/radio/choice.jsx +4 -10
  28. package/src/widgets/radio/widget.jsx +128 -129
  29. package/src/widgets/passage/get-line-height-for-node.js +0 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @khanacademy/perseus
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 9b3bf30f: CSS bug fixes for SAT Radio widget
8
+ - Updated dependencies [113953e8]
9
+ - @khanacademy/math-input@0.6.4
10
+
11
+ ## 1.3.0
12
+
13
+ ### Minor Changes
14
+
15
+ - e783be76: Fix passage fonts because content depends on it being an exact size
16
+
17
+ ### Patch Changes
18
+
19
+ - ec92d308: Fix bug with Passage where line numbers stopped abruptly
20
+ - dd55b5e4: Update gutter line numbers in passage
21
+
3
22
  ## 1.2.0
4
23
 
5
24
  ### Minor Changes
package/dist/es/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: 20px;
3076
- line-height: 24px;
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/es/index.js CHANGED
@@ -25344,10 +25344,6 @@ var styles$w = StyleSheet.create({
25344
25344
  }
25345
25345
  });
25346
25346
 
25347
- var getLineHeightForNode = (line1, line2) => {
25348
- return $(line2).offset().top - $(line1).offset().top;
25349
- };
25350
-
25351
25347
  function getInitialParseState() {
25352
25348
  return {
25353
25349
  currentRef: [],
@@ -25716,65 +25712,40 @@ var PassageMarkdown = {
25716
25712
  getInitialParseState
25717
25713
  };
25718
25714
 
25719
- // A fake paragraph to measure the line height of the passage. In CSS we always
25720
- // set the line height to 22 pixels, but when using the browser zoom feature,
25721
- // the line height often ends up being a fractional number of pixels close to
25722
- // 22 pixels.
25715
+ // A fake paragraph to measure the line height of the passage,
25716
+ // so we can adapt to browser zoom
25723
25717
  class LineHeightMeasurer extends React.Component {
25724
25718
  constructor() {
25725
25719
  super(...arguments);
25726
25720
 
25727
- _defineProperty(this, "_cachedLineHeight", void 0);
25728
-
25729
- _defineProperty(this, "body", void 0);
25730
-
25731
- _defineProperty(this, "end", void 0);
25721
+ _defineProperty(this, "_line", void 0);
25732
25722
  }
25733
25723
 
25734
25724
  measureLineHeight() {
25735
- if (typeof this._cachedLineHeight !== "number") {
25736
- this.forceMeasureLineHeight();
25725
+ if (!this._line) {
25726
+ return 0;
25737
25727
  }
25738
25728
 
25739
- return this._cachedLineHeight;
25740
- }
25741
-
25742
- forceMeasureLineHeight() {
25743
- var body = this.body;
25744
- var end = this.end;
25745
-
25746
- if (!body || !end) {
25747
- return;
25748
- } // Add some text which magically fills an entire line.
25749
-
25750
-
25751
- body.textContent = " \u0080"; // Now, the line height is the difference between the top of the
25752
- // second line and the top of the first line.
25753
-
25754
- this._cachedLineHeight = getLineHeightForNode(body, end); // Clear out the first line so it doesn't overlap the passage.
25755
-
25756
- body.textContent = "";
25729
+ return this._line.clientHeight;
25757
25730
  }
25758
25731
 
25759
25732
  render() {
25760
25733
  return /*#__PURE__*/React.createElement("div", {
25761
- className: css(styles$v.measurer)
25762
- }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
25763
- ref: _ref => this.body = _ref,
25734
+ className: css(styles$v.measurer),
25735
+ "aria-hidden": "true"
25736
+ }, /*#__PURE__*/React.createElement("div", {
25737
+ ref: _ref => this._line = _ref,
25764
25738
  className: "paragraph"
25765
- }), /*#__PURE__*/React.createElement("div", {
25766
- ref: _ref2 => this.end = _ref2
25767
- })));
25739
+ }, "Line Height Measurement"));
25768
25740
  }
25769
25741
 
25770
25742
  }
25771
-
25772
25743
  var styles$v = StyleSheet.create({
25773
25744
  measurer: {
25774
25745
  position: "absolute",
25775
- width: "100%",
25776
25746
  top: 0,
25777
25747
  left: 0,
25748
+ visibility: "hidden",
25778
25749
  // keep from blocking text selection
25779
25750
  zIndex: -1
25780
25751
  }
@@ -25821,7 +25792,7 @@ class Passage extends React.Component {
25821
25792
  // listen for resize events.
25822
25793
 
25823
25794
 
25824
- (_this$_lineHeightMeas = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas === void 0 ? void 0 : _this$_lineHeightMeas.forceMeasureLineHeight();
25795
+ (_this$_lineHeightMeas = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas === void 0 ? void 0 : _this$_lineHeightMeas.measureLineHeight();
25825
25796
 
25826
25797
  this._updateState();
25827
25798
  }, 500);
@@ -26115,9 +26086,9 @@ class Passage extends React.Component {
26115
26086
  onSerializedHighlightsUpdate: this._handleSerializedHighlightsUpdate,
26116
26087
  serializedHighlights: this.props.highlights
26117
26088
  }, /*#__PURE__*/React.createElement("div", {
26118
- ref: _ref4 => this._contentRef = _ref4
26089
+ ref: _ref3 => this._contentRef = _ref3
26119
26090
  }, /*#__PURE__*/React.createElement(LineHeightMeasurer, {
26120
- ref: _ref3 => this._lineHeightMeasurerRef = _ref3
26091
+ ref: _ref2 => this._lineHeightMeasurerRef = _ref2
26121
26092
  }), PassageMarkdown.output(parsed)));
26122
26093
  }
26123
26094
 
@@ -27377,7 +27348,7 @@ function Choice(props) {
27377
27348
 
27378
27349
  var satCorrectChoice = apiOptions.satStyling && reviewMode && correct;
27379
27350
  var satIncorrectChecked = apiOptions.satStyling && reviewMode && !correct && checked;
27380
- var descriptionClassName = classNames("description", satCorrectChoice && "sat-correct", satIncorrectChecked && "sat-incorrect", css(!apiOptions.satStyling && styles$o.description, apiOptions.satStyling && styles$o.satDescription, satCorrectChoice && styles$o.satDescriptionCorrect, satCorrectChoice && checked && styles$o.satDescriptionCorrectChecked, satIncorrectChecked && styles$o.satDescriptionIncorrectChecked));
27351
+ var descriptionClassName = classNames("description", satCorrectChoice && "sat-correct", satIncorrectChecked && "sat-incorrect", css(!apiOptions.satStyling && styles$o.description, apiOptions.satStyling && styles$o.satDescription, satCorrectChoice && checked && styles$o.satDescriptionCorrectChecked, satIncorrectChecked && styles$o.satDescriptionIncorrectChecked));
27381
27352
  var rationaleClassName = classNames("perseus-radio-rationale-content", css(styles$o.rationale, !apiOptions.satStyling && styles$o.nonSatRationale, apiOptions.satStyling && styles$o.satReviewRationale)); // We want to show the choices as dimmed out when the choices are
27382
27353
  // disabled. However, we don't want to do this in the SAT product and
27383
27354
  // we also don't want to do this when we're in review mode in the
@@ -27391,12 +27362,12 @@ function Choice(props) {
27391
27362
  style: {
27392
27363
  dispay: "flex",
27393
27364
  flexDirection: "column"
27394
- }
27365
+ },
27366
+ className: descriptionClassName
27395
27367
  }, /*#__PURE__*/React.createElement("div", {
27396
27368
  style: {
27397
27369
  display: "flex",
27398
27370
  flexDirection: "row",
27399
- width: "100%",
27400
27371
  opacity: showDimmed ? 0.5 : 1.0
27401
27372
  }
27402
27373
  }, /*#__PURE__*/React.createElement("div", {
@@ -27415,7 +27386,8 @@ function Choice(props) {
27415
27386
  },
27416
27387
  onChange: () => {},
27417
27388
  disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
27418
- tabIndex: -1
27389
+ tabIndex: -1,
27390
+ className: ClassNames.RADIO.OPTION_CONTENT
27419
27391
  }), /*#__PURE__*/React.createElement("label", {
27420
27392
  htmlFor: choiceId
27421
27393
  }, a11yText, " \xA0 ", content)), /*#__PURE__*/React.createElement(Clickable, {
@@ -27427,7 +27399,6 @@ function Choice(props) {
27427
27399
  crossedOut: false
27428
27400
  });
27429
27401
  },
27430
- className: descriptionClassName,
27431
27402
  disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
27432
27403
  style: {
27433
27404
  flex: 1
@@ -27560,16 +27531,12 @@ var styles$o = StyleSheet.create({
27560
27531
  boxSizing: "border-box",
27561
27532
  cursor: "pointer",
27562
27533
  marginLeft: 0,
27563
- padding: "17px 14px"
27564
- },
27565
- satDescriptionCorrect: {
27566
- color: satCorrectColor
27534
+ padding: "17px 14px 17px 0"
27567
27535
  },
27568
27536
  satDescriptionCorrectChecked: {
27569
27537
  backgroundColor: satCorrectBackgroundColor
27570
27538
  },
27571
27539
  satDescriptionIncorrectChecked: {
27572
- color: satIncorrectColor,
27573
27540
  backgroundColor: satIncorrectBackgroundColor
27574
27541
  },
27575
27542
  rationale: {
@@ -27923,8 +27890,7 @@ var styles$n = StyleSheet.create({
27923
27890
  }
27924
27891
  },
27925
27892
  radio: {
27926
- // Avoid centering
27927
- width: "100%"
27893
+ padding: 0
27928
27894
  },
27929
27895
  responsiveRadioContainer: {
27930
27896
  borderBottom: "1px solid ".concat(radioBorderColor$1),
@@ -28153,6 +28119,129 @@ class Radio$1 extends React.Component {
28153
28119
  });
28154
28120
  }
28155
28121
 
28122
+ static validate(userInput, rubric) {
28123
+ var numSelected = userInput.choicesSelected.reduce((sum, selected) => {
28124
+ return sum + (selected ? 1 : 0);
28125
+ }, 0);
28126
+
28127
+ if (numSelected === 0) {
28128
+ return {
28129
+ type: "invalid",
28130
+ message: null
28131
+ };
28132
+ }
28133
+
28134
+ if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
28135
+ return {
28136
+ type: "invalid",
28137
+ message: i18n._("Please choose the correct number of answers.")
28138
+ }; // If NOTA and some other answer are checked, ...
28139
+ }
28140
+
28141
+ if (userInput.noneOfTheAboveSelected && numSelected > 1) {
28142
+ return {
28143
+ type: "invalid",
28144
+ message: i18n._("'None of the above' may not be selected " + "when other answers are selected.")
28145
+ };
28146
+ }
28147
+
28148
+ var correct = userInput.choicesSelected.every((selected, i) => {
28149
+ var isCorrect;
28150
+
28151
+ if (userInput.noneOfTheAboveIndex === i) {
28152
+ isCorrect = rubric.choices.every((choice, j) => {
28153
+ return i === j || !choice.correct;
28154
+ });
28155
+ } else {
28156
+ isCorrect = !!rubric.choices[i].correct;
28157
+ }
28158
+
28159
+ return isCorrect === selected;
28160
+ });
28161
+ return {
28162
+ type: "points",
28163
+ earned: correct ? 1 : 0,
28164
+ total: 1,
28165
+ message: null
28166
+ };
28167
+ }
28168
+
28169
+ static getUserInputFromProps(props) {
28170
+ // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
28171
+ // future timeline implementers: this used to be {value: i} before
28172
+ // multiple select was added)
28173
+ if (props.choiceStates) {
28174
+ var noneOfTheAboveIndex = null;
28175
+ var noneOfTheAboveSelected = false;
28176
+ var choiceStates = props.choiceStates;
28177
+ var choicesSelected = choiceStates.map(() => false);
28178
+ var countChoices = props.countChoices;
28179
+ var numCorrect = props.numCorrect;
28180
+
28181
+ for (var i = 0; i < choicesSelected.length; i++) {
28182
+ var index = props.choices[i].originalIndex;
28183
+ choicesSelected[index] = choiceStates[i].selected;
28184
+
28185
+ if (props.choices[i].isNoneOfTheAbove) {
28186
+ noneOfTheAboveIndex = index;
28187
+
28188
+ if (choicesSelected[i]) {
28189
+ noneOfTheAboveSelected = true;
28190
+ }
28191
+ }
28192
+ }
28193
+
28194
+ return {
28195
+ countChoices,
28196
+ choicesSelected,
28197
+ numCorrect,
28198
+ noneOfTheAboveIndex,
28199
+ noneOfTheAboveSelected
28200
+ }; // Support legacy choiceState implementation
28201
+ }
28202
+ /* istanbul ignore if - props.values is deprecated */
28203
+
28204
+
28205
+ var {
28206
+ values
28207
+ } = props;
28208
+
28209
+ if (values) {
28210
+ var _noneOfTheAboveIndex = null;
28211
+ var _noneOfTheAboveSelected = false;
28212
+ var _choicesSelected = [...values];
28213
+ var _countChoices = props.countChoices;
28214
+ var _numCorrect = props.numCorrect;
28215
+ var valuesLength = values.length;
28216
+
28217
+ for (var _i = 0; _i < valuesLength; _i++) {
28218
+ var _index = props.choices[_i].originalIndex;
28219
+ _choicesSelected[_index] = values[_i];
28220
+
28221
+ if (props.choices[_i].isNoneOfTheAbove) {
28222
+ _noneOfTheAboveIndex = _index;
28223
+
28224
+ if (_choicesSelected[_i]) {
28225
+ _noneOfTheAboveSelected = true;
28226
+ }
28227
+ }
28228
+ }
28229
+
28230
+ return {
28231
+ choicesSelected: _choicesSelected,
28232
+ noneOfTheAboveIndex: _noneOfTheAboveIndex,
28233
+ noneOfTheAboveSelected: _noneOfTheAboveSelected,
28234
+ countChoices: _countChoices,
28235
+ numCorrect: _numCorrect
28236
+ };
28237
+ } // Nothing checked
28238
+
28239
+
28240
+ return {
28241
+ choicesSelected: props.choices.map(() => false)
28242
+ };
28243
+ }
28244
+
28156
28245
  // TODO(LP-10672): I think this might be unused right now. I can't find anywhere
28157
28246
  // that we pass a value to `.focus()` and it seems to have been used for
28158
28247
  // adding hints when editing.
@@ -28283,129 +28372,6 @@ class Radio$1 extends React.Component {
28283
28372
  });
28284
28373
  }
28285
28374
 
28286
- static validate(userInput, rubric) {
28287
- var numSelected = userInput.choicesSelected.reduce((sum, selected) => {
28288
- return sum + (selected ? 1 : 0);
28289
- }, 0);
28290
-
28291
- if (numSelected === 0) {
28292
- return {
28293
- type: "invalid",
28294
- message: null
28295
- };
28296
- }
28297
-
28298
- if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
28299
- return {
28300
- type: "invalid",
28301
- message: i18n._("Please choose the correct number of answers.")
28302
- }; // If NOTA and some other answer are checked, ...
28303
- }
28304
-
28305
- if (userInput.noneOfTheAboveSelected && numSelected > 1) {
28306
- return {
28307
- type: "invalid",
28308
- message: i18n._("'None of the above' may not be selected " + "when other answers are selected.")
28309
- };
28310
- }
28311
-
28312
- var correct = userInput.choicesSelected.every((selected, i) => {
28313
- var isCorrect;
28314
-
28315
- if (userInput.noneOfTheAboveIndex === i) {
28316
- isCorrect = rubric.choices.every((choice, j) => {
28317
- return i === j || !choice.correct;
28318
- });
28319
- } else {
28320
- isCorrect = !!rubric.choices[i].correct;
28321
- }
28322
-
28323
- return isCorrect === selected;
28324
- });
28325
- return {
28326
- type: "points",
28327
- earned: correct ? 1 : 0,
28328
- total: 1,
28329
- message: null
28330
- };
28331
- }
28332
-
28333
- static getUserInputFromProps(props) {
28334
- // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
28335
- // future timeline implementers: this used to be {value: i} before
28336
- // multiple select was added)
28337
- if (props.choiceStates) {
28338
- var noneOfTheAboveIndex = null;
28339
- var noneOfTheAboveSelected = false;
28340
- var choiceStates = props.choiceStates;
28341
- var choicesSelected = choiceStates.map(() => false);
28342
- var countChoices = props.countChoices;
28343
- var numCorrect = props.numCorrect;
28344
-
28345
- for (var i = 0; i < choicesSelected.length; i++) {
28346
- var index = props.choices[i].originalIndex;
28347
- choicesSelected[index] = choiceStates[i].selected;
28348
-
28349
- if (props.choices[i].isNoneOfTheAbove) {
28350
- noneOfTheAboveIndex = index;
28351
-
28352
- if (choicesSelected[i]) {
28353
- noneOfTheAboveSelected = true;
28354
- }
28355
- }
28356
- }
28357
-
28358
- return {
28359
- countChoices,
28360
- choicesSelected,
28361
- numCorrect,
28362
- noneOfTheAboveIndex,
28363
- noneOfTheAboveSelected
28364
- }; // Support legacy choiceState implementation
28365
- }
28366
- /* istanbul ignore if - props.values is deprecated */
28367
-
28368
-
28369
- var {
28370
- values
28371
- } = props;
28372
-
28373
- if (values) {
28374
- var _noneOfTheAboveIndex = null;
28375
- var _noneOfTheAboveSelected = false;
28376
- var _choicesSelected = [...values];
28377
- var _countChoices = props.countChoices;
28378
- var _numCorrect = props.numCorrect;
28379
- var valuesLength = values.length;
28380
-
28381
- for (var _i = 0; _i < valuesLength; _i++) {
28382
- var _index = props.choices[_i].originalIndex;
28383
- _choicesSelected[_index] = values[_i];
28384
-
28385
- if (props.choices[_i].isNoneOfTheAbove) {
28386
- _noneOfTheAboveIndex = _index;
28387
-
28388
- if (_choicesSelected[_i]) {
28389
- _noneOfTheAboveSelected = true;
28390
- }
28391
- }
28392
- }
28393
-
28394
- return {
28395
- choicesSelected: _choicesSelected,
28396
- noneOfTheAboveIndex: _noneOfTheAboveIndex,
28397
- noneOfTheAboveSelected: _noneOfTheAboveSelected,
28398
- countChoices: _countChoices,
28399
- numCorrect: _numCorrect
28400
- };
28401
- } // Nothing checked
28402
-
28403
-
28404
- return {
28405
- choicesSelected: props.choices.map(() => false)
28406
- };
28407
- }
28408
-
28409
28375
  }
28410
28376
 
28411
28377
  _defineProperty(Radio$1, "defaultProps", {