@khanacademy/perseus 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/dist/es/index.js +15 -1
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +11934 -0
  6. package/dist/index.js +15 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/strings.js +148 -1
  9. package/package.json +40 -4
  10. package/src/README.md +7 -0
  11. package/src/__stories__/item-renderer.stories.jsx +27 -0
  12. package/src/__stories__/server-item-renderer.stories.jsx +27 -0
  13. package/src/__testdata__/item-renderer_testdata.js +161 -0
  14. package/src/__testdata__/renderer_testdata.js +123 -0
  15. package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
  16. package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
  17. package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
  18. package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
  19. package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
  20. package/src/__tests__/a11y_test.jsx +304 -0
  21. package/src/__tests__/item-renderer_test.jsx +892 -0
  22. package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
  23. package/src/__tests__/mock-widget.jsx +35 -0
  24. package/src/__tests__/perseus-markdown_test.jsx +747 -0
  25. package/src/__tests__/pure-markdown_test.js +552 -0
  26. package/src/__tests__/renderability_test.jsx +481 -0
  27. package/src/__tests__/renderer-api_test.jsx +333 -0
  28. package/src/__tests__/renderer_test.jsx +1792 -0
  29. package/src/__tests__/server-item-renderer_test.jsx +516 -0
  30. package/src/__tests__/test-items/image-item.js +25 -0
  31. package/src/__tests__/test-items/input-number-1-item.js +27 -0
  32. package/src/__tests__/test-items/input-number-2-item.js +39 -0
  33. package/src/__tests__/test-items/table-item.js +30 -0
  34. package/src/__tests__/tex-wrangler_test.js +83 -0
  35. package/src/__tests__/util_test.js +41 -0
  36. package/src/a11y.js +46 -0
  37. package/src/all-widgets.js +9 -0
  38. package/src/article-renderer.jsx +215 -0
  39. package/src/asset-context.js +30 -0
  40. package/src/basic-widgets.js +13 -0
  41. package/src/components/__stories__/button-group.stories.jsx +61 -0
  42. package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
  43. package/src/components/__stories__/graph.stories.jsx +27 -0
  44. package/src/components/__stories__/graphie.stories.jsx +28 -0
  45. package/src/components/__stories__/hud.stories.jsx +36 -0
  46. package/src/components/__stories__/icon.stories.jsx +56 -0
  47. package/src/components/__stories__/image-loader.stories.jsx +60 -0
  48. package/src/components/__stories__/info-tip.stories.jsx +33 -0
  49. package/src/components/__stories__/inline-icon.stories.jsx +39 -0
  50. package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
  51. package/src/components/__stories__/lint.stories.jsx +40 -0
  52. package/src/components/__stories__/math-input.stories.jsx +29 -0
  53. package/src/components/__stories__/math-output.stories.jsx +24 -0
  54. package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
  55. package/src/components/__stories__/number-input.stories.jsx +42 -0
  56. package/src/components/__stories__/possible-answers.stories.jsx +22 -0
  57. package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
  58. package/src/components/__stories__/range-input.stories.jsx +28 -0
  59. package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
  60. package/src/components/__stories__/sortable.stories.jsx +73 -0
  61. package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
  62. package/src/components/__stories__/svg-image.stories.jsx +60 -0
  63. package/src/components/__stories__/tex.stories.jsx +18 -0
  64. package/src/components/__stories__/text-buttons.stories.jsx +51 -0
  65. package/src/components/__stories__/text-input.stories.jsx +34 -0
  66. package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
  67. package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
  68. package/src/components/__stories__/zoomable.stories.jsx +40 -0
  69. package/src/components/__testdata__/sorter_testdata.js +20 -0
  70. package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
  71. package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
  72. package/src/components/__tests__/number-input_test.jsx +112 -0
  73. package/src/components/__tests__/sortable_test.jsx +234 -0
  74. package/src/components/__tests__/sorter_test.jsx +111 -0
  75. package/src/components/__tests__/zoomable_test.jsx +388 -0
  76. package/src/components/button-group.jsx +131 -0
  77. package/src/components/constants.js +15 -0
  78. package/src/components/fixed-to-responsive.jsx +163 -0
  79. package/src/components/graph.jsx +414 -0
  80. package/src/components/graphie-classes.jsx +148 -0
  81. package/src/components/graphie-movables.jsx +342 -0
  82. package/src/components/graphie.jsx +353 -0
  83. package/src/components/highlighting/highlightable-content.jsx +245 -0
  84. package/src/components/highlighting/highlights.js +208 -0
  85. package/src/components/highlighting/ranges.js +299 -0
  86. package/src/components/highlighting/types.js +73 -0
  87. package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
  88. package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
  89. package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
  90. package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
  91. package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
  92. package/src/components/highlighting/ui/types.js +21 -0
  93. package/src/components/highlighting/ui/util.js +166 -0
  94. package/src/components/highlighting/word-indexer.jsx +155 -0
  95. package/src/components/hud.jsx +179 -0
  96. package/src/components/icon.jsx +149 -0
  97. package/src/components/image-loader.jsx +175 -0
  98. package/src/components/info-tip/info-tip.jsx +149 -0
  99. package/src/components/info-tip.jsx +38 -0
  100. package/src/components/inline-icon.jsx +81 -0
  101. package/src/components/input-with-examples.jsx +231 -0
  102. package/src/components/lint.jsx +456 -0
  103. package/src/components/math-input.jsx +282 -0
  104. package/src/components/math-output.jsx +128 -0
  105. package/src/components/multi-button-group.jsx +137 -0
  106. package/src/components/new-tooltip/README.md +110 -0
  107. package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
  108. package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
  109. package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
  110. package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
  111. package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
  112. package/src/components/new-tooltip/__tests__/util_test.js +47 -0
  113. package/src/components/new-tooltip/flexible-position.jsx +182 -0
  114. package/src/components/new-tooltip/new-tooltip.jsx +399 -0
  115. package/src/components/new-tooltip/style-constants.js +13 -0
  116. package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
  117. package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
  118. package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
  119. package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
  120. package/src/components/new-tooltip/types.js +183 -0
  121. package/src/components/new-tooltip/util.js +27 -0
  122. package/src/components/number-input.jsx +253 -0
  123. package/src/components/possible-answers.jsx +36 -0
  124. package/src/components/prop-check-box.jsx +78 -0
  125. package/src/components/range-input.jsx +61 -0
  126. package/src/components/simple-keypad-input.jsx +91 -0
  127. package/src/components/sortable.jsx +872 -0
  128. package/src/components/stub-tag-editor.jsx +53 -0
  129. package/src/components/svg-image.jsx +762 -0
  130. package/src/components/tex-buttons.jsx +322 -0
  131. package/src/components/tex.jsx +70 -0
  132. package/src/components/text-input.jsx +82 -0
  133. package/src/components/text-list-editor.jsx +157 -0
  134. package/src/components/tooltip.jsx +395 -0
  135. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
  136. package/src/components/visibility-observer/get-scroll-parent.js +45 -0
  137. package/src/components/visibility-observer/visibility-observer.js +109 -0
  138. package/src/components/visibility-observer/visibility-observer.jsx +123 -0
  139. package/src/components/zoomable-tex.jsx +70 -0
  140. package/src/components/zoomable.jsx +304 -0
  141. package/src/components.js +17 -0
  142. package/src/dependencies.js +22 -0
  143. package/src/error-boundary.jsx +48 -0
  144. package/src/extra-widgets.js +75 -0
  145. package/src/gorgon/README +40 -0
  146. package/src/gorgon/__tests__/matcher_test.js +497 -0
  147. package/src/gorgon/__tests__/rule_test.js +101 -0
  148. package/src/gorgon/__tests__/rules_test.js +487 -0
  149. package/src/gorgon/__tests__/selector-parser_test.js +52 -0
  150. package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
  151. package/src/gorgon/gorgon.js +290 -0
  152. package/src/gorgon/proptypes.js +29 -0
  153. package/src/gorgon/rule.js +413 -0
  154. package/src/gorgon/rules/absolute-url.js +24 -0
  155. package/src/gorgon/rules/all-rules.js +72 -0
  156. package/src/gorgon/rules/blockquoted-math.js +10 -0
  157. package/src/gorgon/rules/blockquoted-widget.js +10 -0
  158. package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
  159. package/src/gorgon/rules/extra-content-spacing.js +12 -0
  160. package/src/gorgon/rules/heading-level-1.js +14 -0
  161. package/src/gorgon/rules/heading-level-skip.js +20 -0
  162. package/src/gorgon/rules/heading-sentence-case.js +11 -0
  163. package/src/gorgon/rules/heading-title-case.js +63 -0
  164. package/src/gorgon/rules/image-alt-text.js +21 -0
  165. package/src/gorgon/rules/image-in-table.js +10 -0
  166. package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
  167. package/src/gorgon/rules/image-widget.js +50 -0
  168. package/src/gorgon/rules/link-click-here.js +11 -0
  169. package/src/gorgon/rules/lint-utils.js +48 -0
  170. package/src/gorgon/rules/long-paragraph.js +14 -0
  171. package/src/gorgon/rules/math-adjacent.js +10 -0
  172. package/src/gorgon/rules/math-align-extra-break.js +11 -0
  173. package/src/gorgon/rules/math-align-linebreaks.js +43 -0
  174. package/src/gorgon/rules/math-empty.js +10 -0
  175. package/src/gorgon/rules/math-font-size.js +12 -0
  176. package/src/gorgon/rules/math-frac.js +10 -0
  177. package/src/gorgon/rules/math-nested.js +11 -0
  178. package/src/gorgon/rules/math-starts-with-space.js +12 -0
  179. package/src/gorgon/rules/math-text-empty.js +10 -0
  180. package/src/gorgon/rules/math-without-dollars.js +14 -0
  181. package/src/gorgon/rules/nested-lists.js +11 -0
  182. package/src/gorgon/rules/profanity.js +10 -0
  183. package/src/gorgon/rules/table-missing-cells.js +20 -0
  184. package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
  185. package/src/gorgon/rules/unescaped-dollar.js +10 -0
  186. package/src/gorgon/rules/widget-in-table.js +10 -0
  187. package/src/gorgon/selector.js +506 -0
  188. package/src/gorgon/tree-transformer.js +588 -0
  189. package/src/hint-renderer.jsx +159 -0
  190. package/src/hints-renderer.jsx +359 -0
  191. package/src/icon-paths.js +120 -0
  192. package/src/index.js +149 -2
  193. package/src/init.js +49 -0
  194. package/src/interaction-tracker.js +60 -0
  195. package/src/interactive2/__tests__/movable-mock.js +48 -0
  196. package/src/interactive2/__tests__/movable-point_test.js +146 -0
  197. package/src/interactive2/interactive-util.js +152 -0
  198. package/src/interactive2/movable-helper-methods.js +160 -0
  199. package/src/interactive2/movable-line-options.js +392 -0
  200. package/src/interactive2/movable-line.js +322 -0
  201. package/src/interactive2/movable-point-options.js +190 -0
  202. package/src/interactive2/movable-point.jsx +604 -0
  203. package/src/interactive2/movable-polygon-options.js +351 -0
  204. package/src/interactive2/movable-polygon.js +367 -0
  205. package/src/interactive2/movable.js +273 -0
  206. package/src/interactive2/objective_.js +79 -0
  207. package/src/interactive2/types.js +3 -0
  208. package/src/interactive2/wrapped-defaults.js +86 -0
  209. package/src/interactive2/wrapped-ellipse.js +87 -0
  210. package/src/interactive2/wrapped-line.js +109 -0
  211. package/src/interactive2/wrapped-path.js +47 -0
  212. package/src/interactive2.js +91 -0
  213. package/src/item-renderer.jsx +518 -0
  214. package/src/item-version.js +12 -0
  215. package/src/jipt-paragraphs.js +44 -0
  216. package/src/jquery.mobile.vmouse.js +545 -0
  217. package/src/keypad-context.js +33 -0
  218. package/src/loading-context.js +20 -0
  219. package/src/logging/log.js +107 -0
  220. package/src/logging/log_test.js +41 -0
  221. package/src/mixins/changeable.jsx +115 -0
  222. package/src/mixins/editor-jsonify.jsx +13 -0
  223. package/src/mixins/provide-keypad.jsx +131 -0
  224. package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
  225. package/src/mixins/widget-prop-blacklist.js +36 -0
  226. package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
  227. package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
  228. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
  229. package/src/multi-items/__tests__/items_test.js +245 -0
  230. package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
  231. package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
  232. package/src/multi-items/__tests__/shapes_test.js +44 -0
  233. package/src/multi-items/__tests__/trees_test.js +316 -0
  234. package/src/multi-items/item-types.js +55 -0
  235. package/src/multi-items/items.js +176 -0
  236. package/src/multi-items/multi-renderer.jsx +542 -0
  237. package/src/multi-items/prop-type-builders.js +105 -0
  238. package/src/multi-items/shape-types.js +47 -0
  239. package/src/multi-items/shapes.js +53 -0
  240. package/src/multi-items/tree-types.js +42 -0
  241. package/src/multi-items/trees.js +345 -0
  242. package/src/multi-items.js +76 -0
  243. package/src/not-gorgon.js +129 -0
  244. package/src/perseus-api.jsx +270 -0
  245. package/src/perseus-env.js +26 -0
  246. package/src/perseus-error.js +19 -0
  247. package/src/perseus-markdown.jsx +434 -0
  248. package/src/perseus-types.js +1597 -0
  249. package/src/pure-markdown.js +346 -0
  250. package/src/question-paragraph.jsx +30 -0
  251. package/src/renderability.jsx +108 -0
  252. package/src/renderer.jsx +1931 -0
  253. package/src/server-item-renderer.jsx +467 -0
  254. package/src/sigfigs.jsx +492 -0
  255. package/src/styles/articles.less +748 -0
  256. package/src/styles/constants.js +119 -0
  257. package/src/styles/global-constants.js +350 -0
  258. package/src/styles/global-styles.js +307 -0
  259. package/src/styles/khan-exercise.css +1304 -0
  260. package/src/styles/media-queries.js +48 -0
  261. package/src/styles/perseus-renderer.less +1008 -0
  262. package/src/styles/shared.js +132 -0
  263. package/src/styles/util.less +47 -0
  264. package/src/styles/variables.boostrap.less +301 -0
  265. package/src/styles/variables.less +68 -0
  266. package/src/styles/variables.shared.less +356 -0
  267. package/src/styles/widgets/categorizer.less +39 -0
  268. package/src/styles/widgets/definition.less +86 -0
  269. package/src/styles/widgets/dropdown.less +3 -0
  270. package/src/styles/widgets/expression.less +147 -0
  271. package/src/styles/widgets/graded-group.less +36 -0
  272. package/src/styles/widgets/grapher.less +11 -0
  273. package/src/styles/widgets/group.less +19 -0
  274. package/src/styles/widgets/image.less +29 -0
  275. package/src/styles/widgets/interactive-graph.less +61 -0
  276. package/src/styles/widgets/label-image.less +41 -0
  277. package/src/styles/widgets/matcher.less +8 -0
  278. package/src/styles/widgets/matrix.less +120 -0
  279. package/src/styles/widgets/measurer.less +20 -0
  280. package/src/styles/widgets/orderer.less +97 -0
  281. package/src/styles/widgets/passage.less +151 -0
  282. package/src/styles/widgets/plotter.less +33 -0
  283. package/src/styles/widgets/radio.less +52 -0
  284. package/src/styles/widgets/reaction-diagram.less +35 -0
  285. package/src/styles/widgets/sequence.less +5 -0
  286. package/src/styles/widgets/sortable.less +127 -0
  287. package/src/styles/widgets/table.less +56 -0
  288. package/src/styles/widgets/transformer.less +28 -0
  289. package/src/styles/widgets/unit.less +14 -0
  290. package/src/tex-wrangler.js +176 -0
  291. package/src/traversal.jsx +141 -0
  292. package/src/types.js +538 -0
  293. package/src/util/a11y.js +24 -0
  294. package/src/util/answer-types.js +862 -0
  295. package/src/util/answer-types_test.js +115 -0
  296. package/src/util/colors.js +94 -0
  297. package/src/util/fix-passage-refs.jsx +122 -0
  298. package/src/util/graph-utils.js +16 -0
  299. package/src/util/graphie.js +1585 -0
  300. package/src/util/interactive.js +4071 -0
  301. package/src/util/katex-preprocess.js +16 -0
  302. package/src/util/math.js +296 -0
  303. package/src/util/mobile-native-utils.js +14 -0
  304. package/src/util/mobile-native-utils_test.js +24 -0
  305. package/src/util/register-all-widgets-for-testing.js +12 -0
  306. package/src/util/scroll-utils.js +162 -0
  307. package/src/util/sizing-utils.js +46 -0
  308. package/src/util/tex.js +203 -0
  309. package/src/util/url-utils.js +36 -0
  310. package/src/util.js +990 -0
  311. package/src/version.js +11 -0
  312. package/src/widget-container.jsx +174 -0
  313. package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
  314. package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
  315. package/src/widgets/__stories__/definition.stories.jsx +38 -0
  316. package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
  317. package/src/widgets/__stories__/explanation.stories.jsx +19 -0
  318. package/src/widgets/__stories__/expression.stories.jsx +26 -0
  319. package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
  320. package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
  321. package/src/widgets/__stories__/grapher.stories.jsx +47 -0
  322. package/src/widgets/__stories__/group.stories.jsx +15 -0
  323. package/src/widgets/__stories__/iframe.stories.jsx +15 -0
  324. package/src/widgets/__stories__/image.stories.jsx +62 -0
  325. package/src/widgets/__stories__/input-number.stories.jsx +90 -0
  326. package/src/widgets/__stories__/interaction.stories.jsx +15 -0
  327. package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
  328. package/src/widgets/__stories__/matcher.stories.jsx +15 -0
  329. package/src/widgets/__stories__/matrix.stories.jsx +15 -0
  330. package/src/widgets/__stories__/number-line.stories.jsx +15 -0
  331. package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
  332. package/src/widgets/__stories__/orderer.stories.jsx +15 -0
  333. package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
  334. package/src/widgets/__stories__/passage.stories.jsx +19 -0
  335. package/src/widgets/__stories__/radio.stories.jsx +82 -0
  336. package/src/widgets/__stories__/sorter.stories.jsx +15 -0
  337. package/src/widgets/__stories__/transformer.stories.jsx +15 -0
  338. package/src/widgets/__stories__/video.stories.jsx +15 -0
  339. package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
  340. package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
  341. package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
  342. package/src/widgets/__testdata__/explanation_testdata.js +56 -0
  343. package/src/widgets/__testdata__/expression_testdata.js +95 -0
  344. package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
  345. package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
  346. package/src/widgets/__testdata__/grapher_testdata.js +376 -0
  347. package/src/widgets/__testdata__/group_testdata.js +156 -0
  348. package/src/widgets/__testdata__/iframe_testdata.js +36 -0
  349. package/src/widgets/__testdata__/image_testdata.js +38 -0
  350. package/src/widgets/__testdata__/input-number_testdata.js +73 -0
  351. package/src/widgets/__testdata__/interaction_testdata.js +264 -0
  352. package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
  353. package/src/widgets/__testdata__/matcher_testdata.js +34 -0
  354. package/src/widgets/__testdata__/matrix_testdata.js +29 -0
  355. package/src/widgets/__testdata__/number-line_testdata.js +31 -0
  356. package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
  357. package/src/widgets/__testdata__/orderer_testdata.js +58 -0
  358. package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
  359. package/src/widgets/__testdata__/passage_testdata.js +71 -0
  360. package/src/widgets/__testdata__/radio_testdata.js +210 -0
  361. package/src/widgets/__testdata__/sorter_testdata.js +20 -0
  362. package/src/widgets/__testdata__/transformer_testdata.js +109 -0
  363. package/src/widgets/__testdata__/video_testdata.js +22 -0
  364. package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
  365. package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
  366. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
  367. package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
  368. package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
  369. package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
  370. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
  371. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
  372. package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
  373. package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
  374. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
  375. package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
  376. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
  377. package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
  378. package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
  379. package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
  380. package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
  381. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
  382. package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
  383. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
  384. package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
  385. package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
  386. package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
  387. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
  388. package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
  389. package/src/widgets/__tests__/categorizer_test.jsx +205 -0
  390. package/src/widgets/__tests__/cs-program_test.jsx +45 -0
  391. package/src/widgets/__tests__/definition_test.js +152 -0
  392. package/src/widgets/__tests__/dropdown_test.jsx +106 -0
  393. package/src/widgets/__tests__/explanation_test.js +199 -0
  394. package/src/widgets/__tests__/expression_test.jsx +439 -0
  395. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
  396. package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
  397. package/src/widgets/__tests__/graded-group_test.jsx +233 -0
  398. package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
  399. package/src/widgets/__tests__/grapher_test.jsx +35 -0
  400. package/src/widgets/__tests__/group_test.jsx +512 -0
  401. package/src/widgets/__tests__/iframe_test.jsx +47 -0
  402. package/src/widgets/__tests__/image_test.jsx +65 -0
  403. package/src/widgets/__tests__/input-number_test.jsx +360 -0
  404. package/src/widgets/__tests__/interaction_test.jsx +33 -0
  405. package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
  406. package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
  407. package/src/widgets/__tests__/matcher_test.jsx +157 -0
  408. package/src/widgets/__tests__/matrix_test.jsx +84 -0
  409. package/src/widgets/__tests__/number-line_test.jsx +81 -0
  410. package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
  411. package/src/widgets/__tests__/orderer_test.jsx +75 -0
  412. package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
  413. package/src/widgets/__tests__/passage_test.jsx +133 -0
  414. package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
  415. package/src/widgets/__tests__/radio_test.jsx +651 -0
  416. package/src/widgets/__tests__/renderQuestion.jsx +65 -0
  417. package/src/widgets/__tests__/unit_test.jsx +144 -0
  418. package/src/widgets/__tests__/video_test.jsx +55 -0
  419. package/src/widgets/categorizer.jsx +338 -0
  420. package/src/widgets/cs-program.jsx +228 -0
  421. package/src/widgets/definition.jsx +141 -0
  422. package/src/widgets/dropdown.jsx +143 -0
  423. package/src/widgets/explanation.jsx +315 -0
  424. package/src/widgets/expression.jsx +654 -0
  425. package/src/widgets/graded-group-answer-bar.jsx +152 -0
  426. package/src/widgets/graded-group-set.jsx +285 -0
  427. package/src/widgets/graded-group.jsx +448 -0
  428. package/src/widgets/grapher/util.jsx +819 -0
  429. package/src/widgets/grapher.jsx +658 -0
  430. package/src/widgets/group.jsx +213 -0
  431. package/src/widgets/iframe.jsx +199 -0
  432. package/src/widgets/image.jsx +309 -0
  433. package/src/widgets/input-number.jsx +410 -0
  434. package/src/widgets/interaction/arrow-picker.jsx +39 -0
  435. package/src/widgets/interaction/color-picker.jsx +69 -0
  436. package/src/widgets/interaction/dash-picker.jsx +46 -0
  437. package/src/widgets/interaction/element-container.jsx +97 -0
  438. package/src/widgets/interaction.jsx +742 -0
  439. package/src/widgets/interactive-graph.jsx +2821 -0
  440. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
  441. package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
  442. package/src/widgets/label-image/answer-choices.jsx +311 -0
  443. package/src/widgets/label-image/marker.jsx +365 -0
  444. package/src/widgets/label-image/types.js +33 -0
  445. package/src/widgets/label-image.jsx +841 -0
  446. package/src/widgets/label-image_test.js +840 -0
  447. package/src/widgets/lights-puzzle.jsx +356 -0
  448. package/src/widgets/matcher.jsx +271 -0
  449. package/src/widgets/matrix.jsx +615 -0
  450. package/src/widgets/measurer.jsx +215 -0
  451. package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
  452. package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
  453. package/src/widgets/molecule/molecule-drawing.jsx +175 -0
  454. package/src/widgets/molecule/molecule-layout.jsx +367 -0
  455. package/src/widgets/molecule/smiles-parser.jsx +300 -0
  456. package/src/widgets/molecule.jsx +172 -0
  457. package/src/widgets/number-line.jsx +858 -0
  458. package/src/widgets/numeric-input.jsx +622 -0
  459. package/src/widgets/orderer.jsx +753 -0
  460. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
  461. package/src/widgets/passage/get-line-height-for-node.js +10 -0
  462. package/src/widgets/passage/passage-markdown.jsx +416 -0
  463. package/src/widgets/passage-ref-target.jsx +70 -0
  464. package/src/widgets/passage-ref.jsx +198 -0
  465. package/src/widgets/passage.jsx +601 -0
  466. package/src/widgets/plotter.jsx +1198 -0
  467. package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
  468. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
  469. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
  470. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
  471. package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
  472. package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
  473. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
  474. package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
  475. package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
  476. package/src/widgets/radio/base-radio.jsx +642 -0
  477. package/src/widgets/radio/choice-icon.jsx +468 -0
  478. package/src/widgets/radio/choice-none-above.jsx +54 -0
  479. package/src/widgets/radio/choice.jsx +692 -0
  480. package/src/widgets/radio/choice.jsx.fixture.js +64 -0
  481. package/src/widgets/radio/cross-out-button.jsx +136 -0
  482. package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
  483. package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
  484. package/src/widgets/radio/focus-ring.jsx +51 -0
  485. package/src/widgets/radio/option-status.jsx +89 -0
  486. package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
  487. package/src/widgets/radio/util.js +32 -0
  488. package/src/widgets/radio/util_test.js +19 -0
  489. package/src/widgets/radio/widget.jsx +470 -0
  490. package/src/widgets/radio.jsx +156 -0
  491. package/src/widgets/reaction-diagram.jsx +136 -0
  492. package/src/widgets/sequence.jsx +140 -0
  493. package/src/widgets/simulator.jsx +808 -0
  494. package/src/widgets/sorter.jsx +146 -0
  495. package/src/widgets/table.jsx +395 -0
  496. package/src/widgets/transformer.jsx +2870 -0
  497. package/src/widgets/unit.jsx +371 -0
  498. package/src/widgets/video-transcript-link.jsx +84 -0
  499. package/src/widgets/video-transcript-link_test.js +211 -0
  500. package/src/widgets/video.jsx +131 -0
  501. package/src/widgets.js +431 -0
  502. package/src/zoom.js +539 -0
  503. package/src/__tests__/index.test.js +0 -14
