@khanacademy/perseus 0.4.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.eslintrc.js +6 -1
  2. package/CHANGELOG.md +34 -0
  3. package/dist/es/index.js +1078 -1299
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.js +1077 -1304
  6. package/dist/index.js.map +1 -1
  7. package/dist/strings.js +0 -4
  8. package/package.json +9 -8
  9. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +110 -110
  10. package/src/perseus-api.jsx +10 -36
  11. package/src/perseus-types.js +0 -138
  12. package/src/styles/constants.js +0 -2
  13. package/src/styles/variables.less +0 -2
  14. package/src/types.js +18 -35
  15. package/src/util.js +0 -6
  16. package/src/version.js +1 -1
  17. package/src/widgets/__testdata__/base-radio_testdata.js +22 -0
  18. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +110 -110
  19. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +1136 -1160
  20. package/src/widgets/__tests__/radio/base-radio_test.jsx +243 -41
  21. package/src/widgets/__tests__/radio/choice-icon_test.jsx +67 -0
  22. package/src/widgets/__tests__/radio/choice_test.jsx +201 -0
  23. package/src/widgets/__tests__/radio/focus-ring_test.jsx +23 -0
  24. package/src/widgets/__tests__/radio/option-status_test.jsx +65 -0
  25. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -9
  26. package/src/widgets/passage/get-line-height-for-node.js +4 -5
  27. package/src/widgets/passage/passage-markdown.jsx +176 -120
  28. package/src/widgets/passage.jsx +48 -46
  29. package/src/widgets/radio/__stories__/base-radio.stories.jsx +164 -17
  30. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +202 -111
  31. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +21 -6
  32. package/src/widgets/radio/__stories__/choice.stories.jsx +138 -0
  33. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +29 -23
  34. package/src/widgets/radio/__stories__/option-status.stories.jsx +93 -99
  35. package/src/widgets/radio/base-radio.jsx +483 -556
  36. package/src/widgets/radio/choice-icon/choice-icon.jsx +155 -0
  37. package/src/widgets/radio/choice-icon/cross-out-line.jsx +55 -0
  38. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +219 -0
  39. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +129 -0
  40. package/src/widgets/radio/choice-icon/shared-styles.js +17 -0
  41. package/src/widgets/radio/choice-none-above.jsx +49 -40
  42. package/src/widgets/radio/choice.jsx +282 -353
  43. package/src/widgets/radio/choice.jsx.fixture.js +0 -18
  44. package/src/widgets/radio/focus-ring.jsx +29 -22
  45. package/src/widgets/radio/option-status.jsx +58 -36
  46. package/src/widgets/radio/widget.jsx +181 -136
  47. package/src/widgets/radio.jsx +16 -17
  48. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +0 -64
  49. package/src/widgets/radio/choice-icon.jsx +0 -469
  50. package/src/widgets/radio/cross-out-button.jsx +0 -134
@@ -3,7 +3,6 @@
3
3
  import {linterContextDefault} from "@khanacademy/perseus-linter";
4
4
  import * as i18n from "@khanacademy/wonder-blocks-i18n";
5
5
  import * as React from "react";
6
- import _ from "underscore";
7
6
 
8
7
  import Renderer from "../../renderer.jsx";
9
8
  import Util from "../../util.js";
@@ -15,7 +14,13 @@ import type {
15
14
  PerseusRadioChoice,
16
15
  PerseusRadioWidgetOptions,
17
16
  } from "../../perseus-types.js";
18
- import type {PerseusScore, WidgetProps} from "../../types.js";
17
+ import type {
18
+ PerseusScore,
19
+ WidgetProps,
20
+ ChoiceState,
21
+ RadioChoiceWithMetadata,
22
+ } from "../../types.js";
23
+ import type {FocusFunction, ChoiceType} from "./base-radio.jsx";
19
24
 
20
25
  // RenderProps is the return type for radio.jsx#transform
