@khanacademy/perseus 43.1.0 → 45.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 (57) hide show
  1. package/dist/all-widgets.d.ts +2 -23
  2. package/dist/basic-widgets.d.ts +2 -23
  3. package/dist/components/graph.d.ts +1 -0
  4. package/dist/components/icon.d.ts +0 -1
  5. package/dist/components/multi-button-group.d.ts +1 -0
  6. package/dist/components/tooltip.d.ts +1 -0
  7. package/dist/es/index.js +276 -212
  8. package/dist/es/index.js.map +1 -1
  9. package/dist/es/strings.js +4 -2
  10. package/dist/es/strings.js.map +1 -1
  11. package/dist/extra-widgets.d.ts +2 -23
  12. package/dist/index.d.ts +4 -2
  13. package/dist/index.js +273 -205
  14. package/dist/index.js.map +1 -1
  15. package/dist/interactive2/interactive-util.d.ts +1 -1
  16. package/dist/interactive2/movable-point-options.d.ts +2 -2
  17. package/dist/interactive2/movable-point.d.ts +7 -7
  18. package/dist/interactive2/objective_.d.ts +1 -1
  19. package/dist/jipt-paragraphs.d.ts +2 -2
  20. package/dist/perseus-api.d.ts +2 -77
  21. package/dist/perseus-types.d.ts +3 -10
  22. package/dist/renderer-util.d.ts +4 -2
  23. package/dist/renderer.d.ts +6 -10
  24. package/dist/server-item-renderer.d.ts +58 -50
  25. package/dist/strings.d.ts +1 -0
  26. package/dist/strings.js +4 -2
  27. package/dist/strings.js.map +1 -1
  28. package/dist/styles/global-styles.d.ts +0 -1
  29. package/dist/styles/shared.d.ts +0 -1
  30. package/dist/traversal.d.ts +1 -1
  31. package/dist/types.d.ts +5 -0
  32. package/dist/util/a11y.d.ts +0 -1
  33. package/dist/util/debounce.d.ts +1 -1
  34. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +2 -2
  35. package/dist/widgets/definition/definition.d.ts +1 -1
  36. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -1
  37. package/dist/widgets/dropdown/dropdown.d.ts +9 -1
  38. package/dist/widgets/explanation/explanation.d.ts +1 -1
  39. package/dist/widgets/expression/expression.d.ts +92 -217
  40. package/dist/widgets/group/group.d.ts +1 -1
  41. package/dist/widgets/image/image.d.ts +1 -1
  42. package/dist/widgets/interaction/interaction.d.ts +1 -1
  43. package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +1 -1
  44. package/dist/widgets/label-image/answer-pill.d.ts +0 -1
  45. package/dist/widgets/label-image/label-image.d.ts +5 -65
  46. package/dist/widgets/label-image/marker.d.ts +0 -1
  47. package/dist/widgets/measurer/measurer.d.ts +1 -1
  48. package/dist/widgets/molecule/molecule.d.ts +1 -1
  49. package/dist/widgets/passage/passage-markdown.d.ts +2 -2
  50. package/dist/widgets/passage/passage.d.ts +1 -1
  51. package/dist/widgets/passage-ref/passage-ref.d.ts +1 -1
  52. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +1 -1
  53. package/dist/widgets/radio/choice-icon/shared-styles.d.ts +0 -1
  54. package/dist/widgets/radio/choice-none-above.d.ts +1 -1
  55. package/dist/widgets/radio/radio.d.ts +2 -2
  56. package/dist/widgets/video/video.d.ts +1 -1
  57. package/package.json +2 -2