@@ -0,0 +1,470 @@
1
+ /* eslint-disable react/sort-comp */
2
+ // @flow
3
+ import * as i18n from "@khanacademy/wonder-blocks-i18n";
4
+ import * as React from "react";
5
+ import _ from "underscore";
6
+
7
+ import {linterContextDefault} from "../../gorgon/proptypes.js";
8
+ import Renderer from "../../renderer.jsx";
9
+ import Util from "../../util.js";
10
+ import PassageRef from "../passage-ref.jsx";
11
+
12
+ import BaseRadio from "./base-radio.jsx";
13
+
14
+ import type {
15
+ PerseusRadioChoice,
16
+ PerseusRadioWidgetOptions,
17
+ } from "../../perseus-types.js";
18
+ import type {PerseusScore, WidgetProps} from "../../types.js";
19
+
20
+ // RenderProps is the return type for radio.jsx#transform
21
+ export type RenderProps = {|
22
+ numCorrect: number,
23
+ hasNoneOfTheAbove?: boolean,
24
+ multipleSelect?: boolean,
25
+ countChoices?: boolean,
26
+ deselectEnabled?: boolean,
27
+ choices: $ReadOnlyArray<PerseusRadioChoice>,
28
+ selectedChoices: $ReadOnlyArray<PerseusRadioChoice["correct"]>,
29
+ choiceStates?: $ReadOnlyArray<$FlowFixMe>,
30
+ |};
31
+
32
+ type UserInput = {|
33
+ countChoices?: boolean,
34
+ choicesSelected: $ReadOnlyArray<boolean>,
35
+ numCorrect?: number,
36
+ noneOfTheAboveIndex?: ?number,
37
+ noneOfTheAboveSelected?: boolean,
38
+ |};
39
+ type Rubric = PerseusRadioWidgetOptions;
40
+ type Props = WidgetProps<RenderProps, Rubric>;
41
+
42
+ class Radio extends React.Component<Props> {
43
+ static defaultProps: $FlowFixMe = {
44
+ choices: [{}],
45
+ displayCount: null,
46
+ multipleSelect: false,
47
+ countChoices: false,
48
+ deselectEnabled: false,
49
+ linterContext: linterContextDefault,
50
+ };
51
+
52
+ _renderRenderer: (string) => React.Node = (content) => {
53
+ content = content || "";
54
+
55
+ let nextPassageRefId = 1;
56
+ const widgets = {};
57
+
58
+ const modContent = content.replace(
59
+ /\{\{passage-ref (\d+) (\d+)(?: "([^"]*)")?\}\}/g,
60
+ (match, passageNum, refNum, summaryText) => {
61
+ const widgetId = "passage-ref " + nextPassageRefId;
62
+ nextPassageRefId++;
63
+
64
+ widgets[widgetId] = {
65
+ type: "passage-ref",
66
+ graded: false,
67
+ options: {
68
+ passageNumber: parseInt(passageNum),
69
+ referenceNumber: parseInt(refNum),
70
+ summaryText: summaryText,
71
+ },
72
+ version: PassageRef.version,
73
+ };
74
+
75
+ return "[[" + Util.snowman + " " + widgetId + "]]";
76
+ },
77
+ );
78
+
79
+ // alwaysUpdate={true} so that passage-refs findWidgets
80
+ // get called when the outer passage updates the renderer
81
+ // TODO(aria): This is really hacky
82
+ // We pass in a key here so that we avoid a semi-spurious
83
+ // react warning when the ChoiceNoneAbove renders a
84
+ // different renderer in the same place. Note this destroys
85
+ // state, but since all we're doing is outputting
86
+ // "None of the above", that is okay.
87
+ // TODO(mdr): Widgets inside this Renderer are not discoverable through
88
+ // the parent Renderer's `findWidgets` function.
89
+ return (
90
+ <Renderer
91
+ key="choiceContentRenderer"
92
+ content={modContent}
93
+ widgets={widgets}
94
+ findExternalWidgets={this.props.findWidgets}
95
+ alwaysUpdate={true}
96
+ linterContext={{
97
+ ...this.props.linterContext,
98
+ blockHighlight: true,
99
+ }}
100
+ />
101
+ );
102
+ };
103
+
104
+ // TODO(LP-10672): I think this might be unused right now. I can't find anywhere
105
+ // that we pass a value to `.focus()` and it seems to have been used for
106
+ // adding hints when editing.
107
+ // 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
+ };
112
+
113
+ // When `BaseRadio`'s `onChange` handler is called, indicating a change in
114
+ // our choices' state, we need to call our `onChange` handler in order to
115
+ // persist those changes in the item's Perseus state.
116
+ //
117
+ // So, given the new values for each choice, construct the new
118
+ // `choiceStates` objects, and pass them to `this.props.onChange`.
119
+ //
120
+ // `newValueLists` is an object with two keys: `checked` and `crossedOut`.
121
+ // Each contains an array of boolean values, specifying the new checked and
122
+ // crossed-out value of each choice.
123
+ //
124
+ // NOTE(mdr): This method expects to be auto-bound. If this component is
125
+ // converted to an ES6 class, take care to auto-bind this method!
126
+ updateChoices: ($FlowFixMe) => void = (newValueLists) => {
127
+ const {choiceStates, choices} = this.props;
128
+
129
+ // Construct the baseline `choiceStates` objects. If this is the user's
130
+ // first interaction with the widget, we'll need to initialize them to
131
+ // new objects with all fields set to the default values. Otherwise, we
132
+ // should clone the old `choiceStates` objects, in preparation to
133
+ // 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
+ }
148
+
149
+ // Mutate the new `choiceState` objects, according to the new `checked`
150
+ // and `crossedOut` values provided in `newValueLists`.
151
+ newChoiceStates.forEach((choiceState, i) => {
152
+ choiceState.selected = newValueLists.checked[i];
153
+ choiceState.crossedOut = newValueLists.crossedOut[i];
154
+ });
155
+
156
+ this.props.onChange({choiceStates: newChoiceStates});
157
+ this.props.trackInteraction();
158
+ };
159
+
160
+ getUserInput: () => UserInput = () => {
161
+ return Radio.getUserInputFromProps(this.props);
162
+ };
163
+
164
+ simpleValidate: (PerseusRadioWidgetOptions) => PerseusScore = (rubric) => {
165
+ return Radio.validate(this.getUserInput(), rubric);
166
+ };
167
+
168
+ /**
169
+ * Turn on rationale display for the currently selected choices. Note that
170
+ * this leaves rationales on for choices that are already showing
171
+ * rationales.
172
+ */
173
+ showRationalesForCurrentlySelectedChoices: (PerseusRadioWidgetOptions) => void =
174
+ (rubric) => {
175
+ const {choiceStates} = this.props;
176
+ if (choiceStates) {
177
+ const score = this.simpleValidate(rubric);
178
+ const widgetCorrect =
179
+ score.type === "points" && score.total === score.earned;
180
+
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
+ }));
200
+
201
+ this.props.onChange(
202
+ {
203
+ choiceStates: newStates,
204
+ },
205
+ null, // cb
206
+ true, // silent
207
+ );
208
+ }
209
+ };
210
+
211
+ /**
212
+ * Deselects any currently-selected choices that are not correct choices.
213
+ */
214
+ deselectIncorrectSelectedChoices: () => void = () => {
215
+ 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
+ }));
221
+
222
+ this.props.onChange(
223
+ {
224
+ choiceStates: newStates,
225
+ },
226
+ null, // cb
227
+ false, // silent
228
+ );
229
+ }
230
+ };
231
+
232
+ render(): React.Node {
233
+ let choices = this.props.choices;
234
+ let choiceStates;
235
+ if (this.props.static) {
236
+ choiceStates = _.map(choices, (val) => ({
237
+ selected: val.correct,
238
+ crossedOut: val.crossedOut,
239
+ readOnly: true,
240
+ highlighted: false,
241
+ rationaleShown: true,
242
+ correctnessShown: true,
243
+ previouslyAnswered: false,
244
+ }));
245
+ } else if (this.props.choiceStates) {
246
+ choiceStates = this.props.choiceStates;
247
+ // $FlowFixMe[prop-missing]
248
+ } else if (this.props.values) {
249
+ // Support legacy choiceStates implementation
250
+ /* istanbul ignore next - props.values is deprecated */
251
+ choiceStates = _.map(this.props.values, (val) => ({
252
+ selected: val,
253
+ crossedOut: false,
254
+ readOnly: false,
255
+ highlighted: false,
256
+ rationaleShown: false,
257
+ correctnessShown: false,
258
+ previouslyAnswered: false,
259
+ }));
260
+ } else {
261
+ choiceStates = _.map(choices, () => ({
262
+ selected: false,
263
+ crossedOut: false,
264
+ readOnly: false,
265
+ highlighted: false,
266
+ rationaleShown: false,
267
+ correctnessShown: false,
268
+ previouslyAnswered: false,
269
+ }));
270
+ }
271
+
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
+ });
324
+
325
+ return (
326
+ <BaseRadio
327
+ // eslint-disable-next-line react/no-string-refs
328
+ ref="baseRadio"
329
+ labelWrap={true}
330
+ multipleSelect={this.props.multipleSelect}
331
+ countChoices={this.props.countChoices}
332
+ numCorrect={this.props.numCorrect}
333
+ choices={choices}
334
+ onChange={this.updateChoices}
335
+ reviewModeRubric={this.props.reviewModeRubric}
336
+ deselectEnabled={this.props.deselectEnabled}
337
+ apiOptions={this.props.apiOptions}
338
+ isLastUsedWidget={this.props.isLastUsedWidget}
339
+ />
340
+ );
341
+ }
342
+
343
+ static validate(userInput: UserInput, rubric: Rubric): PerseusScore {
344
+ const numSelected = _.reduce(
345
+ userInput.choicesSelected,
346
+ (sum, selected) => {
347
+ return sum + (selected ? 1 : 0);
348
+ },
349
+ 0,
350
+ );
351
+
352
+ if (numSelected === 0) {
353
+ return {
354
+ type: "invalid",
355
+ message: null,
356
+ };
357
+ }
358
+ // $FlowFixMe[invalid-compare]
359
+ if (userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
360
+ return {
361
+ type: "invalid",
362
+ message: i18n._("Please choose the correct number of answers."),
363
+ };
364
+ // If NOTA and some other answer are checked, ...
365
+ }
366
+ if (userInput.noneOfTheAboveSelected && numSelected > 1) {
367
+ return {
368
+ type: "invalid",
369
+ message: i18n._(
370
+ "'None of the above' may not be selected " +
371
+ "when other answers are selected.",
372
+ ),
373
+ };
374
+ }
375
+ /* jshint -W018 */
376
+ const correct = _.all(userInput.choicesSelected, (selected, i) => {
377
+ let isCorrect;
378
+ if (userInput.noneOfTheAboveIndex === i) {
379
+ isCorrect = _.all(rubric.choices, (choice, j) => {
380
+ return i === j || !choice.correct;
381
+ });
382
+ } else {
383
+ isCorrect = !!rubric.choices[i].correct;
384
+ }
385
+ return isCorrect === selected;
386
+ });
387
+ /* jshint +W018 */
388
+
389
+ return {
390
+ type: "points",
391
+ earned: correct ? 1 : 0,
392
+ total: 1,
393
+ message: null,
394
+ };
395
+ }
396
+
397
+ static getUserInputFromProps(props: Props): UserInput {
398
+ // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
399
+ // future timeline implementers: this used to be {value: i} before
400
+ // multiple select was added)
401
+ if (props.choiceStates) {
402
+ let noneOfTheAboveIndex = null;
403
+ let noneOfTheAboveSelected = false;
404
+
405
+ const choiceStates = props.choiceStates;
406
+ const choicesSelected = choiceStates.map(() => false);
407
+ const countChoices = props.countChoices;
408
+ const numCorrect = props.numCorrect;
409
+
410
+ for (let i = 0; i < choicesSelected.length; i++) {
411
+ // $FlowFixMe[prop-missing]
412
+ const index = props.choices[i].originalIndex;
413
+ // $FlowFixMe[incompatible-use]
414
+ choicesSelected[index] = choiceStates[i].selected;
415
+
416
+ if (props.choices[i].isNoneOfTheAbove) {
417
+ noneOfTheAboveIndex = index;
418
+
419
+ if (choicesSelected[i]) {
420
+ noneOfTheAboveSelected = true;
421
+ }
422
+ }
423
+ }
424
+
425
+ return {
426
+ countChoices,
427
+ choicesSelected,
428
+ numCorrect,
429
+ noneOfTheAboveIndex,
430
+ noneOfTheAboveSelected,
431
+ };
432
+ // Support legacy choiceState implementation
433
+ }
434
+ /* istanbul ignore if - props.values is deprecated */
435
+ // $FlowFixMe[prop-missing]
436
+ if (props.values) {
437
+ let noneOfTheAboveIndex = null;
438
+ let noneOfTheAboveSelected = false;
439
+
440
+ const values = props.values.slice();
441
+ const countChoices = props.countChoices;
442
+ const numCorrect = props.numCorrect;
443
+
444
+ for (let i = 0; i < props.values.length; i++) {
445
+ const index = props.choices[i].originalIndex;
446
+ values[index] = props.values[i];
447
+
448
+ if (props.choices[i].isNoneOfTheAbove) {
449
+ noneOfTheAboveIndex = index;
450
+ if (values[i]) {
451
+ noneOfTheAboveSelected = true;
452
+ }
453
+ }
454
+ }
455
+ return {
456
+ choicesSelected: values,
457
+ noneOfTheAboveIndex,
458
+ noneOfTheAboveSelected,
459
+ countChoices,
460
+ numCorrect,
461
+ };
462
+ }
463
+ // Nothing checked
464
+ return {
465
+ choicesSelected: _.map(props.choices, () => false),
466
+ };
467
+ }
468
+ }
469
+
470
+ export default Radio;
@@ -0,0 +1,156 @@
1
+ // @flow
2
+ import * as i18n from "@khanacademy/wonder-blocks-i18n";
3
+ import _ from "underscore";
4
+
5
+ import Util from "../util.js";
6
+
7
+ import Radio from "./radio/widget.jsx";
8
+
9
+ import type {
10
+ PerseusRadioWidgetOptions,
11
+ PerseusRadioChoice,
12
+ } from "../perseus-types.js";
13
+ import type {WidgetExports} from "../types.js";
14
+ import type {RenderProps} from "./radio/widget.jsx";
15
+
16
+ const {shuffle, random} = Util;
17
+
18
+ // Represents choices that we automatically re-order if encountered.
19
+ // Note: these are in the reversed (incorrect) order that we will swap, if
20
+ // found.
21
+ // Note 2: these are internationalized when compared later on.
22
+ const ReversedChoices: $ReadOnlyArray<[string, string]> = [
23
+ [i18n._("False"), i18n._("True")],
24
+ [i18n._("No"), i18n._("Yes")],
25
+ ];
26
+
27
+ // Transforms the choices for display.
28
+ const _choiceTransform = (
29
+ widgetOptions: PerseusRadioWidgetOptions,
30
+ problemNum: ?number,
31
+ ) => {
32
+ const _maybeRandomize = function (array) {
33
+ const randomSeed = problemNum === undefined ? random : problemNum;
34
+ // NOTE: `problemNum` will only be set when the radio widget is
35
+ // rendered at the root of an exercise question. It will be `undefined`
36
+ // if it's rendered embedded in another widget, such as `graded-group`,
37
+ // or if rendered within an article. This results in a predictable
38
+ // shuffle order. To avoid this we use a random seed when `problemNum`
39
+ // is `undefined`.
40
+ return widgetOptions.randomize
41
+ ? shuffle(array, randomSeed ?? 0)
42
+ : array;
43
+ };
44
+
45
+ const _addNoneOfAbove = function (
46
+ choices: $ReadOnlyArray<PerseusRadioChoice>,
47
+ ): $ReadOnlyArray<PerseusRadioChoice> {
48
+ let noneOfTheAbove = null;
49
+
50
+ const newChoices = _.reject(choices, function (choice, index) {
51
+ if (choice.isNoneOfTheAbove) {
52
+ noneOfTheAbove = choice;
53
+ return true;
54
+ }
55
+ });
56
+
57
+ // Place the "None of the above" options last
58
+ if (noneOfTheAbove) {
59
+ newChoices.push(noneOfTheAbove);
60
+ }
61
+
62
+ return newChoices;
63
+ };
64
+
65
+ const enforceOrdering = (
66
+ choices: $ReadOnlyArray<PerseusRadioChoice>,
67
+ ): $ReadOnlyArray<PerseusRadioChoice> => {
68
+ const content = choices.map((c) => c.content);
69
+ if (ReversedChoices.some((reversed) => _.isEqual(content, reversed))) {
70
+ return [choices[1], choices[0]];
71
+ }
72
+ return choices;
73
+ };
74
+
75
+ // 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),
81
+ });
82
+ });
83
+
84
+ // Apply all the transforms. Note that the order we call these is
85
+ // important!
86
+ // 3) finally add "None of the above" to the bottom
87
+ return _addNoneOfAbove(
88
+ // 2) then (potentially) enforce ordering (eg. False, True becomes
89
+ // True, False)
90
+ enforceOrdering(
91
+ // 1) we randomize the order first
92
+ _maybeRandomize(choices),
93
+ ),
94
+ );
95
+ };
96
+
97
+ const transform = (
98
+ widgetOptions: PerseusRadioWidgetOptions,
99
+ problemNum: ?number,
100
+ ): RenderProps => {
101
+ const choices = _choiceTransform(widgetOptions, problemNum);
102
+
103
+ const numCorrect: number = _.reduce(
104
+ widgetOptions.choices,
105
+ function (memo, choice) {
106
+ return choice.correct ? memo + 1 : memo;
107
+ },
108
+ 0,
109
+ );
110
+
111
+ const {hasNoneOfTheAbove, multipleSelect, countChoices, deselectEnabled} =
112
+ widgetOptions;
113
+
114
+ return {
115
+ numCorrect,
116
+ hasNoneOfTheAbove,
117
+ multipleSelect,
118
+ countChoices,
119
+ deselectEnabled,
120
+ choices,
121
+ selectedChoices: _.pluck(choices, "correct"),
122
+ };
123
+ };
124
+
125
+ const propUpgrades = {
126
+ "1": (v0props: $FlowFixMe): $FlowFixMe => {
127
+ let choices;
128
+ let hasNoneOfTheAbove;
129
+
130
+ if (!v0props.noneOfTheAbove) {
131
+ choices = v0props.choices;
132
+ hasNoneOfTheAbove = false;
133
+ } else {
134
+ throw new Error(
135
+ "radio widget v0 no longer supports auto noneOfTheAbove",
136
+ );
137
+ }
138
+
139
+ return _.extend(_.omit(v0props, "noneOfTheAbove"), {
140
+ choices: choices,
141
+ hasNoneOfTheAbove: hasNoneOfTheAbove,
142
+ });
143
+ },
144
+ };
145
+
146
+ export default ({
147
+ name: "radio",
148
+ displayName: "Multiple choice",
149
+ accessible: true,
150
+ widget: Radio,
151
+ transform: transform,
152
+ staticTransform: transform,
153
+ version: {major: 1, minor: 0},
154
+ propUpgrades: propUpgrades,
155
+ isLintable: true,
156
+ }: WidgetExports<typeof Radio>);