21
26
  export type RenderProps = {|
@@ -24,9 +29,12 @@ export type RenderProps = {|
24
29
  multipleSelect?: boolean,
25
30
  countChoices?: boolean,
26
31
  deselectEnabled?: boolean,
27
- choices: $ReadOnlyArray<PerseusRadioChoice>,
32
+ choices: $ReadOnlyArray<RadioChoiceWithMetadata>,
28
33
  selectedChoices: $ReadOnlyArray<PerseusRadioChoice["correct"]>,
29
- choiceStates?: $ReadOnlyArray<$FlowFixMe>,
34
+ choiceStates?: $ReadOnlyArray<ChoiceState>,
35
+ // Depreciated; support for legacy way of handling changes
36
+ // Adds proptype for prop that is used but was lacking type
37
+ values?: $ReadOnlyArray<boolean>,
30
38
  |};
31
39
 
32
40
  type UserInput = {|
@@ -39,25 +47,39 @@ type UserInput = {|
39
47
  type Rubric = PerseusRadioWidgetOptions;
40
48
  type Props = WidgetProps<RenderProps, Rubric>;
41
49
 
50
+ type DefaultProps = {|
51
+ choices: Props["choices"],
52
+ multipleSelect: Props["multipleSelect"],
53
+ countChoices: Props["countChoices"],
54
+ deselectEnabled: Props["deselectEnabled"],
55
+ linterContext: Props["linterContext"],
56
+ |};
57
+
42
58
  class Radio extends React.Component<Props> {
43
- static defaultProps: $FlowFixMe = {
44
- choices: [{}],
45
- displayCount: null,
59
+ focusFunction: FocusFunction;
60
+
61
+ static defaultProps: DefaultProps = {
62
+ choices: [],
46
63
  multipleSelect: false,
47
64
  countChoices: false,
48
65
  deselectEnabled: false,
49
66
  linterContext: linterContextDefault,
50
67
  };
51
68
 
52
- _renderRenderer: (string) => React.Node = (content) => {
53
- content = content || "";
54
-
69
+ _renderRenderer: (content?: string) => React.Node = (
70
+ content?: string = "",
71
+ ) => {
55
72
  let nextPassageRefId = 1;
56
73
  const widgets = {};
57
74
 
58
75
  const modContent = content.replace(
59
76
  /\{\{passage-ref (\d+) (\d+)(?: "([^"]*)")?\}\}/g,
60
- (match, passageNum, refNum, summaryText) => {
77
+ (
78
+ match: string,
79
+ passageNum: string,
80
+ refNum: string,
81
+ summaryText: string,
82
+ ) => {
61
83
  const widgetId = "passage-ref " + nextPassageRefId;
62
84
  nextPassageRefId++;
63
85
 
@@ -105,10 +127,19 @@ class Radio extends React.Component<Props> {
105
127
  // that we pass a value to `.focus()` and it seems to have been used for
106
128
  // adding hints when editing.
107
129
  // See: https://github.com/Khan/perseus/blame/e18582b4b69959270b90e237ef1813899711ddfa/src/widgets/radio.js#L169
108
- focus: ($FlowFixMe) => boolean = (i) => {
109
- // eslint-disable-next-line react/no-string-refs
110
- return this.refs.baseRadio.focus(i);
111
- };
130
+ focus(choiceIndex: ?number): boolean {
131
+ if (this.focusFunction) {
132
+ return this.focusFunction(choiceIndex);
133
+ }
134
+
135
+ return false;
136
+ }
137
+
138
+ // lets BaseRadio regiser a focus callback so widget
139
+ // can focus an individual choice
140
+ registerFocusFunction(fun: FocusFunction): void {
141
+ this.focusFunction = fun;
142
+ }
112
143
 
113
144
  // When `BaseRadio`'s `onChange` handler is called, indicating a change in
114
145
  // our choices' state, we need to call our `onChange` handler in order to
@@ -123,7 +154,12 @@ class Radio extends React.Component<Props> {
123
154
  //
124
155
  // NOTE(mdr): This method expects to be auto-bound. If this component is
125
156
  // converted to an ES6 class, take care to auto-bind this method!
126
- updateChoices: ($FlowFixMe) => void = (newValueLists) => {
157
+ updateChoices: (
158
+ newValueLists: $ReadOnly<{
159
+ checked: $ReadOnlyArray<boolean>,
160
+ crossedOut: $ReadOnlyArray<boolean>,
161
+ }>,
162
+ ) => void = (newValueLists) => {
127
163
  const {choiceStates, choices} = this.props;
128
164
 
129
165
  // Construct the baseline `choiceStates` objects. If this is the user's
@@ -131,24 +167,21 @@ class Radio extends React.Component<Props> {
131
167
  // new objects with all fields set to the default values. Otherwise, we
132
168
  // should clone the old `choiceStates` objects, in preparation to
133
169
  // mutate them.
134
- let newChoiceStates;
135
- if (choiceStates) {
136
- newChoiceStates = choiceStates.map((state) => ({...state}));
137
- } else {
138
- newChoiceStates = choices.map(() => ({
139
- selected: false,
140
- crossedOut: false,
141
- highlighted: false,
142
- rationaleShown: false,
143
- correctnessShown: false,
144
- previouslyAnswered: false,
145
- readOnly: false,
146
- }));
147
- }
170
+ const newChoiceStates = choiceStates
171
+ ? choiceStates.map((state: ChoiceState) => ({...state}))
172
+ : choices.map(() => ({
173
+ selected: false,
174
+ crossedOut: false,
175
+ highlighted: false,
176
+ rationaleShown: false,
177
+ correctnessShown: false,
178
+ previouslyAnswered: false,
179
+ readOnly: false,
180
+ }));
148
181
 
149
182
  // Mutate the new `choiceState` objects, according to the new `checked`
150
183
  // and `crossedOut` values provided in `newValueLists`.
151
- newChoiceStates.forEach((choiceState, i) => {
184
+ newChoiceStates.forEach((choiceState: ChoiceState, i) => {
152
185
  choiceState.selected = newValueLists.checked[i];
153
186
  choiceState.crossedOut = newValueLists.crossedOut[i];
154
187
  });
@@ -178,25 +211,29 @@ class Radio extends React.Component<Props> {
178
211
  const widgetCorrect =
179
212
  score.type === "points" && score.total === score.earned;
180
213
 
181
- const newStates = choiceStates.map((state) => ({
182
- ...state,
183
- highlighted: state.selected,
184
- // If the choice is selected, show the rationale now
185
- rationaleShown:
186
- state.selected ||
187
- // If the choice already had a rationale, keep it shown
188
- state.rationaleShown ||
189
- // If the widget is correctly answered, show the rationale
190
- // for all the choices
191
- widgetCorrect,
192
- // We use the same behavior for the readOnly flag as for
193
- // rationaleShown, but we keep it separate in case other
194
- // behaviors want to disable choices without showing rationales.
195
- readOnly: state.selected || state.readOnly || widgetCorrect,
196
- correctnessShown: state.selected || state.correctnessShown,
197
- previouslyAnswered:
198
- state.previouslyAnswered || state.selected,
199
- }));
214
+ const newStates: $ReadOnlyArray<ChoiceState> = choiceStates.map(
215
+ (state: ChoiceState): ChoiceState => ({
216
+ ...state,
217
+ highlighted: state.selected,
218
+ // If the choice is selected, show the rationale now
219
+ rationaleShown:
220
+ state.selected ||
221
+ // If the choice already had a rationale, keep it shown
222
+ state.rationaleShown ||
223
+ // If the widget is correctly answered, show the rationale
224
+ // for all the choices
225
+ widgetCorrect,
226
+ // We use the same behavior for the readOnly flag as for
227
+ // rationaleShown, but we keep it separate in case other
228
+ // behaviors want to disable choices without showing rationales.
229
+ readOnly:
230
+ state.selected || state.readOnly || widgetCorrect,
231
+ correctnessShown:
232
+ state.selected || state.correctnessShown,
233
+ previouslyAnswered:
234
+ state.previouslyAnswered || state.selected,
235
+ }),
236
+ );
200
237
 
201
238
  this.props.onChange(
202
239
  {
@@ -213,11 +250,15 @@ class Radio extends React.Component<Props> {
213
250
  */
214
251
  deselectIncorrectSelectedChoices: () => void = () => {
215
252
  if (this.props.choiceStates) {
216
- const newStates = this.props.choiceStates.map((state, i) => ({
217
- ...state,
218
- selected: state.selected && !!this.props.choices[i].correct,
219
- highlighted: false,
220
- }));
253
+ const newStates: $ReadOnlyArray<ChoiceState> =
254
+ this.props.choiceStates.map(
255
+ (state: ChoiceState, i): ChoiceState => ({
256
+ ...state,
257
+ selected:
258
+ state.selected && !!this.props.choices[i].correct,
259
+ highlighted: false,
260
+ }),
261
+ );
221
262
 
222
263
  this.props.onChange(
223
264
  {
@@ -230,12 +271,12 @@ class Radio extends React.Component<Props> {
230
271
  };
231
272
 
232
273
  render(): React.Node {
233
- let choices = this.props.choices;
234
- let choiceStates;
274
+ const {choices} = this.props;
275
+ let choiceStates: $ReadOnlyArray<ChoiceState>;
235
276
  if (this.props.static) {
236
- choiceStates = _.map(choices, (val) => ({
237
- selected: val.correct,
238
- crossedOut: val.crossedOut,
277
+ choiceStates = choices.map((choice) => ({
278
+ selected: !!choice.correct,
279
+ crossedOut: false,
239
280
  readOnly: true,
240
281
  highlighted: false,
241
282
  rationaleShown: true,
@@ -244,11 +285,10 @@ class Radio extends React.Component<Props> {
244
285
  }));
245
286
  } else if (this.props.choiceStates) {
246
287
  choiceStates = this.props.choiceStates;
247
- // $FlowFixMe[prop-missing]
248
288
  } else if (this.props.values) {
249
289
  // Support legacy choiceStates implementation
250
290
  /* istanbul ignore next - props.values is deprecated */
251
- choiceStates = _.map(this.props.values, (val) => ({
291
+ choiceStates = this.props.values.map((val) => ({
252
292
  selected: val,
253
293
  crossedOut: false,
254
294
  readOnly: false,
@@ -258,7 +298,7 @@ class Radio extends React.Component<Props> {
258
298
  previouslyAnswered: false,
259
299
  }));
260
300
  } else {
261
- choiceStates = _.map(choices, () => ({
301
+ choiceStates = choices.map(() => ({
262
302
  selected: false,
263
303
  crossedOut: false,
264
304
  readOnly: false,
@@ -269,80 +309,82 @@ class Radio extends React.Component<Props> {
269
309
  }));
270
310
  }
271
311
 
272
- choices = _.map(choices, (choice, i) => {
273
- const content =
274
- choice.isNoneOfTheAbove && !choice.content
275
- ? // we use i18n._ instead of $_ here because the content
276
- // sent to a renderer needs to be a string, not a react
277
- // node (/renderable/fragment).
278
- i18n._("None of the above")
279
- : choice.content;
280
-
281
- const {
282
- selected,
283
- crossedOut,
284
- rationaleShown,
285
- correctnessShown,
286
- readOnly,
287
- highlighted,
288
- previouslyAnswered,
289
- } = choiceStates[i];
290
-
291
- const reviewChoice =
292
- this.props.reviewModeRubric &&
293
- this.props.reviewModeRubric.choices[i];
294
-
295
- return {
296
- content: this._renderRenderer(content),
297
- checked: selected,
298
- // Current versions of the radio widget always pass in the
299
- // "correct" value through the choices. Old serialized state
300
- // for radio widgets doesn't have this though, so we have to
301
- // pull the correctness out of the review mode rubric. This
302
- // only works because all of the places we use
303
- // `restoreSerializedState()` also turn on reviewMode, but is
304
- // fine for now.
305
- // TODO(emily): Come up with a more comprehensive way to solve
306
- // this sort of "serialized state breaks when internal
307
- // structure changes" problem.
308
- correct:
309
- typeof choice.correct === "undefined"
310
- ? !!reviewChoice && reviewChoice.correct
311
- : choice.correct,
312
- disabled: readOnly,
313
- hasRationale: !!choice.clue,
314
- rationale: this._renderRenderer(choice.clue),
315
- showRationale: rationaleShown,
316
- showCorrectness: correctnessShown,
317
- isNoneOfTheAbove: choice.isNoneOfTheAbove,
318
- revealNoneOfTheAbove: this.props.questionCompleted && selected,
319
- crossedOut,
320
- highlighted,
321
- previouslyAnswered: previouslyAnswered,
322
- };
323
- });
312
+ const choicesProp: $ReadOnlyArray<ChoiceType> = choices.map(
313
+ (choice, i) => {
314
+ const content =
315
+ choice.isNoneOfTheAbove && !choice.content
316
+ ? // we use i18n._ instead of $_ here because the content
317
+ // sent to a renderer needs to be a string, not a react
318
+ // node (/renderable/fragment).
319
+ i18n._("None of the above")
320
+ : choice.content;
321
+
322
+ const {
323
+ selected,
324
+ crossedOut,
325
+ rationaleShown,
326
+ correctnessShown,
327
+ readOnly,
328
+ highlighted,
329
+ previouslyAnswered,
330
+ } = choiceStates[i];
331
+
332
+ const reviewChoice =
333
+ this.props.reviewModeRubric &&
334
+ this.props.reviewModeRubric.choices[i];
335
+
336
+ return {
337
+ content: this._renderRenderer(content),
338
+ checked: selected,
339
+ // Current versions of the radio widget always pass in the
340
+ // "correct" value through the choices. Old serialized state
341
+ // for radio widgets doesn't have this though, so we have to
342
+ // pull the correctness out of the review mode rubric. This
343
+ // only works because all of the places we use
344
+ // `restoreSerializedState()` also turn on reviewMode, but is
345
+ // fine for now.
346
+ // TODO(emily): Come up with a more comprehensive way to solve
347
+ // this sort of "serialized state breaks when internal
348
+ // structure changes" problem.
349
+ correct:
350
+ choice.correct === undefined
351
+ ? !!reviewChoice && !!reviewChoice.correct
352
+ : choice.correct,
353
+ disabled: readOnly,
354
+ hasRationale: !!choice.clue,
355
+ rationale: this._renderRenderer(choice.clue),
356
+ showRationale: rationaleShown,
357
+ showCorrectness: correctnessShown,
358
+ isNoneOfTheAbove: !!choice.isNoneOfTheAbove,
359
+ revealNoneOfTheAbove: !!(
360
+ this.props.questionCompleted && selected
361
+ ),
362
+ crossedOut,
363
+ highlighted,
364
+ previouslyAnswered: previouslyAnswered,
365
+ };
366
+ },
367
+ );
324
368
 
325
369
  return (
326
370
  <BaseRadio
327
- // eslint-disable-next-line react/no-string-refs
328
- ref="baseRadio"
329
371
  labelWrap={true}
330
372
  multipleSelect={this.props.multipleSelect}
331
373
  countChoices={this.props.countChoices}
332
374
  numCorrect={this.props.numCorrect}
333
- choices={choices}
375
+ choices={choicesProp}
334
376
  onChange={this.updateChoices}
335
377
  reviewModeRubric={this.props.reviewModeRubric}
336
378
  deselectEnabled={this.props.deselectEnabled}
337
379
  apiOptions={this.props.apiOptions}
338
380
  isLastUsedWidget={this.props.isLastUsedWidget}
381
+ registerFocusFunction={(i) => this.registerFocusFunction(i)}
339
382
  />
340
383
  );
341
384
  }
342
385
 
343
386
  static validate(userInput: UserInput, rubric: Rubric): PerseusScore {
344
- const numSelected = _.reduce(
345
- userInput.choicesSelected,
387
+ const numSelected = userInput.choicesSelected.reduce(
346
388
  (sum, selected) => {
347
389
  return sum + (selected ? 1 : 0);
348
390
  },
@@ -355,8 +397,12 @@ class Radio extends React.Component<Props> {
355
397
  message: null,
356
398
  };
357
399
  }
358
- // $FlowFixMe[invalid-compare]
359
- if (userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
400
+
401
+ if (
402
+ userInput.numCorrect &&
403
+ userInput.numCorrect > 1 &&
404
+ numSelected !== userInput.numCorrect
405
+ ) {
360
406
  return {
361
407
  type: "invalid",
362
408
  message: i18n._("Please choose the correct number of answers."),
@@ -372,11 +418,11 @@ class Radio extends React.Component<Props> {
372
418
  ),
373
419
  };
374
420
  }
375
- /* jshint -W018 */
376
- const correct = _.all(userInput.choicesSelected, (selected, i) => {
421
+
422
+ const correct = userInput.choicesSelected.every((selected, i) => {
377
423
  let isCorrect;
378
424
  if (userInput.noneOfTheAboveIndex === i) {
379
- isCorrect = _.all(rubric.choices, (choice, j) => {
425
+ isCorrect = rubric.choices.every((choice, j) => {
380
426
  return i === j || !choice.correct;
381
427
  });
382
428
  } else {
@@ -384,7 +430,6 @@ class Radio extends React.Component<Props> {
384
430
  }
385
431
  return isCorrect === selected;
386
432
  });
387
- /* jshint +W018 */
388
433
 
389
434
  return {
390
435
  type: "points",
@@ -408,9 +453,8 @@ class Radio extends React.Component<Props> {
408
453
  const numCorrect = props.numCorrect;
409
454
 
410
455
  for (let i = 0; i < choicesSelected.length; i++) {
411
- // $FlowFixMe[prop-missing]
412
456
  const index = props.choices[i].originalIndex;
413
- // $FlowFixMe[incompatible-use]
457
+
414
458
  choicesSelected[index] = choiceStates[i].selected;
415
459
 
416
460
  if (props.choices[i].isNoneOfTheAbove) {
@@ -432,28 +476,29 @@ class Radio extends React.Component<Props> {
432
476
  // Support legacy choiceState implementation
433
477
  }
434
478
  /* istanbul ignore if - props.values is deprecated */
435
- // $FlowFixMe[prop-missing]
436
- if (props.values) {
479
+ const {values} = props;
480
+ if (values) {
437
481
  let noneOfTheAboveIndex = null;
438
482
  let noneOfTheAboveSelected = false;
439
483
 
440
- const values = props.values.slice();
484
+ const choicesSelected = [...values];
441
485
  const countChoices = props.countChoices;
442
486
  const numCorrect = props.numCorrect;
487
+ const valuesLength = values.length;
443
488
 
444
- for (let i = 0; i < props.values.length; i++) {
489
+ for (let i = 0; i < valuesLength; i++) {
445
490
  const index = props.choices[i].originalIndex;
446
- values[index] = props.values[i];
491
+ choicesSelected[index] = values[i];
447
492
 
448
493
  if (props.choices[i].isNoneOfTheAbove) {
449
494
  noneOfTheAboveIndex = index;
450
- if (values[i]) {
495
+ if (choicesSelected[i]) {
451
496
  noneOfTheAboveSelected = true;
452
497
  }
453
498
  }
454
499
  }
455
500
  return {
456
- choicesSelected: values,
501
+ choicesSelected,
457
502
  noneOfTheAboveIndex,
458
503
  noneOfTheAboveSelected,
459
504
  countChoices,
@@ -462,7 +507,7 @@ class Radio extends React.Component<Props> {
462
507
  }
463
508
  // Nothing checked
464
509
  return {
465
- choicesSelected: _.map(props.choices, () => false),
510
+ choicesSelected: props.choices.map(() => false),
466
511
  };
467
512
  }
468
513
  }
@@ -6,11 +6,8 @@ import Util from "../util.js";
6
6
 
7
7
  import Radio from "./radio/widget.jsx";
8
8
 
9
- import type {
10
- PerseusRadioWidgetOptions,
11
- PerseusRadioChoice,
12
- } from "../perseus-types.js";
13
- import type {WidgetExports} from "../types.js";
9
+ import type {PerseusRadioWidgetOptions} from "../perseus-types.js";
10
+ import type {WidgetExports, RadioChoiceWithMetadata} from "../types.js";
14
11
  import type {RenderProps} from "./radio/widget.jsx";
15
12
 
16
13
  const {shuffle, random} = Util;
@@ -43,15 +40,16 @@ const _choiceTransform = (
43
40
  };
44
41
 
45
42
  const _addNoneOfAbove = function (
46
- choices: $ReadOnlyArray<PerseusRadioChoice>,
47
- ): $ReadOnlyArray<PerseusRadioChoice> {
43
+ choices: $ReadOnlyArray<RadioChoiceWithMetadata>,
44
+ ) {
48
45
  let noneOfTheAbove = null;
49
46
 
50
- const newChoices = _.reject(choices, function (choice, index) {
47
+ const newChoices = choices.filter((choice, index) => {
51
48
  if (choice.isNoneOfTheAbove) {
52
49
  noneOfTheAbove = choice;
53
- return true;
50
+ return false;
54
51
  }
52
+ return true;
55
53
  });
56
54
 
57
55
  // Place the "None of the above" options last
@@ -63,8 +61,8 @@ const _choiceTransform = (
63
61
  };
64
62
 
65
63
  const enforceOrdering = (
66
- choices: $ReadOnlyArray<PerseusRadioChoice>,
67
- ): $ReadOnlyArray<PerseusRadioChoice> => {
64
+ choices: $ReadOnlyArray<RadioChoiceWithMetadata>,
65
+ ) => {
68
66
  const content = choices.map((c) => c.content);
69
67
  if (ReversedChoices.some((reversed) => _.isEqual(content, reversed))) {
70
68
  return [choices[1], choices[0]];
@@ -73,13 +71,14 @@ const _choiceTransform = (
73
71
  };
74
72
 
75
73
  // Add meta-information to choices
76
- let choices = widgetOptions.choices.slice();
77
- choices = _.map(choices, (choice, i) => {
78
- return _.extend({}, choice, {
79
- originalIndex: i,
80
- correct: Boolean(choice.correct),
74
+ const choices: $ReadOnlyArray<RadioChoiceWithMetadata> =
75
+ widgetOptions.choices.map((choice, i): RadioChoiceWithMetadata => {
76
+ return {
77
+ ...choice,
78
+ originalIndex: i,
79
+ correct: Boolean(choice.correct),
80
+ };
81
81
  });
82
- });
83
82
 
84
83
  // Apply all the transforms. Note that the order we call these is
85
84
  // important!
@@ -1,64 +0,0 @@
1
- // @flow
2
- import colors from "@khanacademy/wonder-blocks-color";
3
- import * as React from "react";
4
-
5
- import CrossOutButton from "../cross-out-button.jsx";
6
-
7
- type StoryArgs = {||};
8
-
9
- type Story = {|
10
- title: string,
11
- |};
12
-
13
- export default ({
14
- title: "Perseus/Widgets/Radio/Cross Out Button",
15
- }: Story);
16
-
17
- export const CrossOutButtonForChoiceAColoredDefaultKaGreen = (
18
- args: StoryArgs,
19
- ): React.Node => {
20
- return (
21
- <CrossOutButton
22
- onClick={() => {}}
23
- onFocus={() => {}}
24
- onBlur={() => {}}
25
- pos={0}
26
- crossedOut={false}
27
- usingKeyboardFocus={false}
28
- theme="day"
29
- />
30
- );
31
- };
32
-
33
- export const ReEnableButtonForChoiceCColoredWonderBlocksBlue = (
34
- args: StoryArgs,
35
- ): React.Node => {
36
- return (
37
- <CrossOutButton
38
- onClick={() => {}}
39
- onFocus={() => {}}
40
- onBlur={() => {}}
41
- pos={0}
42
- primaryProductColor={colors.blue}
43
- crossedOut={true}
44
- usingKeyboardFocus={false}
45
- theme="day"
46
- />
47
- );
48
- };
49
-
50
- export const CrossOutButtonForChoiceAColoredWhiteForNightTheme = (
51
- args: StoryArgs,
52
- ): React.Node => {
53
- return (
54
- <CrossOutButton
55
- onClick={() => {}}
56
- onFocus={() => {}}
57
- onBlur={() => {}}
58
- pos={0}
59
- crossedOut={false}
60
- usingKeyboardFocus={false}
61
- theme="night"
62
- />
63
- );
64
- };