package/dist/es/index.js CHANGED
@@ -4,7 +4,7 @@ import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
5
5
  import { View, RenderStateRoot, UniqueIDProvider, useOnMountEffect, useLatestRef, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
- import { HeadingMedium, LabelSmall, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
7
+ import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
8
8
  import { StyleSheet, css } from 'aphrodite';
9
9
  import classNames$1 from 'classnames';
10
10
  import * as React from 'react';
@@ -249,7 +249,8 @@ const mockStrings = {
249
249
  srInteractiveElements: ({
250
250
  elements
251
251
  }) => `Interactive elements: ${elements}`,
252
- srNoInteractiveElements: "No interactive elements"
252
+ srNoInteractiveElements: "No interactive elements",
253
+ selectAnAnswer: "Select an answer"
253
254
  };
254
255
 
255
256
  /**
@@ -14608,9 +14609,9 @@ const pluck = function pluck(table, subKey) {
14608
14609
  * });
14609
14610
  * {a: 2, b: 3}
14610
14611
  */
14611
- const mapObject$1 = function mapObject(obj, lambda) {
14612
+ const mapObject$2 = function mapObject(obj, lambda) {
14612
14613
  const result = {};
14613
- _.each(_.keys(obj), function (key) {
14614
+ Object.keys(obj).forEach(key => {
14614
14615
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
14615
14616
  result[key] = lambda(obj[key], key);
14616
14617
  });
@@ -14627,14 +14628,14 @@ const mapObject$1 = function mapObject(obj, lambda) {
14627
14628
  */
14628
14629
  const mapObjectFromArray = function mapObjectFromArray(arr, lambda) {
14629
14630
  const result = {};
14630
- _.each(arr, function (elem) {
14631
+ arr.forEach(elem => {
14631
14632
  result[elem] = lambda(elem);
14632
14633
  });
14633
14634
  return result;
14634
14635
  };
14635
14636
  var objective_ = {
14636
14637
  pluck: pluck,
14637
- mapObject: mapObject$1,
14638
+ mapObject: mapObject$2,
14638
14639
  mapObjectFromArray: mapObjectFromArray
14639
14640
  };
14640
14641
 
@@ -16629,7 +16630,7 @@ class Graphie extends React.Component {
16629
16630
  };
16630
16631
  this._setupGraphie = () => {
16631
16632
  this._removeMovables();
16632
- const graphieDiv = ReactDOM__default.findDOMNode(this.graphieDivRef.current);
16633
+ const graphieDiv = this.graphieDivRef.current;
16633
16634
  if (graphieDiv == null || graphieDiv instanceof Text) {
16634
16635
  throw new Error("No graphie container div found");
16635
16636
  }
@@ -18718,6 +18719,140 @@ class QuestionParagraph extends React.Component {
18718
18719
  }
18719
18720
  }
18720
18721
 
18722
+ // These are all the current widgets that require user-input and are supported for automatic scoring
18723
+ const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
18724
+
18725
+ /**
18726
+ * Regex for widget placeholders in a string.
18727
+ *
18728
+ * First capture group is the widget ID (ex. 'radio 1')
18729
+ * Second capture group is the widget type (ex. "radio)
18730
+ * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
18731
+ */
18732
+ function getWidgetRegex() {
18733
+ return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
18734
+ }
18735
+
18736
+ /**
18737
+ * Add a widget placeholder using the widget ID.
18738
+ * ex. addWidget("radio 1") => "[[☃ radio 1]]"
18739
+ *
18740
+ * @param {string} id
18741
+ * @returns {string}
18742
+ */
18743
+ function addWidget(id) {
18744
+ return `[[☃ ${id}]]`;
18745
+ }
18746
+
18747
+ /**
18748
+ * Get a widget type by a widget's ID
18749
+ * (please don't derive type from ID)
18750
+ *
18751
+ * @param {string} widgetId the ID of the widget
18752
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
18753
+ * @returns {string} the widget type (ie "radio")
18754
+ */
18755
+ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
18756
+ var _widget$type;
18757
+ const widget = widgetMap[WidgetId];
18758
+ return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
18759
+ }
18760
+
18761
+ /**
18762
+ * Does the content have a specific type of widget?
18763
+ *
18764
+ * @param {string} type the type of the widget in question (ie "radio")
18765
+ * @param {string} content the string to search through
18766
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
18767
+ * @returns {boolean} if the content includes the widget type
18768
+ */
18769
+ function contentHasWidgetType(type, content, widgetMap) {
18770
+ return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
18771
+ }
18772
+
18773
+ /**
18774
+ * Extract all widget IDs, which includes the widget type and instance number.
18775
+ * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
18776
+ *
18777
+ * Content should contain Perseus widget placeholders,
18778
+ * which look like: '[[☃ radio 1]]'.
18779
+ *
18780
+ * @param {string} content
18781
+ * @returns {ReadonlyArray<string>} widgetIds
18782
+ */
18783
+ function getWidgetIdsFromContent(content) {
18784
+ const widgets = [];
18785
+ const localWidgetRegex = getWidgetRegex();
18786
+ let match = localWidgetRegex.exec(content);
18787
+ while (match !== null) {
18788
+ widgets.push(match[1]);
18789
+ match = localWidgetRegex.exec(content);
18790
+ }
18791
+ return widgets;
18792
+ }
18793
+
18794
+ /**
18795
+ * Get a list of widget IDs from content,
18796
+ * but only for specific widget types
18797
+ *
18798
+ * @param {string} type the type of widget (ie "radio")
18799
+ * @param {string} content the string to parse
18800
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
18801
+ * @returns {ReadonlyArray<string>} the widget type (ie "radio")
18802
+ */
18803
+ function getWidgetIdsFromContentByType(type, content, widgetMap) {
18804
+ const rv = [];
18805
+ const widgetIdsInContent = getWidgetIdsFromContent(content);
18806
+ widgetIdsInContent.forEach(widgetId => {
18807
+ const widget = widgetMap[widgetId];
18808
+ if ((widget == null ? void 0 : widget.type) === type) {
18809
+ rv.push(widgetId);
18810
+ }
18811
+ });
18812
+ return rv;
18813
+ }
18814
+
18815
+ /**
18816
+ * Pull the widget map out of ItemData
18817
+ *
18818
+ * @param {PerseusItem} PerseusItem containing a widgetMap
18819
+ * @returns {WidgetMap} the widget map in the PerseusItem
18820
+ */
18821
+ function getWidgetsMapFromItemData(itemData) {
18822
+ return itemData.question.widgets;
18823
+ }
18824
+
18825
+ /**
18826
+ * Get the widget information from a WidgetMap
18827
+ *
18828
+ * @param {string} widgetId the ID of the widget
18829
+ * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
18830
+ */
18831
+ function getWidgetFromWidgetMap(widgetId, widgetMap) {
18832
+ var _widgetMap$widgetId;
18833
+ return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
18834
+ }
18835
+
18836
+ /**
18837
+ * Get select widgets from a widget map.
18838
+ * Useful for multi-items that needs to split
18839
+ * one PerseusItem into several
18840
+ *
18841
+ * @param {ReadonlyArray<string>} widgetIds to extract
18842
+ * @param {PerseusWidgetsMap} widgetMap to extract from
18843
+ * @return {PerseusWidgetsMap} a new widget map with requested widgets
18844
+ */
18845
+ function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
18846
+ const widgets = {};
18847
+ widgetIds.forEach(widgetId => {
18848
+ const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
18849
+ if (widget) {
18850
+ widgets[widgetId] = widget;
18851
+ }
18852
+ });
18853
+ return widgets;
18854
+ }
18855
+
18721
18856
  const DEFAULT_ALIGNMENT$1 = "block";
18722
18857
  // NOTE(kevinb): "default" is not one in `validAlignments`.
18723
18858
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -19149,12 +19284,35 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19149
19284
  isLintable: isLintable
19150
19285
  });
19151
19286
 
19287
+ const {
19288
+ mapObject: mapObject$1
19289
+ } = objective_;
19290
+ function getUpgradedWidgetOptions(oldWidgetOptions) {
19291
+ // @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
19292
+ return mapObject$1(oldWidgetOptions, (widgetInfo, widgetId) => {
19293
+ if (!widgetInfo.type || !widgetInfo.alignment) {
19294
+ const newValues = {};
19295
+ if (!widgetInfo.type) {
19296
+ // TODO: why does widget have no type?
19297
+ // We don't want to derive type from widget ID
19298
+ // see: LEMS-1845
19299
+ newValues.type = widgetId.split(" ")[0];
19300
+ }
19301
+ if (!widgetInfo.alignment) {
19302
+ newValues.alignment = "default";
19303
+ }
19304
+ widgetInfo = _extends({}, widgetInfo, newValues);
19305
+ }
19306
+ return upgradeWidgetInfoToLatestVersion(widgetInfo);
19307
+ });
19308
+ }
19152
19309
  function emptyWidgetsFunctional(widgets,
19153
19310
  // This is a port of old code, I'm not sure why
19154
19311
  // we need widgetIds vs the keys of the widgets object
19155
19312
  widgetIds, userInputMap, strings, locale) {
19313
+ const upgradedWidgets = getUpgradedWidgetOptions(widgets);
19156
19314
  return widgetIds.filter(id => {
19157
- const widget = widgets[id];
19315
+ const widget = upgradedWidgets[id];
19158
19316
  if (!widget || widget.static) {
19159
19317
  // Static widgets shouldn't count as empty
19160
19318
  return false;
@@ -19173,12 +19331,27 @@ widgetIds, userInputMap, strings, locale) {
19173
19331
  }
19174
19332
  });
19175
19333
  }
19334
+
19335
+ // TODO: combine scorePerseusItem with scoreWidgetsFunctional
19336
+ // once scorePerseusItem is the only one calling scoreWidgetsFunctional
19337
+ function scorePerseusItem(perseusRenderData, userInputMap,
19338
+ // TODO(LEMS-2461,LEMS-2391): these probably
19339
+ // need to be removed before we move this to the server
19340
+ strings, locale) {
19341
+ // There seems to be a chance that PerseusRenderer.widgets might include
19342
+ // widget data for widgets that are not in PerseusRenderer.content,
19343
+ // so this checks that the widgets are being used before scoring them
19344
+ const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
19345
+ const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
19346
+ return Util.flattenScores(scores);
19347
+ }
19176
19348
  function scoreWidgetsFunctional(widgets,
19177
19349
  // This is a port of old code, I'm not sure why
19178
19350
  // we need widgetIds vs the keys of the widgets object
19179
19351
  widgetIds, userInputMap, strings, locale) {
19352
+ const upgradedWidgets = getUpgradedWidgetOptions(widgets);
19180
19353
  const gradedWidgetIds = widgetIds.filter(id => {
19181
- const props = widgets[id];
19354
+ const props = upgradedWidgets[id];
19182
19355
  const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
19183
19356
  const widgetIsStatic = !!(props != null && props.static);
19184
19357
  // Ungraded widgets or widgets set to static shouldn't be graded.
@@ -19186,14 +19359,14 @@ widgetIds, userInputMap, strings, locale) {
19186
19359
  });
19187
19360
  const widgetScores = {};
19188
19361
  gradedWidgetIds.forEach(id => {
19189
- const widget = widgets[id];
19362
+ const widget = upgradedWidgets[id];
19190
19363
  if (!widget) {
19191
19364
  return;
19192
19365
  }
19193
19366
  const userInput = userInputMap[id];
19194
19367
  const scorer = getWidgetScorer(widget.type);
19195
19368
  if (widget.type === "group") {
19196
- const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19369
+ const scores = scoreWidgetsFunctional(widget.options.widgets, getWidgetIdsFromContent(widget.options.content), userInputMap[id], strings, locale);
19197
19370
  widgetScores[id] = Util.flattenScores(scores);
19198
19371
  } else if (scorer) {
19199
19372
  widgetScores[id] = scorer(userInput, widget.options, strings, locale);
@@ -19555,31 +19728,12 @@ class Renderer extends React.Component {
19555
19728
  return _extends({}, ApiOptions.defaults, this.props.apiOptions);
19556
19729
  };
19557
19730
  this._getInitialWidgetState = props => {
19558
- const allWidgetInfo = this._getAllWidgetsInfo(props);
19731
+ const allWidgetInfo = getUpgradedWidgetOptions(props.widgets);
19559
19732
  return {
19560
19733
  widgetInfo: allWidgetInfo,
19561
19734
  widgetProps: this._getAllWidgetsStartProps(allWidgetInfo, props)
19562
19735
  };
19563
19736
  };
19564
- // @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
19565
- this._getAllWidgetsInfo = props => {
19566
- return mapObject(props.widgets, (widgetInfo, widgetId) => {
19567
- if (!widgetInfo.type || !widgetInfo.alignment) {
19568
- const newValues = {};
19569
- if (!widgetInfo.type) {
19570
- // TODO: why does widget have no type?
19571
- // We don't want to derive type from widget ID
19572
- // see: LEMS-1845
19573
- newValues.type = widgetId.split(" ")[0];
19574
- }
19575
- if (!widgetInfo.alignment) {
19576
- newValues.alignment = "default";
19577
- }
19578
- widgetInfo = _.extend({}, widgetInfo, newValues);
19579
- }
19580
- return upgradeWidgetInfoToLatestVersion(widgetInfo);
19581
- });
19582
- };
19583
19737
  this._getAllWidgetsStartProps = (allWidgetInfo, props) => {
19584
19738
  const {
19585
19739
  apiOptions,
@@ -20025,7 +20179,7 @@ class Renderer extends React.Component {
20025
20179
  // Instead, we center them with css magic in styles.less
20026
20180
  // /cry(aria)
20027
20181
  this._foundTextNodes = true;
20028
- if (_.contains(this.widgetIds, node.id)) {
20182
+ if (this.widgetIds.includes(node.id)) {
20029
20183
  // We don't want to render a duplicate widget key/ref,
20030
20184
  // as this causes problems with react (for obvious
20031
20185
  // reasons). Instead we just notify the
@@ -20329,7 +20483,7 @@ class Renderer extends React.Component {
20329
20483
  };
20330
20484
  this.getInputPaths = () => {
20331
20485
  const inputPaths = [];
20332
- _.each(this.widgetIds, widgetId => {
20486
+ this.widgetIds.forEach(widgetId => {
20333
20487
  const widget = this.getWidgetInstance(widgetId);
20334
20488
  if (widget && widget.getInputPaths) {
20335
20489
  // Grab all input paths and add widgetID to the front
@@ -20337,7 +20491,7 @@ class Renderer extends React.Component {
20337
20491
  // Prefix paths with their widgetID and add to collective
20338
20492
  // list of paths.
20339
20493
  // @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
20340
- _.each(widgetInputPaths, inputPath => {
20494
+ widgetInputPaths.forEach(inputPath => {
20341
20495
  const relativeInputPath = [widgetId].concat(inputPath);
20342
20496
  inputPaths.push(relativeInputPath);
20343
20497
  });
@@ -20466,12 +20620,20 @@ class Renderer extends React.Component {
20466
20620
  this.getWidgetIds = () => {
20467
20621
  return this.widgetIds;
20468
20622
  };
20623
+ /**
20624
+ * @deprecated use scorePerseusItem
20625
+ */
20626
+ this.guessAndScore = () => {
20627
+ const totalGuess = this.getUserInput();
20628
+ const totalScore = this.score();
20629
+ return [totalGuess, totalScore];
20630
+ };
20469
20631
  this.examples = () => {
20470
20632
  const widgetIds = this.widgetIds;
20471
- const examples = _.compact(_.map(widgetIds, widgetId => {
20633
+ const examples = widgetIds.map(widgetId => {
20472
20634
  const widget = this.getWidgetInstance(widgetId);
20473
20635
  return widget != null && widget.examples ? widget.examples() : null;
20474
- }));
20636
+ }).filter(Boolean);
20475
20637
 
20476
20638
  // no widgets with examples
20477
20639
  if (!examples.length) {
@@ -20726,27 +20888,15 @@ class Renderer extends React.Component {
20726
20888
  }
20727
20889
 
20728
20890
  /**
20729
- * Returns an object mapping from widget ID to perseus-style score.
20730
- * The keys of this object are the values of the array returned
20731
- * from `getWidgetIds`.
20732
- */
20733
- scoreWidgets() {
20734
- return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20735
- }
20736
-
20737
- /**
20738
- * Grades the content.
20891
+ * Scores the content.
20892
+ *
20893
+ * @deprecated use scorePerseusItem
20739
20894
  */
20740
20895
  score() {
20741
- const scores = this.scoreWidgets();
20896
+ const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20742
20897
  const combinedScore = Util.flattenScores(scores);
20743
20898
  return combinedScore;
20744
20899
  }
20745
- guessAndScore() {
20746
- const totalGuess = this.getUserInput();
20747
- const totalScore = this.score();
20748
- return [totalGuess, totalScore];
20749
- }
20750
20900
  render() {
20751
20901
  const apiOptions = this.getApiOptions();
20752
20902
  if (this.reuseMarkdown) {
@@ -21135,6 +21285,7 @@ const HORIZONTAL_ALIGNMNENTS = {
21135
21285
  class Tooltip extends React.Component {
21136
21286
  constructor(...args) {
21137
21287
  super(...args);
21288
+ this.tooltipContainerRef = /*#__PURE__*/React.createRef();
21138
21289
  this.state = {
21139
21290
  height: null // used for offsetting "top" positioned tooltips
21140
21291
  };
@@ -21209,8 +21360,7 @@ class Tooltip extends React.Component {
21209
21360
  display: this.props.show ? "block" : "none"
21210
21361
  }
21211
21362
  }, /*#__PURE__*/React.createElement("div", {
21212
- // eslint-disable-next-line react/no-string-refs
21213
- ref: "tooltipContainer",
21363
+ ref: this.tooltipContainerRef,
21214
21364
  className: "tooltipContainer",
21215
21365
  style: {
21216
21366
  position: "absolute",
@@ -21237,10 +21387,8 @@ class Tooltip extends React.Component {
21237
21387
  }, this.props.children.slice(1)), arrowBelow));
21238
21388
  }
21239
21389
  _updateHeight() {
21240
- const tooltipContainer = ReactDOM__default.findDOMNode(
21241
- // eslint-disable-next-line react/no-string-refs
21242
- this.refs.tooltipContainer);
21243
- const height = tooltipContainer.offsetHeight;
21390
+ const tooltipContainer = this.tooltipContainerRef.current;
21391
+ const height = (tooltipContainer == null ? void 0 : tooltipContainer.offsetHeight) || 0;
21244
21392
  if (height !== this.state.height) {
21245
21393
  this.setState({
21246
21394
  height
@@ -27224,23 +27372,40 @@ class Dropdown extends React.Component {
27224
27372
  value: String(i + 1),
27225
27373
  label: choice
27226
27374
  }))];
27227
- return /*#__PURE__*/React.createElement("div", {
27228
- // NOTE(jared): These are required to prevent weird behavior
27229
- // When there's a dropdown in a zoomable table.
27375
+ return /*#__PURE__*/React.createElement(UniqueIDProvider, {
27376
+ scope: "dropdown-widget",
27377
+ mockOnFirstRender: true
27378
+ }, ids => /*#__PURE__*/React.createElement(View
27379
+ // NOTE(jared): These are required to prevent weird behavior
27380
+ // When there's a dropdown in a zoomable table.
27381
+ , {
27230
27382
  onClick: e => {
27231
27383
  e.stopPropagation();
27232
27384
  },
27233
27385
  onTouchStart: e => {
27234
27386
  e.stopPropagation();
27235
27387
  }
27236
- }, /*#__PURE__*/React.createElement(SingleSelect, {
27388
+ }, this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelLarge, {
27389
+ tag: "label",
27390
+ id: ids.get("dropdown-label")
27391
+ }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27392
+ id: ids.get("dropdown"),
27237
27393
  placeholder: "",
27238
27394
  onChange: value => this._handleChange(parseInt(value)),
27239
27395
  selectedValue: String(this.props.selected),
27240
- disabled: this.props.apiOptions.readOnly
27241
- }, children));
27396
+ disabled: this.props.apiOptions.readOnly,
27397
+ "aria-label": this.props.ariaLabel || this.context.strings.selectAnAnswer,
27398
+ "aria-labelledby": ids.get("dropdown-label")
27399
+ // This is currently necessary for SRs to read the labels properly.
27400
+ // However, WB is working on a change to add the "combobox" role to
27401
+ // all dropdowns.
27402
+ // See https://khanacademy.atlassian.net/browse/WB-1671
27403
+ ,
27404
+ role: "combobox"
27405
+ }, children)));
27242
27406
  }
27243
27407
  }
27408
+ Dropdown.contextType = PerseusI18nContext;
27244
27409
  Dropdown.defaultProps = {
27245
27410
  choices: [],
27246
27411
  selected: 0,
@@ -27250,6 +27415,8 @@ Dropdown.defaultProps = {
27250
27415
  const optionsTransform = widgetOptions => {
27251
27416
  return {
27252
27417
  placeholder: widgetOptions.placeholder,
27418
+ visibleLabel: widgetOptions.visibleLabel,
27419
+ ariaLabel: widgetOptions.ariaLabel,
27253
27420
  choices: widgetOptions.choices.map(choice => choice.content)
27254
27421
  };
27255
27422
  };
@@ -29309,6 +29476,9 @@ class Group extends React.Component {
29309
29476
  this.change("widgets", this.rendererRef.props.widgets);
29310
29477
  }
29311
29478
  };
29479
+
29480
+ // TODO(LEMS-2391): replace this when there's a separate check
29481
+ // for valid/invalid state
29312
29482
  const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
29313
29483
  const isValid = score && score.type !== "invalid";
29314
29484
  const isInvalid = score && score.type === "invalid";
@@ -30345,6 +30515,7 @@ function numSteps$1(range, step) {
30345
30515
  class Graph extends React.Component {
30346
30516
  constructor(...args) {
30347
30517
  super(...args);
30518
+ this.graphieDivRef = /*#__PURE__*/React.createRef();
30348
30519
  this.protractor = void 0;
30349
30520
  this.ruler = void 0;
30350
30521
  this._graphie = void 0;
@@ -30388,9 +30559,7 @@ class Graph extends React.Component {
30388
30559
  if (this._hasSetupGraphieThisUpdate) {
30389
30560
  return;
30390
30561
  }
30391
-
30392
- // eslint-disable-next-line react/no-string-refs
30393
- const graphieDiv = ReactDOM__default.findDOMNode(this.refs.graphieDiv);
30562
+ const graphieDiv = this.graphieDivRef.current;
30394
30563
  // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'empty' does not exist on type 'JQueryStatic'.
30395
30564
  $(graphieDiv).empty();
30396
30565
 
@@ -30598,7 +30767,7 @@ class Graph extends React.Component {
30598
30767
  onClick: this.onClick
30599
30768
  }, image, /*#__PURE__*/React.createElement("div", {
30600
30769
  className: "graphie",
30601
- ref: "graphieDiv"
30770
+ ref: this.graphieDivRef
30602
30771
  }));
30603
30772
  }
30604
30773
  }
@@ -31686,6 +31855,7 @@ class InfoTip extends React.Component {
31686
31855
  class MultiButtonGroup extends React.Component {
31687
31856
  constructor(...args) {
31688
31857
  super(...args);
31858
+ this.buttonContainerRef = /*#__PURE__*/React.createRef();
31689
31859
  this.toggleSelect = newValue => {
31690
31860
  const values = (this.props.values || []).slice(0);
31691
31861
  const allowEmpty = this.props.allowEmpty;
@@ -31702,9 +31872,8 @@ class MultiButtonGroup extends React.Component {
31702
31872
  };
31703
31873
  }
31704
31874
  focus() {
31705
- var _ReactDOM$findDOMNode;
31706
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element | Text'.
31707
- (_ReactDOM$findDOMNode = ReactDOM.findDOMNode(this)) == null || _ReactDOM$findDOMNode.focus();
31875
+ var _this$buttonContainer;
31876
+ (_this$buttonContainer = this.buttonContainerRef.current) == null || _this$buttonContainer.focus();
31708
31877
  return true;
31709
31878
  }
31710
31879
  render() {
@@ -31725,7 +31894,8 @@ class MultiButtonGroup extends React.Component {
31725
31894
  display: "inline-block"
31726
31895
  };
31727
31896
  return /*#__PURE__*/React.createElement("div", {
31728
- style: outerStyle
31897
+ style: outerStyle,
31898
+ ref: this.buttonContainerRef
31729
31899
  }, buttons);
31730
31900
  }
31731
31901
  }
@@ -44843,7 +45013,7 @@ const init = function init() {
44843
45013
 
44844
45014
  // This file is processed by a Rollup plugin (replace) to inject the production
44845
45015
  const libName = "@khanacademy/perseus";
44846
- const libVersion = "43.1.0";
45016
+ const libVersion = "45.0.0";
44847
45017
  addLibraryVersionToPerseusDebug(libName, libVersion);
44848
45018
 
44849
45019
  const apiVersion = {
@@ -45467,6 +45637,13 @@ class ServerItemRenderer extends React.Component {
45467
45637
  this.props.onRendered(true);
45468
45638
  }
45469
45639
  }
45640
+ if (this.props.score && this.props.score !== prevProps.score) {
45641
+ const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
45642
+ this.setState({
45643
+ questionCompleted: this.props.score.correct,
45644
+ questionHighlightedWidgets: emptyQuestionAreaWidgets
45645
+ });
45646
+ }
45470
45647
  }
45471
45648
  componentWillUnmount() {
45472
45649
  if (this.blurTimeoutID != null) {
@@ -45584,18 +45761,37 @@ class ServerItemRenderer extends React.Component {
45584
45761
  return this.questionRenderer.getPromptJSON();
45585
45762
  }
45586
45763
 
45764
+ /**
45765
+ * Returns an array of the widget `.getUserInput()` results
45766
+ *
45767
+ * TODO: can we remove this? Seems to be just for backwards
45768
+ * compatibility with old Perseus Chrome logging
45769
+ * @deprecated use getUserInput
45770
+ */
45771
+ getUserInputLegacy() {
45772
+ return this.questionRenderer.getUserInput();
45773
+ }
45774
+
45775
+ /**
45776
+ * Returns an object of the widget `.getUserInput()` results
45777
+ */
45778
+ getUserInput() {
45779
+ return this.questionRenderer.getUserInputMap();
45780
+ }
45781
+
45587
45782
  /**
45588
45783
  * Grades the item.
45784
+ *
45785
+ * @deprecated use scorePerseusItem
45589
45786
  */
45590
45787
  scoreInput() {
45591
- const guessAndScore = this.questionRenderer.guessAndScore();
45592
- const guess = guessAndScore[0];
45593
- const score = guessAndScore[1];
45788
+ const guess = this.getUserInput();
45789
+ const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
45594
45790
 
45595
45791
  // Continue to include an empty guess for the now defunct answer area.
45596
45792
  // TODO(alex): Check whether we rely on the format here for
45597
45793
  // analyzing ProblemLogs. If not, remove this layer.
45598
- const maxCompatGuess = [guess, []];
45794
+ const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
45599
45795
  const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
45600
45796
  const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
45601
45797
  this.setState({
@@ -47443,140 +47639,6 @@ const registerAllWidgetsForTesting = () => {
47443
47639
  replaceDeprecatedWidgets();
47444
47640
  };
47445
47641
 
47446
- // These are all the current widgets that require user-input and are supported for automatic scoring
47447
- const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
47448
-
47449
- /**
47450
- * Regex for widget placeholders in a string.
47451
- *
47452
- * First capture group is the widget ID (ex. 'radio 1')
47453
- * Second capture group is the widget type (ex. "radio)
47454
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
47455
- */
47456
- function getWidgetRegex() {
47457
- return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
47458
- }
47459
-
47460
- /**
47461
- * Add a widget placeholder using the widget ID.
47462
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
47463
- *
47464
- * @param {string} id
47465
- * @returns {string}
47466
- */
47467
- function addWidget(id) {
47468
- return `[[☃ ${id}]]`;
47469
- }
47470
-
47471
- /**
47472
- * Get a widget type by a widget's ID
47473
- * (please don't derive type from ID)
47474
- *
47475
- * @param {string} widgetId the ID of the widget
47476
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47477
- * @returns {string} the widget type (ie "radio")
47478
- */
47479
- function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
47480
- var _widget$type;
47481
- const widget = widgetMap[WidgetId];
47482
- return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
47483
- }
47484
-
47485
- /**
47486
- * Does the content have a specific type of widget?
47487
- *
47488
- * @param {string} type the type of the widget in question (ie "radio")
47489
- * @param {string} content the string to search through
47490
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47491
- * @returns {boolean} if the content includes the widget type
47492
- */
47493
- function contentHasWidgetType(type, content, widgetMap) {
47494
- return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
47495
- }
47496
-
47497
- /**
47498
- * Extract all widget IDs, which includes the widget type and instance number.
47499
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
47500
- *
47501
- * Content should contain Perseus widget placeholders,
47502
- * which look like: '[[☃ radio 1]]'.
47503
- *
47504
- * @param {string} content
47505
- * @returns {ReadonlyArray<string>} widgetIds
47506
- */
47507
- function getWidgetIdsFromContent(content) {
47508
- const widgets = [];
47509
- const localWidgetRegex = getWidgetRegex();
47510
- let match = localWidgetRegex.exec(content);
47511
- while (match !== null) {
47512
- widgets.push(match[1]);
47513
- match = localWidgetRegex.exec(content);
47514
- }
47515
- return widgets;
47516
- }
47517
-
47518
- /**
47519
- * Get a list of widget IDs from content,
47520
- * but only for specific widget types
47521
- *
47522
- * @param {string} type the type of widget (ie "radio")
47523
- * @param {string} content the string to parse
47524
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47525
- * @returns {ReadonlyArray<string>} the widget type (ie "radio")
47526
- */
47527
- function getWidgetIdsFromContentByType(type, content, widgetMap) {
47528
- const rv = [];
47529
- const widgetIdsInContent = getWidgetIdsFromContent(content);
47530
- widgetIdsInContent.forEach(widgetId => {
47531
- const widget = widgetMap[widgetId];
47532
- if ((widget == null ? void 0 : widget.type) === type) {
47533
- rv.push(widgetId);
47534
- }
47535
- });
47536
- return rv;
47537
- }
47538
-
47539
- /**
47540
- * Pull the widget map out of ItemData
47541
- *
47542
- * @param {PerseusItem} PerseusItem containing a widgetMap
47543
- * @returns {WidgetMap} the widget map in the PerseusItem
47544
- */
47545
- function getWidgetsMapFromItemData(itemData) {
47546
- return itemData.question.widgets;
47547
- }
47548
-
47549
- /**
47550
- * Get the widget information from a WidgetMap
47551
- *
47552
- * @param {string} widgetId the ID of the widget
47553
- * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
47554
- */
47555
- function getWidgetFromWidgetMap(widgetId, widgetMap) {
47556
- var _widgetMap$widgetId;
47557
- return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
47558
- }
47559
-
47560
- /**
47561
- * Get select widgets from a widget map.
47562
- * Useful for multi-items that needs to split
47563
- * one PerseusItem into several
47564
- *
47565
- * @param {ReadonlyArray<string>} widgetIds to extract
47566
- * @param {PerseusWidgetsMap} widgetMap to extract from
47567
- * @return {PerseusWidgetsMap} a new widget map with requested widgets
47568
- */
47569
- function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
47570
- const widgets = {};
47571
- widgetIds.forEach(widgetId => {
47572
- const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
47573
- if (widget) {
47574
- widgets[widgetId] = widget;
47575
- }
47576
- });
47577
- return widgets;
47578
- }
47579
-
47580
47642
  /**
47581
47643
  * This function extracts the answers from the widgets.
47582
47644
  *
@@ -48513,6 +48575,8 @@ const parseDefinitionWidget = parseWidget(constant("definition"), object({
48513
48575
 
48514
48576
  const parseDropdownWidget = parseWidget(constant("dropdown"), object({
48515
48577
  placeholder: string,
48578
+ ariaLabel: optional(string),
48579
+ visibleLabel: optional(string),
48516
48580
  static: defaulted(boolean, () => false),
48517
48581
  choices: array(object({
48518
48582
  content: string,
@@ -49631,5 +49695,5 @@ const GrapherUtil = {
49631
49695
  typeToButton
49632
49696
  };
49633
49697
 
49634
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
49698
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
49635
49699
  //# sourceMappingURL=index.js.map