@khanacademy/perseus 53.1.0 → 54.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 (53) hide show
  1. package/dist/components/highlighting/types.d.ts +2 -1
  2. package/dist/components.d.ts +0 -1
  3. package/dist/es/index.js +621 -1948
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/es/strings.js +61 -10
  6. package/dist/es/strings.js.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +698 -775
  9. package/dist/index.js.map +1 -1
  10. package/dist/interactive2/movable.d.ts +2 -1
  11. package/dist/strings.d.ts +57 -6
  12. package/dist/strings.js +139 -67
  13. package/dist/strings.js.map +1 -1
  14. package/dist/types.d.ts +6 -13
  15. package/dist/util/graphie.d.ts +1 -1
  16. package/dist/util.d.ts +0 -5
  17. package/dist/widget-ai-utils/radio/radio-ai-utils.d.ts +2 -1
  18. package/dist/widget-type-utils.d.ts +1 -0
  19. package/dist/widgets/categorizer/categorizer.d.ts +1 -2
  20. package/dist/widgets/cs-program/cs-program.d.ts +0 -2
  21. package/dist/widgets/dropdown/dropdown.d.ts +0 -2
  22. package/dist/widgets/expression/expression.d.ts +3 -6
  23. package/dist/widgets/grapher/grapher.d.ts +0 -2
  24. package/dist/widgets/iframe/iframe.d.ts +1 -2
  25. package/dist/widgets/interactive-graph.d.ts +2 -87
  26. package/dist/widgets/interactive-graphs/backgrounds/axis-labels.d.ts +4 -1
  27. package/dist/widgets/interactive-graphs/graphs/angle.d.ts +2 -1
  28. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +6 -2
  29. package/dist/widgets/interactive-graphs/graphs/components/hairlines.d.ts +7 -0
  30. package/dist/widgets/interactive-graphs/graphs/components/movable-point-view.d.ts +1 -0
  31. package/dist/widgets/interactive-graphs/graphs/components/svg-line.d.ts +2 -1
  32. package/dist/widgets/interactive-graphs/graphs/linear-system.d.ts +2 -2
  33. package/dist/widgets/interactive-graphs/graphs/linear.d.ts +1 -1
  34. package/dist/widgets/interactive-graphs/graphs/point.d.ts +3 -2
  35. package/dist/widgets/interactive-graphs/graphs/ray.d.ts +1 -1
  36. package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +1 -0
  37. package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -2
  38. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +2 -1
  39. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +0 -3
  40. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -2
  41. package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +3 -3
  42. package/dist/widgets/label-image/label-image.d.ts +1 -3
  43. package/dist/widgets/matrix/matrix.d.ts +1 -2
  44. package/dist/widgets/number-line/number-line.d.ts +0 -2
  45. package/dist/widgets/numeric-input/numeric-input.d.ts +0 -2
  46. package/dist/widgets/orderer/orderer.d.ts +0 -2
  47. package/dist/widgets/plotter/plotter.d.ts +0 -2
  48. package/dist/widgets/radio/radio.d.ts +1 -2
  49. package/dist/widgets/sorter/sorter.d.ts +1 -2
  50. package/dist/widgets/table/table.d.ts +0 -2
  51. package/dist/widgets.d.ts +1 -2
  52. package/package.json +10 -10
  53. package/dist/components/graph.d.ts +0 -61
package/dist/es/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as KAS from '@khanacademy/kas';
2
2
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
- import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, getNumericInputPublicWidgetOptions, passageRefLogic, radioLogic, getRadioPublicWidgetOptions, getCategorizerPublicWidgetOptions, getCSProgramPublicWidgetOptions, getDropdownPublicWidgetOptions, GrapherUtil as GrapherUtil$1, getGrapherPublicWidgetOptions, getIFramePublicWidgetOptions, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getInteractiveGraphPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatrixPublicWidgetOptions, getMatrixSize, measurerLogic, getNumberLinePublicWidgetOptions, getOrdererPublicWidgetOptions, getPlotterPublicWidgetOptions, getSorterPublicWidgetOptions, getTablePublicWidgetOptions, addLibraryVersionToPerseusDebug, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
3
+ import { PerseusError, Errors, expressionLogic, getDecimalSeparator, approximateDeepEqual, pluck, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, passageRefLogic, radioLogic, shuffle, random, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, shuffleMatcher, getMatrixSize, measurerLogic, addLibraryVersionToPerseusDebug, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
4
4
  export { isFailure, isSuccess, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem } from '@khanacademy/perseus-core';
5
5
  import * as PerseusLinter from '@khanacademy/perseus-linter';
6
6
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
@@ -33,7 +33,6 @@ import Banner from '@khanacademy/wonder-blocks-banner';
33
33
  import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-blocks-dropdown';
34
34
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
35
35
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
36
- import debounce$2 from 'lodash.debounce';
37
36
  import { vec, Coordinates, Ellipse, Plot as Plot$2, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Polyline, Mafs } from 'mafs';
38
37
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
39
38
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
@@ -240,6 +239,10 @@ const mockStrings = {
240
239
  simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
241
240
  selectAnAnswer: "Select an answer",
242
241
  // The following strings are used for interactive graph SR descriptions.
242
+ srGraphInstructions: "Use the Tab key to move through the interactive elements in the graph. When an interactive element has focus, use the Arrow keys to move it.",
243
+ srUnlimitedGraphInstructions: "Press Shift + Enter to interact with the graph. Use the Tab key to move through the interactive elements in the graph and access the graph Action Bar. When an interactive element has focus, use the Arrow keys to move it or use the Delete key to remove it from the graph. Use the buttons in the Action Bar to add or adjust elements within the graph.",
244
+ xAxis: "X-axis",
245
+ yAxis: "Y-axis",
243
246
  graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
244
247
  addPoint: "Add Point",
245
248
  removePoint: "Remove Point",
@@ -304,17 +307,23 @@ const mockStrings = {
304
307
  point2X,
305
308
  point2Y
306
309
  }) => `Line from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`,
307
- srAngleSideAtCoordinates: ({
308
- point,
309
- side,
310
+ srAngleStartingSide: ({
310
311
  x,
311
312
  y
312
- }) => `Point ${point}, ${side} at ${x} comma ${y}`,
313
- srAngleVertexAtCoordinatesWithAngleMeasure: ({
313
+ }) => `Point 3, starting side at ${x} comma ${y}.`,
314
+ srAngleEndingSide: ({
315
+ x,
316
+ y
317
+ }) => `Point 2, ending side at ${x} comma ${y}.`,
318
+ srAngleVertex: ({
319
+ x,
320
+ y
321
+ }) => `Point 1, vertex at ${x} comma ${y}.`,
322
+ srAngleVertexWithAngleMeasure: ({
314
323
  x,
315
324
  y,
316
325
  angleMeasure
317
- }) => `Point 2, vertex at ${x} comma ${y}. Angle ${angleMeasure} degrees`,
326
+ }) => `Point 1, vertex at ${x} comma ${y}. Angle ${angleMeasure} degrees.`,
318
327
  srAngleGraphAriaLabel: "An angle on a coordinate plane.",
319
328
  srAngleGraphAriaDescription: ({
320
329
  angleMeasure,
@@ -325,6 +334,14 @@ const mockStrings = {
325
334
  endingSideX,
326
335
  endingSideY
327
336
  }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,
337
+ srAngleInteractiveElements: ({
338
+ vertexX,
339
+ vertexY,
340
+ startingSideX,
341
+ startingSideY,
342
+ endingSideX,
343
+ endingSideY
344
+ }) => `An angle formed by 3 points. The vertex is at ${vertexX} comma ${vertexY}. The starting side point is at ${startingSideX} comma ${startingSideY}. The ending side point is at ${endingSideX} comma ${endingSideY}.`,
328
345
  srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
329
346
  srMultipleSegmentGraphAriaLabel: ({
330
347
  countOfSegments
@@ -462,7 +479,28 @@ const mockStrings = {
462
479
  pointNum,
463
480
  length
464
481
  }) => `A line segment, length approximately equal to ${length} units, connects to point ${pointNum}.`,
465
- srUnlimitedPolygonEmpty: "An empty coordinate plane."
482
+ srUnlimitedPolygonEmpty: "An empty coordinate plane.",
483
+ srSinusoidGraph: "A sinusoid function on a coordinate plane.",
484
+ srSinusoidRootPoint: ({
485
+ x,
486
+ y
487
+ }) => `Midline intersection at ${x} comma ${y}.`,
488
+ srSinusoidPeakPoint: ({
489
+ x,
490
+ y
491
+ }) => `Extremum point at ${x} comma ${y}.`,
492
+ srSinusoidDescription: ({
493
+ minValue,
494
+ maxValue,
495
+ cycleStart,
496
+ cycleEnd
497
+ }) => `The graph shows a wave with a minimum value of ${minValue} and a maximum value of ${maxValue}. The wave completes a full cycle from ${cycleStart} to ${cycleEnd}.`,
498
+ srSinusoidInteractiveElements: ({
499
+ point1X,
500
+ point1Y,
501
+ point2X,
502
+ point2Y
503
+ }) => `Sinusoid graph with midline intersection point at ${point1X} comma ${point1Y} and extremum point at ${point2X} comma ${point2Y}.`
466
504
  // The above strings are used for interactive graph SR descriptions.
467
505
  };
468
506
 
@@ -1394,6 +1432,7 @@ const ApiOptions = {
1394
1432
  },
1395
1433
  baseElements: {
1396
1434
  Link: props => {
1435
+ // eslint-disable-next-line jsx-a11y/anchor-has-content -- TODO(LEMS-2871): Address a11y error
1397
1436
  return /*#__PURE__*/React.createElement("a", props);
1398
1437
  }
1399
1438
  },
@@ -1797,7 +1836,6 @@ var Expression$1 = {
1797
1836
  propUpgrades: expressionLogic.widgetOptionsUpgrades,
1798
1837
  // For use by the editor
1799
1838
  isLintable: true,
1800
- getPublicWidgetOptions: getExpressionPublicWidgetOptions,
1801
1839
  // TODO(LEMS-2656): remove TS suppression
1802
1840
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
1803
1841
  getOneCorrectAnswerFromRubric(rubric) {
@@ -1901,54 +1939,6 @@ const rWidgetRule = /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;
1901
1939
  const rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
1902
1940
  const rWidgetParts = new RegExp(rWidgetRule.source + "$");
1903
1941
  const snowman = "\u2603";
1904
- const seededRNG$1 = function seededRNG(seed) {
1905
- let randomSeed = seed;
1906
- return function () {
1907
- // Robert Jenkins' 32 bit integer hash function.
1908
- let seed = randomSeed;
1909
- seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
1910
- seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
1911
- seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
1912
- seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
1913
- seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
1914
- seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
1915
- return (randomSeed = seed & 0xfffffff) / 0x10000000;
1916
- };
1917
- };
1918
-
1919
- // Shuffle an array using a given random seed or function.
1920
- // If `ensurePermuted` is true, the input and ouput are guaranteed to be
1921
- // distinct permutations.
1922
- function shuffle$3(array, randomSeed, ensurePermuted = false) {
1923
- // Always return a copy of the input array
1924
- const shuffled = _.clone(array);
1925
-
1926
- // Handle edge cases (input array is empty or uniform)
1927
- if (!shuffled.length || _.all(shuffled, function (value) {
1928
- return _.isEqual(value, shuffled[0]);
1929
- })) {
1930
- return shuffled;
1931
- }
1932
- let random;
1933
- if (typeof randomSeed === "function") {
1934
- random = randomSeed;
1935
- } else {
1936
- random = seededRNG$1(randomSeed);
1937
- }
1938
- do {
1939
- // Fischer-Yates shuffle
1940
- for (let top = shuffled.length; top > 0; top--) {
1941
- const newEnd = Math.floor(random() * top);
1942
- const temp = shuffled[newEnd];
1943
-
1944
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
1945
- shuffled[newEnd] = shuffled[top - 1];
1946
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
1947
- shuffled[top - 1] = temp;
1948
- }
1949
- } while (ensurePermuted && _.isEqual(array, shuffled));
1950
- return shuffled;
1951
- }
1952
1942
 
1953
1943
  /**
1954
1944
  * TODO(somewhatabstract, FEI-3463):
@@ -2381,7 +2371,6 @@ const textarea = {
2381
2371
  * to translate it but don't need the extra dollar signs.
2382
2372
  */
2383
2373
  const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
2384
- const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
2385
2374
  const Util = {
2386
2375
  inputPathsEqual,
2387
2376
  nestedMap: nestedMap$2,
@@ -2389,8 +2378,6 @@ const Util = {
2389
2378
  rTypeFromWidgetId,
2390
2379
  rWidgetParts,
2391
2380
  snowman,
2392
- seededRNG: seededRNG$1,
2393
- shuffle: shuffle$3,
2394
2381
  split,
2395
2382
  firstNumericalParse: firstNumericalParse$1,
2396
2383
  stringArrayOfSize: stringArrayOfSize$1,
@@ -2418,8 +2405,7 @@ const Util = {
2418
2405
  getSvgUrl: getSvgUrl,
2419
2406
  getDataUrl: getDataUrl,
2420
2407
  textarea,
2421
- unescapeMathMode: unescapeMathMode$1,
2422
- random: random$1
2408
+ unescapeMathMode: unescapeMathMode$1
2423
2409
  };
2424
2410
 
2425
2411
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
@@ -15699,12 +15685,17 @@ class ImageLoader extends React.Component {
15699
15685
  };
15700
15686
  }
15701
15687
  const staticUrl = getDependencies().staticUrl;
15702
- return /*#__PURE__*/React.createElement("img", _extends({
15703
- tabIndex: 0,
15704
- src: staticUrl(src),
15705
- onKeyUp: onKeyUp,
15706
- onKeyDown: onKeyDown
15707
- }, imgProps));
15688
+ return (
15689
+ /*#__PURE__*/
15690
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
15691
+ React.createElement("img", _extends({
15692
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- TODO(LEMS-2871): Address a11y error
15693
+ tabIndex: 0,
15694
+ src: staticUrl(src),
15695
+ onKeyUp: onKeyUp,
15696
+ onKeyDown: onKeyDown
15697
+ }, imgProps))
15698
+ );
15708
15699
  };
15709
15700
  this.state = {
15710
15701
  status: props.src ? Status.LOADING : Status.PENDING
@@ -16389,16 +16380,20 @@ class Zoomable extends React.Component {
16389
16380
  opacity: opacity,
16390
16381
  WebkitTapHighlightColor: "transparent"
16391
16382
  }, transitionStyle);
16392
- return /*#__PURE__*/React.createElement("span", {
16393
- onClick: this.handleClick,
16394
- onClickCapture: this.handleClickIfZoomed,
16395
- onTouchCancelCapture: this.stopPropagationIfZoomed,
16396
- onTouchEndCapture: this.stopPropagationIfZoomed,
16397
- onTouchStartCapture: this.stopPropagationIfZoomed
16398
- // @ts-expect-error - TS2322 - Type '{ readonly transitionProperty: string; readonly transitionDuration: string; readonly transitionTimingFunction: string; readonly display: "block"; readonly width: "100%"; readonly height: number | ... 1 more ... | undefined; readonly transform: string; readonly transformOrigin: "0 0"; readonly opacity: 0 | 1; readonl...' is not assignable to type 'CSSProperties | undefined'.
16399
- ,
16400
- style: style
16401
- }, this.props.children);
16383
+ return (
16384
+ /*#__PURE__*/
16385
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
16386
+ React.createElement("span", {
16387
+ onClick: this.handleClick,
16388
+ onClickCapture: this.handleClickIfZoomed,
16389
+ onTouchCancelCapture: this.stopPropagationIfZoomed,
16390
+ onTouchEndCapture: this.stopPropagationIfZoomed,
16391
+ onTouchStartCapture: this.stopPropagationIfZoomed
16392
+ // @ts-expect-error - TS2322 - Type '{ readonly transitionProperty: string; readonly transitionDuration: string; readonly transitionTimingFunction: string; readonly display: "block"; readonly width: "100%"; readonly height: number | ... 1 more ... | undefined; readonly transform: string; readonly transformOrigin: "0 0"; readonly opacity: 0 | 1; readonl...' is not assignable to type 'CSSProperties | undefined'.
16393
+ ,
16394
+ style: style
16395
+ }, this.props.children)
16396
+ );
16402
16397
  }
16403
16398
  }
16404
16399
  Zoomable.defaultProps = {
@@ -17619,10 +17614,6 @@ const getWidgetExport = name => {
17619
17614
  var _widgets$name3;
17620
17615
  return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
17621
17616
  };
17622
- const getPublicWidgetOptionsFunction = name => {
17623
- var _widgets$name$getPubl, _widgets$name4;
17624
- return (_widgets$name$getPubl = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
17625
- };
17626
17617
  const getEditor = name => {
17627
17618
  return _.has(editors, name) ? editors[name] : null;
17628
17619
  };
@@ -17759,7 +17750,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
17759
17750
  replaceDeprecatedEditors: replaceDeprecatedEditors,
17760
17751
  getWidget: getWidget,
17761
17752
  getWidgetExport: getWidgetExport,
17762
- getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
17763
17753
  getEditor: getEditor,
17764
17754
  getTransform: getTransform,
17765
17755
  getVersion: getVersion,
@@ -17835,6 +17825,7 @@ class WidgetContainer extends React.Component {
17835
17825
  "perseus-widget__definition": this.props.type === "definition"
17836
17826
  });
17837
17827
  const type = this.props.type;
17828
+ const userAgent = navigator.userAgent;
17838
17829
  const WidgetType = getWidget(type);
17839
17830
  if (WidgetType == null) {
17840
17831
  // This is for the good of all people!!
@@ -17845,6 +17836,12 @@ class WidgetContainer extends React.Component {
17845
17836
  className: className
17846
17837
  });
17847
17838
  }
17839
+ let subType = "null";
17840
+ if (type === "interactive-graph") {
17841
+ var _props$graph$type, _props$graph;
17842
+ const props = this.state.widgetProps;
17843
+ subType = (_props$graph$type = (_props$graph = props.graph) == null ? void 0 : _props$graph.type) != null ? _props$graph$type : "null";
17844
+ }
17848
17845
  let alignment = this.state.widgetProps.alignment;
17849
17846
  if (alignment === "default") {
17850
17847
  alignment = CoreWidgetRegistry.getDefaultAlignment(type);
@@ -17898,12 +17895,26 @@ class WidgetContainer extends React.Component {
17898
17895
  widget_type: type,
17899
17896
  widget_id: this.props.id
17900
17897
  },
17901
- onError: () => {
17898
+ onError: error => {
17899
+ // TODO(LEMS-2826): Remove analytics event in LEMS-2826 in favor of ti below.
17902
17900
  analytics.onAnalyticsEvent({
17903
17901
  type: "perseus:widget-rendering-error",
17904
17902
  payload: {
17903
+ widgetSubType: subType,
17904
+ widgetType: type,
17905
+ widgetId: this.props.id,
17906
+ message: error.message,
17907
+ userAgent: userAgent
17908
+ }
17909
+ });
17910
+ analytics.onAnalyticsEvent({
17911
+ type: "perseus:widget-rendering-error:ti",
17912
+ payload: {
17913
+ widgetSubType: subType,
17905
17914
  widgetType: type,
17906
- widgetId: this.props.id
17915
+ widgetId: this.props.id,
17916
+ message: error.message,
17917
+ userAgent: userAgent
17907
17918
  }
17908
17919
  });
17909
17920
  }
@@ -20327,7 +20338,6 @@ var NumericInput$1 = {
20327
20338
  widget: NumericInput,
20328
20339
  transform: propsTransform,
20329
20340
  isLintable: true,
20330
- getPublicWidgetOptions: getNumericInputPublicWidgetOptions,
20331
20341
  // TODO(LEMS-2656): remove TS suppression
20332
20342
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
20333
20343
  getOneCorrectAnswerFromRubric(rubric) {
@@ -21132,7 +21142,10 @@ class HighlightTooltip extends React.PureComponent {
21132
21142
  // using div instead of TooltipContent because
21133
21143
  // TooltipContent wouldn't let me overwrite
21134
21144
  // user-select and onClick
21135
- const content = /*#__PURE__*/React.createElement("div", {
21145
+ const content =
21146
+ /*#__PURE__*/
21147
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
21148
+ React.createElement("div", {
21136
21149
  className: css(styles$x.tooltipLabel),
21137
21150
  onClick: this.props.onClick
21138
21151
  }, this.props.label);
@@ -21143,14 +21156,18 @@ class HighlightTooltip extends React.PureComponent {
21143
21156
  top: `calc(${Math.round(focusRect.top)}px + 0.95em)`,
21144
21157
  height: 0
21145
21158
  };
21146
- return /*#__PURE__*/React.createElement("div", {
21147
- style: style,
21148
- onMouseEnter: this.props.onMouseEnter,
21149
- onMouseLeave: this.props.onMouseLeave
21150
- }, /*#__PURE__*/React.createElement(Tooltip$1, {
21151
- content: content,
21152
- opened: true
21153
- }, /*#__PURE__*/React.createElement("div", null)));
21159
+ return (
21160
+ /*#__PURE__*/
21161
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
21162
+ React.createElement("div", {
21163
+ style: style,
21164
+ onMouseEnter: this.props.onMouseEnter,
21165
+ onMouseLeave: this.props.onMouseLeave
21166
+ }, /*#__PURE__*/React.createElement(Tooltip$1, {
21167
+ content: content,
21168
+ opened: true
21169
+ }, /*#__PURE__*/React.createElement("div", null)))
21170
+ );
21154
21171
  }
21155
21172
  }
21156
21173
  const styles$x = StyleSheet.create({
@@ -23853,15 +23870,19 @@ const BaseRadio = function BaseRadio({
23853
23870
  // content somehow? Would changing our choice of key
23854
23871
  // somehow break something happening inside a choice's
23855
23872
  // child Renderers, by changing when we mount/unmount?
23856
- return /*#__PURE__*/React.createElement("li", {
23857
- key: i,
23858
- ref: e => listElem = e,
23859
- className: className,
23860
- onClick: clickHandler,
23861
- onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
23862
- }, /*#__PURE__*/React.createElement(Element, _extends({}, elementProps, {
23863
- ref: ref
23864
- })));
23873
+ return (
23874
+ /*#__PURE__*/
23875
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
23876
+ React.createElement("li", {
23877
+ key: i,
23878
+ ref: e => listElem = e,
23879
+ className: className,
23880
+ onClick: clickHandler,
23881
+ onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
23882
+ }, /*#__PURE__*/React.createElement(Element, _extends({}, elementProps, {
23883
+ ref: ref
23884
+ })))
23885
+ );
23865
23886
  })));
23866
23887
 
23867
23888
  // Allow for horizontal scrolling if content is too wide, which may be
@@ -24296,11 +24317,6 @@ Radio$1.defaultProps = {
24296
24317
  showSolutions: "none"
24297
24318
  };
24298
24319
 
24299
- const {
24300
- shuffle: shuffle$2,
24301
- random
24302
- } = Util;
24303
-
24304
24320
  // Transforms the choices for display.
24305
24321
  const _choiceTransform = (widgetOptions, strings, problemNum) => {
24306
24322
  const _maybeRandomize = function _maybeRandomize(array) {
@@ -24311,7 +24327,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24311
24327
  // or if rendered within an article. This results in a predictable
24312
24328
  // shuffle order. To avoid this we use a random seed when `problemNum`
24313
24329
  // is `undefined`.
24314
- return widgetOptions.randomize ? shuffle$2(array, randomSeed != null ? randomSeed : 0) : array;
24330
+ return widgetOptions.randomize ? shuffle(array, randomSeed != null ? randomSeed : 0) : array;
24315
24331
  };
24316
24332
  const _addNoneOfAbove = function _addNoneOfAbove(choices) {
24317
24333
  let noneOfTheAbove = null;
@@ -24391,8 +24407,7 @@ var Radio = {
24391
24407
  staticTransform: transform,
24392
24408
  version: radioLogic.version,
24393
24409
  propUpgrades: radioLogic.widgetOptionsUpgrades,
24394
- isLintable: true,
24395
- getPublicWidgetOptions: getRadioPublicWidgetOptions
24410
+ isLintable: true
24396
24411
  };
24397
24412
 
24398
24413
  /* eslint-disable import/no-named-as-default */
@@ -24542,8 +24557,7 @@ class Categorizer extends React.Component {
24542
24557
  const isMobile = this.props.apiOptions.isMobile;
24543
24558
  let indexedItems = this.props.items.map((item, n) => [item, n]);
24544
24559
  if (this.props.randomizeItems) {
24545
- // @ts-expect-error - TS4104 - The type 'readonly (string | number)[][]' is 'readonly' and cannot be assigned to the mutable type '(string | number)[][]'. | TS2345 - Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | RNG'.
24546
- indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
24560
+ indexedItems = shuffle(indexedItems, this.props.problemNum);
24547
24561
  }
24548
24562
  const table = /*#__PURE__*/React.createElement("table", {
24549
24563
  className: "categorizer-table " + css(styles$n.mobileTable)
@@ -24569,9 +24583,7 @@ class Categorizer extends React.Component {
24569
24583
  const uniqueId = self.state.uniqueId + "_" + itemNum;
24570
24584
  return /*#__PURE__*/React.createElement("tr", {
24571
24585
  key: itemNum
24572
- }, /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement(Renderer
24573
- // @ts-expect-error - TS2322 - Type 'string | number' is not assignable to type 'string | undefined'.
24574
- , {
24586
+ }, /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement(Renderer, {
24575
24587
  content: item,
24576
24588
  linterContext: this.props.linterContext,
24577
24589
  strings: this.context.strings
@@ -24678,8 +24690,7 @@ var Categorizer$1 = {
24678
24690
  staticTransform: editorProps => {
24679
24691
  return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
24680
24692
  },
24681
- isLintable: true,
24682
- getPublicWidgetOptions: getCategorizerPublicWidgetOptions
24693
+ isLintable: true
24683
24694
  };
24684
24695
 
24685
24696
  /**
@@ -24885,8 +24896,7 @@ var CSProgram$1 = {
24885
24896
  name: "cs-program",
24886
24897
  displayName: "CS Program",
24887
24898
  widget: CSProgram,
24888
- hidden: true,
24889
- getPublicWidgetOptions: getCSProgramPublicWidgetOptions
24899
+ hidden: true
24890
24900
  };
24891
24901
 
24892
24902
  const getPromptJSON$l = renderProps => {
@@ -25104,8 +25114,7 @@ var Dropdown$1 = {
25104
25114
  displayName: "Drop down",
25105
25115
  accessible: true,
25106
25116
  widget: Dropdown,
25107
- transform: optionsTransform,
25108
- getPublicWidgetOptions: getDropdownPublicWidgetOptions
25117
+ transform: optionsTransform
25109
25118
  };
25110
25119
 
25111
25120
  const getPromptJSON$j = renderProps => {
@@ -26579,8 +26588,7 @@ var Grapher$1 = {
26579
26588
  hidden: true,
26580
26589
  widget: Grapher,
26581
26590
  transform: propTransform$2,
26582
- staticTransform: staticTransform$4,
26583
- getPublicWidgetOptions: getGrapherPublicWidgetOptions
26591
+ staticTransform: staticTransform$4
26584
26592
  };
26585
26593
 
26586
26594
  const getPromptJSON$f = rendererJSON => {
@@ -26865,12 +26873,16 @@ class Iframe extends React.Component {
26865
26873
  // that we need. This makes it a bit safer in case some content
26866
26874
  // creator "went wild".
26867
26875
  // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
26868
- return /*#__PURE__*/React.createElement("iframe", {
26869
- sandbox: sandboxProperties,
26870
- style: style,
26871
- src: url,
26872
- allowFullScreen: this.props.allowFullScreen
26873
- });
26876
+ return (
26877
+ /*#__PURE__*/
26878
+ // eslint-disable-next-line jsx-a11y/iframe-has-title -- TODO(LEMS-2871): Address a11y error
26879
+ React.createElement("iframe", {
26880
+ sandbox: sandboxProperties,
26881
+ style: style,
26882
+ src: url,
26883
+ allowFullScreen: this.props.allowFullScreen
26884
+ })
26885
+ );
26874
26886
  }
26875
26887
  }
26876
26888
  Iframe.defaultProps = {
@@ -26885,8 +26897,7 @@ var Iframe$1 = {
26885
26897
  displayName: "Iframe (deprecated)",
26886
26898
  widget: Iframe,
26887
26899
  // Let's not expose it to all content creators yet
26888
- hidden: true,
26889
- getPublicWidgetOptions: getIFramePublicWidgetOptions
26900
+ hidden: true
26890
26901
  };
26891
26902
 
26892
26903
  const getPromptJSON$d = renderProps => {
@@ -26903,7 +26914,7 @@ const getPromptJSON$d = renderProps => {
26903
26914
 
26904
26915
  const defaultBoxSize = 400;
26905
26916
  const defaultRange = [0, 10];
26906
- const defaultBackgroundImage$2 = {
26917
+ const defaultBackgroundImage$1 = {
26907
26918
  url: null,
26908
26919
  width: 0,
26909
26920
  height: 0
@@ -27060,7 +27071,7 @@ ImageWidget.defaultProps = {
27060
27071
  title: "",
27061
27072
  range: [defaultRange, defaultRange],
27062
27073
  box: [defaultBoxSize, defaultBoxSize],
27063
- backgroundImage: defaultBackgroundImage$2,
27074
+ backgroundImage: defaultBackgroundImage$1,
27064
27075
  labels: [],
27065
27076
  alt: "",
27066
27077
  caption: "",
@@ -27677,307 +27688,6 @@ var Interactive = {
27677
27688
  hidden: true
27678
27689
  };
27679
27690
 
27680
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
27681
- const defaultBackgroundImage$1 = {
27682
- url: null
27683
- };
27684
-
27685
- /* Style objects */
27686
- const defaultInstructionsStyle = {
27687
- fontStyle: "italic",
27688
- fontWeight: "bold",
27689
- fontSize: "32px",
27690
- width: "100%",
27691
- height: "100%",
27692
- textAlign: "center",
27693
- backgroundColor: "white",
27694
- position: "absolute",
27695
- zIndex: 1,
27696
- transition: "opacity .25s ease-in-out"
27697
- };
27698
- const instructionsTextStyle = {
27699
- position: "relative",
27700
- top: "25%"
27701
- };
27702
- function numSteps$1(range, step) {
27703
- return Math.floor((range[1] - range[0]) / step);
27704
- }
27705
- class Graph extends React.Component {
27706
- constructor(...args) {
27707
- super(...args);
27708
- this.graphieDivRef = /*#__PURE__*/React.createRef();
27709
- this.protractor = void 0;
27710
- this.ruler = void 0;
27711
- this._graphie = void 0;
27712
- // @ts-expect-error - TS2564 - Property '_hasSetupGraphieThisUpdate' has no initializer and is not definitely assigned in the constructor.
27713
- this._hasSetupGraphieThisUpdate = void 0;
27714
- // @ts-expect-error - TS2564 - Property '_shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
27715
- this._shouldSetupGraphie = void 0;
27716
- /* Reset the graphie canvas to its initial state
27717
- *
27718
- * Use when re-rendering the parent component and you need a blank
27719
- * graphie.
27720
- */
27721
- this.reset = () => {
27722
- this._setupGraphie(false);
27723
- };
27724
- this.graphie = () => {
27725
- return this._graphie;
27726
- };
27727
- this.pointsFromNormalized = (coordsList, noSnap) => {
27728
- const self = this;
27729
- return _.map(coordsList, function (coords) {
27730
- return _.map(coords, function (coord, i) {
27731
- const range = self.props.range[i];
27732
- if (noSnap) {
27733
- return range[0] + (range[1] - range[0]) * coord;
27734
- }
27735
- const step = self.props.step[i];
27736
- const nSteps = numSteps$1(range, step);
27737
- const tick = Math.round(coord * nSteps);
27738
- return range[0] + step * tick;
27739
- });
27740
- });
27741
- };
27742
- this._setupGraphie = initialMount => {
27743
- // Only setupGraphie once per componentDidUpdate().
27744
- // This prevents this component from rendering graphie
27745
- // and then immediately re-render graphie because its
27746
- // parent component asked it to. This will happen when
27747
- // props on the parent and props on this component both
27748
- // require graphie to be re-rendered.
27749
- if (this._hasSetupGraphieThisUpdate) {
27750
- return;
27751
- }
27752
- const graphieDiv = this.graphieDivRef.current;
27753
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'empty' does not exist on type 'JQueryStatic'.
27754
- $(graphieDiv).empty();
27755
-
27756
- // Content creators may need to explicitly add the dollar signs so the
27757
- // strings are picked up by our translation tools. However, these math
27758
- // annotations are redundant because we already render all graph labels
27759
- // in math mode. For example, a label value of `$\text{Dollars}$` will
27760
- // be translatable, but we only want to pass the string
27761
- // `\text{Dollars}` to the Graph widget.
27762
- const labels = this.props.labels.map(label => Util.unescapeMathMode(label));
27763
- const range = this.props.range;
27764
- // @ts-expect-error - TS2345: Argument of type 'Element | Text | null' is not assignable to parameter of type 'HTMLElement'.
27765
- const graphie = this._graphie = GraphUtils.createGraphie(graphieDiv);
27766
- const gridConfig = this._getGridConfig();
27767
- // @ts-expect-error - TS2339: Property 'snap' does not exist on type 'Graphie'.
27768
- graphie.snap = this.props.snapStep;
27769
- if (this.props.markings === "graph") {
27770
- graphie.graphInit({
27771
- range: range,
27772
- scale: vector$3.map(gridConfig, g => g.scale),
27773
- axisArrows: "<->",
27774
- labelFormat: function (s) {
27775
- return "\\small{" + s + "}";
27776
- },
27777
- gridStep: this.props.gridStep,
27778
- tickStep: vector$3.map(gridConfig, g => g.tickStep),
27779
- labelStep: 1,
27780
- unityLabels: vector$3.map(gridConfig, g => g.unityLabel),
27781
- isMobile: this.props.isMobile
27782
- });
27783
- graphie.label([0, range[1][1]], labels[1], this.props.isMobile ? "below right" : "above");
27784
- graphie.label([range[0][1], 0], labels[0], this.props.isMobile ? "above left" : "right");
27785
- } else if (this.props.markings === "grid") {
27786
- graphie.graphInit({
27787
- range: range,
27788
- scale: vector$3.map(gridConfig, g => g.scale),
27789
- gridStep: this.props.gridStep,
27790
- axes: false,
27791
- ticks: false,
27792
- labels: false,
27793
- isMobile: this.props.isMobile
27794
- });
27795
- } else if (this.props.markings === "none") {
27796
- graphie.init({
27797
- range: range,
27798
- scale: vector$3.map(gridConfig, g => g.scale),
27799
- isMobile: this.props.isMobile
27800
- });
27801
- }
27802
-
27803
- // Add instructions just before mouse layer
27804
- const visible = 0.5;
27805
- const invisible = 0.0;
27806
- let $instructionsWrapper;
27807
- if (this.props.instructions) {
27808
- $instructionsWrapper = $("<div/>");
27809
- _.each(defaultInstructionsStyle, function (value, key) {
27810
- $instructionsWrapper.css(key, value);
27811
- });
27812
- $instructionsWrapper.css("opacity", visible);
27813
- const $instructions = $("<span/>", {
27814
- text: this.props.instructions
27815
- });
27816
- _.each(instructionsTextStyle, function (value, key) {
27817
- $instructions.css(key, value);
27818
- });
27819
- $instructionsWrapper.append($instructions);
27820
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'append' does not exist on type 'JQueryStatic'.
27821
- $(graphieDiv).append($instructionsWrapper);
27822
- } else {
27823
- $instructionsWrapper = undefined;
27824
- }
27825
-
27826
- // Add some handlers for instructions text (if necessary)
27827
- const onMouseDown = $instructionsWrapper || this.props.onMouseDown ? _.bind(function (coord) {
27828
- if ($instructionsWrapper) {
27829
- $instructionsWrapper.remove();
27830
- $instructionsWrapper = null;
27831
- }
27832
- // eslint-disable-next-line @babel/no-invalid-this
27833
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
27834
- // eslint-disable-next-line @babel/no-invalid-this
27835
- this.props.onMouseDown(coord);
27836
- }, this) : undefined;
27837
- const onMouseOver = $instructionsWrapper ? function () {
27838
- var _$instructionsWrapper;
27839
- (_$instructionsWrapper = $instructionsWrapper) == null || _$instructionsWrapper.css("opacity", invisible);
27840
- } : undefined;
27841
- const onMouseOut = $instructionsWrapper ? function () {
27842
- var _$instructionsWrapper2;
27843
- (_$instructionsWrapper2 = $instructionsWrapper) == null || _$instructionsWrapper2.css("opacity", visible);
27844
- } : undefined;
27845
- /* eslint-enable indent */
27846
-
27847
- graphie.addMouseLayer({
27848
- onClick: this.props.onClick,
27849
- onMouseDown: onMouseDown,
27850
- onMouseOver: onMouseOver,
27851
- onMouseOut: onMouseOut,
27852
- onMouseMove: this.props.onMouseMove,
27853
- allowScratchpad: true,
27854
- setDrawingAreaAvailable: this.props.setDrawingAreaAvailable
27855
- });
27856
- this._updateProtractor();
27857
- this._updateRuler();
27858
-
27859
- // We set this flag before jumping into our callback
27860
- // to avoid recursing if our callback calls reset() itself
27861
- this._hasSetupGraphieThisUpdate = true;
27862
- if (!initialMount && this.props.onGraphieUpdated) {
27863
- // Calling a parent callback in componentDidMount is bad and
27864
- // results in hard-to-reason-about lifecycle problems (esp. with
27865
- // refs), so we do it only on update and rely on the parent to
27866
- // query for the graphie object on initial mount
27867
- this.props.onGraphieUpdated(graphie);
27868
- }
27869
- };
27870
- this._getGridConfig = () => {
27871
- return vector$3.map(this.props.step, (step, i) => Util.gridDimensionConfig(step, this.props.range[i], this.props.box[i], this.props.gridStep[i]));
27872
- };
27873
- this._updateProtractor = () => {
27874
- if (this.protractor) {
27875
- this.protractor.remove();
27876
- }
27877
- if (this.props.showProtractor) {
27878
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
27879
- const coord = this.pointsFromNormalized([[0.5, 0.05]])[0];
27880
- this.protractor = this._graphie.protractor(coord);
27881
- }
27882
- };
27883
- this._updateRuler = () => {
27884
- if (this.ruler) {
27885
- this.ruler.remove();
27886
- }
27887
- if (this.props.showRuler) {
27888
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
27889
- const coord = this.pointsFromNormalized([[0.5, 0.25]])[0];
27890
- const extent = this._graphie.range[0][1] - this._graphie.range[0][0];
27891
- this.ruler = this._graphie.ruler({
27892
- center: coord,
27893
- label: this.props.rulerLabel,
27894
- pixelsPerUnit: this._graphie.scale[0],
27895
- ticksPerUnit: this.props.rulerTicks,
27896
- units: Math.round(0.8 * extent)
27897
- });
27898
- }
27899
- };
27900
- this.toJSON = () => {
27901
- return _.pick(this.props, "range", "step", "markings", "labels", "backgroundImage", "showProtractor", "showRuler", "rulerLabel", "rulerTicks", "gridStep", "snapStep");
27902
- };
27903
- }
27904
- componentDidMount() {
27905
- this._setupGraphie(true);
27906
- }
27907
- UNSAFE_componentWillReceiveProps(nextProps) {
27908
- const potentialChanges = ["labels", "range", "step", "markings", "showProtractor", "showRuler", "rulerLabel", "rulerTicks", "gridStep", "snapStep"];
27909
- const self = this;
27910
- _.each(potentialChanges, function (prop) {
27911
- if (!_.isEqual(self.props[prop], nextProps[prop])) {
27912
- self._shouldSetupGraphie = true;
27913
- }
27914
- });
27915
- }
27916
- componentDidUpdate() {
27917
- // Only setupGraphie once per componentDidUpdate().
27918
- // See explanation in setupGraphie().
27919
- this._hasSetupGraphieThisUpdate = false;
27920
- if (this._shouldSetupGraphie) {
27921
- this._setupGraphie(false);
27922
- this._shouldSetupGraphie = false;
27923
- }
27924
- }
27925
- render() {
27926
- let image;
27927
- const imageData = this.props.backgroundImage;
27928
- if (imageData.url) {
27929
- const scale = this.props.box[0] / interactiveSizes$1.defaultBoxSize;
27930
- image = /*#__PURE__*/React.createElement(context$1.Consumer, null, ({
27931
- setAssetStatus
27932
- }) => /*#__PURE__*/React.createElement(SvgImage
27933
- // @ts-expect-error - TS2741 - Property 'alt' is missing in type '{ src: any; width: any; height: any; scale: number; responsive: false; }' but required in type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | "height" | "width" | "title" | "alt" | "trackInteraction" | "preloader" | "allowFullBleed" | "extraGraphie" | "overrideAriaHidden">'.
27934
- , {
27935
- src: imageData.url,
27936
- width: imageData.width,
27937
- height: imageData.height,
27938
- scale: scale,
27939
- responsive: false,
27940
- setAssetStatus: setAssetStatus
27941
- }));
27942
- } else {
27943
- image = null;
27944
- }
27945
- return /*#__PURE__*/React.createElement("div", {
27946
- className: "graphie-container blank-background",
27947
- style: {
27948
- width: this.props.box[0],
27949
- height: this.props.box[1]
27950
- }
27951
- // @ts-expect-error - TS2339 - Property 'onMouseOut' does not exist on type 'Graph'.
27952
- ,
27953
- onMouseOut: this.onMouseOut
27954
- // @ts-expect-error - TS2339 - Property 'onMouseOver' does not exist on type 'Graph'.
27955
- ,
27956
- onMouseOver: this.onMouseOver
27957
- // @ts-expect-error - TS2339 - Property 'onClick' does not exist on type 'Graph'.
27958
- ,
27959
- onClick: this.onClick
27960
- }, image, /*#__PURE__*/React.createElement("div", {
27961
- className: "graphie",
27962
- ref: this.graphieDivRef
27963
- }));
27964
- }
27965
- }
27966
- Graph.defaultProps = {
27967
- labels: ["x", "y"],
27968
- range: [[-10, 10], [-10, 10]],
27969
- step: [1, 1],
27970
- gridStep: [1, 1],
27971
- snapStep: [0.5, 0.5],
27972
- markings: "graph",
27973
- backgroundImage: defaultBackgroundImage$1,
27974
- showProtractor: false,
27975
- showRuler: false,
27976
- rulerLabel: "",
27977
- rulerTicks: 10,
27978
- isMobile: false
27979
- };
27980
-
27981
27691
  const getPromptJSON$b = (props, userInput) => {
27982
27692
  var _props$backgroundImag;
27983
27693
  return {
@@ -28530,13 +28240,18 @@ function escapeIfUnescaped(character) {
28530
28240
  }
28531
28241
  }
28532
28242
 
28533
- function AxisLabels() {
28243
+ function AxisLabels({
28244
+ i18n
28245
+ }) {
28534
28246
  const {
28535
28247
  range,
28536
28248
  labels,
28537
28249
  width,
28538
28250
  height
28539
28251
  } = useGraphConfig();
28252
+ const {
28253
+ strings
28254
+ } = i18n;
28540
28255
  const yAxisLabelLocation = [0, range[Y][MAX]];
28541
28256
  const xAxisLabelLocation = [range[X][MAX], 0];
28542
28257
  const [xAxisLabelText, yAxisLabelText] = labels;
@@ -28554,6 +28269,7 @@ function AxisLabels() {
28554
28269
  TeX
28555
28270
  } = getDependencies();
28556
28271
  return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
28272
+ "aria-label": strings.xAxis,
28557
28273
  style: {
28558
28274
  position: "absolute",
28559
28275
  left: x1,
@@ -28562,6 +28278,7 @@ function AxisLabels() {
28562
28278
  transform: "translate(7px, -50%)"
28563
28279
  }
28564
28280
  }, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default.createElement("span", {
28281
+ "aria-label": strings.yAxis,
28565
28282
  style: {
28566
28283
  position: "absolute",
28567
28284
  left: x2,
@@ -29371,7 +29088,6 @@ RangeInput.defaultProps = {
29371
29088
  var components = /*#__PURE__*/Object.freeze({
29372
29089
  __proto__: null,
29373
29090
  ButtonGroup: ButtonGroup,
29374
- Graph: Graph,
29375
29091
  Graphie: Graphie,
29376
29092
  HUD: HUD,
29377
29093
  Icon: Icon,
@@ -29512,9 +29228,9 @@ const LockedFunction = props => {
29512
29228
  const {
29513
29229
  color,
29514
29230
  strokeStyle,
29515
- directionalAxis,
29516
- domain
29231
+ directionalAxis
29517
29232
  } = props;
29233
+ const domain = props.domain ? [Number.isFinite(props.domain[0]) ? props.domain[0] : -Infinity, Number.isFinite(props.domain[1]) ? props.domain[1] : Infinity] : undefined;
29518
29234
  const plotProps = {
29519
29235
  color: lockedFigureColors[color],
29520
29236
  style: strokeStyle,
@@ -29594,6 +29310,11 @@ function Vector(props) {
29594
29310
  }
29595
29311
 
29596
29312
  function srFormatNumber(a, locale, maximumFractionDigits) {
29313
+ const piBasedNumber = getPiMultiple(a);
29314
+ if (piBasedNumber) {
29315
+ return piBasedNumber;
29316
+ }
29317
+
29597
29318
  // adding zero here converts negative zero to positive zero.
29598
29319
  return (0 + a).toLocaleString(locale, {
29599
29320
  maximumFractionDigits: maximumFractionDigits != null ? maximumFractionDigits : 3,
@@ -29601,6 +29322,77 @@ function srFormatNumber(a, locale, maximumFractionDigits) {
29601
29322
  });
29602
29323
  }
29603
29324
 
29325
+ // Exported for testing
29326
+ function getPiMultiple(a) {
29327
+ // - Save some calculations by checking if this is an integer first.
29328
+ // If a is an integer, it's definitely not a multiple of π.
29329
+ // - 0 should not be presented as a multiple of π.
29330
+ // - If a is greater than 1e12, then it thinks that it's always a
29331
+ // multiple of π even when it's not, due to rounding errors.
29332
+ if (Number.isInteger(a) || a === 0 || a > 1e12) {
29333
+ return null;
29334
+ }
29335
+
29336
+ // Figure out the coefficient before the π.
29337
+ // Example: If a = π/2, then piCoefficient = 0.5
29338
+ // Example: If a = 2π, then piCoefficient = 2
29339
+ const piCoefficient = a / Math.PI;
29340
+ // Truncate the coefficient to account for floating point errors.
29341
+ // π calculations are only accurate up to precision of 1e-12.
29342
+ const truncatedCoefficient = parseFloat(piCoefficient.toFixed(12));
29343
+
29344
+ // If the coefficient is already an integer, then the number
29345
+ // is a multiple of π. Return it here.
29346
+ // Example: If a = 2π, then truncatedCoefficient = 2, so return "2π"
29347
+ if (Number.isInteger(truncatedCoefficient)) {
29348
+ // Return "π" rather than "1π".
29349
+ if (truncatedCoefficient === 1) {
29350
+ return "π";
29351
+ }
29352
+
29353
+ // Return "-π" rather than "-1π".
29354
+ if (truncatedCoefficient === -1) {
29355
+ return "-π";
29356
+ }
29357
+ return truncatedCoefficient + "π";
29358
+ }
29359
+
29360
+ // If the coefficient is not an integer, then we need to
29361
+ // check if it's a multiple of π/2, π/3, π/4, or π/6.
29362
+ // These are π-based values on a unit circle.
29363
+ const acceptableDivisors = [2, 3, 4, 6];
29364
+
29365
+ // Loop through the acceptable divisors and check if the
29366
+ // coefficient is a multiple of (1/divisor).
29367
+ // Example: If a = 5π/6, the coeff is 5/6, which is a multiple of 1/6.
29368
+ // So we return "5π/6".
29369
+ for (const divisor of acceptableDivisors) {
29370
+ // Check if the coefficient is a multiple of (1/divisor) by
29371
+ // multiplying the coefficient by the divisor and checking if
29372
+ // the result is an integer.
29373
+ // Example: If a = 5π/6, then piCoefficient = 5/6. We multiply
29374
+ // by 6 (divisor) to get 5, which is an integer.
29375
+ const coefficientNumerator = parseFloat(
29376
+ // π calculations are only accurate up to precision of 1e-12.
29377
+ (piCoefficient * divisor).toFixed(12));
29378
+ if (Number.isInteger(coefficientNumerator)) {
29379
+ // Handle the case where the coefficient numberator is just 1.
29380
+ // We don't want to write "π/6" as "1π/6"
29381
+ if (coefficientNumerator === 1) {
29382
+ return "π/" + divisor;
29383
+ }
29384
+
29385
+ // Handle the case where the coefficient numberator is just -1.
29386
+ // We don't want to write "π/6" as "-1π/6"
29387
+ if (coefficientNumerator === -1) {
29388
+ return "-π/" + divisor;
29389
+ }
29390
+ return coefficientNumerator + "π/" + divisor;
29391
+ }
29392
+ }
29393
+ return null;
29394
+ }
29395
+
29604
29396
  /**
29605
29397
  * Given a ray and a rectangular box, find the point where the ray intersects
29606
29398
  * the edge of the box. Assumes the `initialPoint` is inside the box.
@@ -30756,6 +30548,34 @@ function range(min, max, step = 1) {
30756
30548
  return result;
30757
30549
  }
30758
30550
 
30551
+ function Hairlines(props) {
30552
+ const {
30553
+ point
30554
+ } = props;
30555
+ const {
30556
+ range
30557
+ } = useGraphConfig();
30558
+ const [[xMin, xMax], [yMin, yMax]] = range;
30559
+ const [[x, y]] = useTransformVectorsToPixels(point);
30560
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
30561
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
30562
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
30563
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
30564
+ return /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
30565
+ x1: verticalStartX,
30566
+ y1: y,
30567
+ x2: verticalEndX,
30568
+ y2: y,
30569
+ stroke: color.blue
30570
+ }), /*#__PURE__*/React.createElement("line", {
30571
+ x1: x,
30572
+ y1: horizontalStartY,
30573
+ x2: x,
30574
+ y2: horizontalEndY,
30575
+ stroke: color.blue
30576
+ }));
30577
+ }
30578
+
30759
30579
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
30760
30580
  // graph.
30761
30581
  const hitboxSizePx = 48;
@@ -30766,7 +30586,6 @@ const hitboxSizePx = 48;
30766
30586
  const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30767
30587
  var _Object$entries$find$, _Object$entries$find;
30768
30588
  const {
30769
- range,
30770
30589
  markings,
30771
30590
  showTooltips
30772
30591
  } = useGraphConfig();
@@ -30774,6 +30593,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30774
30593
  point,
30775
30594
  color: color$1 = color.blue,
30776
30595
  dragging,
30596
+ focused,
30777
30597
  cursor,
30778
30598
  showFocusRing,
30779
30599
  onClick = () => {}
@@ -30784,25 +30604,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30784
30604
  const wbColorName = (_Object$entries$find$ = (_Object$entries$find = Object.entries(color).find(([_, value]) => value === color$1)) == null ? void 0 : _Object$entries$find[0]) != null ? _Object$entries$find$ : "blue";
30785
30605
  const pointClasses = classNames("movable-point", dragging && "movable-point--dragging", showFocusRing && "movable-point--focus");
30786
30606
  const [[x, y]] = useTransformVectorsToPixels(point);
30787
- const [[xMin, xMax], [yMin, yMax]] = range;
30788
- const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
30789
- const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
30790
- const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
30791
- const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
30792
- const showHairlines = dragging && markings !== "none";
30793
- const hairlines = /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
30794
- x1: verticalStartX,
30795
- y1: y,
30796
- x2: verticalEndX,
30797
- y2: y,
30798
- stroke: color$1
30799
- }), /*#__PURE__*/React.createElement("line", {
30800
- x1: x,
30801
- y1: horizontalStartY,
30802
- x2: x,
30803
- y2: horizontalEndY,
30804
- stroke: color$1
30805
- }));
30607
+ const showHairlines = (dragging || focused) && markings !== "none";
30806
30608
  const svgForPoint = /*#__PURE__*/React.createElement("g", {
30807
30609
  ref: hitboxRef,
30808
30610
  className: pointClasses,
@@ -30838,7 +30640,9 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30838
30640
  },
30839
30641
  "data-testid": "movable-point__center"
30840
30642
  }));
30841
- return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
30643
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement(Hairlines, {
30644
+ point: point
30645
+ }), showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
30842
30646
  autoUpdate: true,
30843
30647
  backgroundColor: wbColorName,
30844
30648
  content: `(${point[X]}, ${point[Y]})`,
@@ -30932,6 +30736,7 @@ function useControlPoint(params) {
30932
30736
  },
30933
30737
  point: point,
30934
30738
  dragging: dragging,
30739
+ focused: focused,
30935
30740
  color: color,
30936
30741
  ref: visiblePointRef,
30937
30742
  showFocusRing: focused
@@ -31148,13 +30953,13 @@ const {
31148
30953
  getClockwiseAngle: getClockwiseAngle$2,
31149
30954
  polar: polar$1
31150
30955
  } = angles;
31151
- function renderAngleGraph(state, dispatch) {
30956
+ function renderAngleGraph(state, dispatch, i18n) {
31152
30957
  return {
31153
30958
  graph: /*#__PURE__*/React.createElement(AngleGraph, {
31154
30959
  graphState: state,
31155
30960
  dispatch: dispatch
31156
30961
  }),
31157
- interactiveElementsDescription: null
30962
+ interactiveElementsDescription: getAngleGraphDescription(state, i18n)
31158
30963
  };
31159
30964
  }
31160
30965
  function AngleGraph(props) {
@@ -31165,6 +30970,9 @@ function AngleGraph(props) {
31165
30970
  const {
31166
30971
  graphDimensionsInPixels
31167
30972
  } = useGraphConfig();
30973
+ const i18n = usePerseusI18n();
30974
+ const id = React.useId();
30975
+ const descriptionId = id + "-description";
31168
30976
  const {
31169
30977
  coords,
31170
30978
  showAngles,
@@ -31213,76 +31021,106 @@ function AngleGraph(props) {
31213
31021
  showAngles: showAngles || false // Whether to show the angle or not
31214
31022
  };
31215
31023
 
31024
+ // Aria strings
31216
31025
  const {
31217
- strings,
31218
- locale
31219
- } = usePerseusI18n();
31220
- const angleMeasure = srFormatNumber(getClockwiseAngle$2([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
31221
- const wholeAngleAriaLabel = strings.srAngleGraphAriaLabel;
31222
- const wholeAngleDescription = strings.srAngleGraphAriaDescription({
31223
- angleMeasure,
31224
- vertexX: srFormatNumber(coords[1][X], locale),
31225
- vertexY: srFormatNumber(coords[1][Y], locale),
31226
- startingSideX: srFormatNumber(coords[2][X], locale),
31227
- startingSideY: srFormatNumber(coords[2][Y], locale),
31228
- endingSideX: srFormatNumber(coords[0][X], locale),
31229
- endingSideY: srFormatNumber(coords[0][Y], locale)
31230
- });
31231
- const formatCoordinates = (x, y) => ({
31232
- x: srFormatNumber(x, locale),
31233
- y: srFormatNumber(y, locale)
31234
- });
31235
- const formatSideAriaLabel = (side, newPoint) => {
31236
- const formattedCoordinates = formatCoordinates(newPoint[X], newPoint[Y]);
31237
- const label = side === "terminal" ? strings.srAngleSideAtCoordinates(_extends({
31238
- point: 1,
31239
- side: "terminal side"
31240
- }, formattedCoordinates)) : strings.srAngleSideAtCoordinates(_extends({
31241
- point: 3,
31242
- side: "initial side"
31243
- }, formattedCoordinates));
31244
- return label;
31245
- };
31246
- const formatVertexAriaLabel = newPoint => {
31247
- const formattedVertexCoordinates = formatCoordinates(newPoint[X], newPoint[Y]);
31248
- const label = showAngles ? strings.srAngleVertexAtCoordinatesWithAngleMeasure(_extends({}, formattedVertexCoordinates, {
31249
- angleMeasure: angleMeasure
31250
- })) : strings.srAngleSideAtCoordinates(_extends({
31251
- point: 2,
31252
- side: "vertex"
31253
- }, formattedVertexCoordinates));
31254
- return label;
31255
- };
31256
- const initialSideAriaLabel = formatSideAriaLabel("initial", coords[2]);
31257
- const terminalSideAriaLabel = formatSideAriaLabel("terminal", coords[0]);
31258
- const vertexAriaLabel = formatVertexAriaLabel(coords[1]);
31026
+ srAngleGraphAriaLabel,
31027
+ srAngleGraphAriaDescription,
31028
+ srAngleStartingSide,
31029
+ srAngleEndingSide,
31030
+ srAngleVertex
31031
+ } = describeAngleGraph(graphState, i18n);
31259
31032
 
31260
31033
  // Render the lines, angle, and then movable points
31261
31034
  return /*#__PURE__*/React.createElement("g", {
31262
- "aria-label": wholeAngleAriaLabel,
31263
- "aria-describedby": "angle-description"
31035
+ "aria-label": srAngleGraphAriaLabel,
31036
+ "aria-describedby": descriptionId
31264
31037
  }, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
31265
31038
  point: coords[1],
31266
31039
  sequenceNumber: 1,
31267
31040
  constrain: p => p,
31268
31041
  onMove: destination => dispatch(actions.angle.movePoint(1, destination)),
31269
- ariaLabel: vertexAriaLabel
31042
+ ariaLabel: srAngleVertex
31270
31043
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
31271
31044
  point: coords[0],
31272
31045
  sequenceNumber: 2,
31273
31046
  constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
31274
31047
  onMove: destination => dispatch(actions.angle.movePoint(0, destination)),
31275
- ariaLabel: terminalSideAriaLabel
31048
+ ariaLabel: srAngleEndingSide
31276
31049
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
31277
31050
  point: coords[2],
31278
31051
  sequenceNumber: 3,
31279
31052
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
31280
31053
  onMove: destination => dispatch(actions.angle.movePoint(2, destination)),
31281
- ariaLabel: initialSideAriaLabel
31054
+ ariaLabel: srAngleStartingSide
31282
31055
  }), /*#__PURE__*/React.createElement("g", {
31283
- id: "angle-description",
31056
+ id: descriptionId,
31284
31057
  style: a11y.srOnly
31285
- }, wholeAngleDescription));
31058
+ }, srAngleGraphAriaDescription));
31059
+ }
31060
+ function getAngleGraphDescription(state, i18n) {
31061
+ const {
31062
+ strings,
31063
+ locale
31064
+ } = i18n;
31065
+ const {
31066
+ coords
31067
+ } = state;
31068
+ return strings.srInteractiveElements({
31069
+ elements: strings.srAngleInteractiveElements({
31070
+ vertexX: srFormatNumber(coords[1][X], locale),
31071
+ vertexY: srFormatNumber(coords[1][Y], locale),
31072
+ startingSideX: srFormatNumber(coords[2][X], locale),
31073
+ startingSideY: srFormatNumber(coords[2][Y], locale),
31074
+ endingSideX: srFormatNumber(coords[0][X], locale),
31075
+ endingSideY: srFormatNumber(coords[0][Y], locale)
31076
+ })
31077
+ });
31078
+ }
31079
+ function describeAngleGraph(state, i18n) {
31080
+ const {
31081
+ strings,
31082
+ locale
31083
+ } = i18n;
31084
+ const {
31085
+ coords,
31086
+ allowReflexAngles,
31087
+ showAngles
31088
+ } = state;
31089
+ const [endingSide, vertex, startingSide] = coords;
31090
+ const angleMeasure = srFormatNumber(getClockwiseAngle$2(coords, allowReflexAngles), locale);
31091
+ const srAngleGraphAriaLabel = strings.srAngleGraphAriaLabel;
31092
+ const srAngleGraphAriaDescription = strings.srAngleGraphAriaDescription({
31093
+ angleMeasure,
31094
+ vertexX: srFormatNumber(vertex[X], locale),
31095
+ vertexY: srFormatNumber(vertex[Y], locale),
31096
+ startingSideX: srFormatNumber(startingSide[X], locale),
31097
+ startingSideY: srFormatNumber(startingSide[Y], locale),
31098
+ endingSideX: srFormatNumber(endingSide[X], locale),
31099
+ endingSideY: srFormatNumber(endingSide[Y], locale)
31100
+ });
31101
+ const srAngleStartingSide = strings.srAngleStartingSide({
31102
+ x: srFormatNumber(startingSide[X], locale),
31103
+ y: srFormatNumber(startingSide[Y], locale)
31104
+ });
31105
+ const srAngleEndingSide = strings.srAngleEndingSide({
31106
+ x: srFormatNumber(endingSide[X], locale),
31107
+ y: srFormatNumber(endingSide[Y], locale)
31108
+ });
31109
+ const srAngleVertex = showAngles ? strings.srAngleVertexWithAngleMeasure({
31110
+ x: srFormatNumber(vertex[X], locale),
31111
+ y: srFormatNumber(vertex[Y], locale),
31112
+ angleMeasure
31113
+ }) : strings.srAngleVertex({
31114
+ x: srFormatNumber(vertex[X], locale),
31115
+ y: srFormatNumber(vertex[Y], locale)
31116
+ });
31117
+ return {
31118
+ srAngleGraphAriaLabel,
31119
+ srAngleGraphAriaDescription,
31120
+ srAngleStartingSide,
31121
+ srAngleEndingSide,
31122
+ srAngleVertex
31123
+ };
31286
31124
  }
31287
31125
  const positiveX = [1, 0];
31288
31126
  const negativeX = [-1, 0];
@@ -31413,17 +31251,16 @@ function getRadius(graph) {
31413
31251
  return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
31414
31252
  }
31415
31253
 
31416
- function renderCircleGraph(state, dispatch) {
31254
+ function renderCircleGraph(state, dispatch, i18n) {
31417
31255
  return {
31418
31256
  graph: /*#__PURE__*/React.createElement(CircleGraph, {
31419
31257
  graphState: state,
31420
31258
  dispatch: dispatch
31421
31259
  }),
31422
- interactiveElementsDescription: /*#__PURE__*/React.createElement(CircleGraphDescription, {
31423
- state: state
31424
- })
31260
+ interactiveElementsDescription: getCircleGraphDescription(state, i18n)
31425
31261
  };
31426
31262
  }
31263
+ // Exported for testing
31427
31264
  function CircleGraph(props) {
31428
31265
  const {
31429
31266
  dispatch,
@@ -31516,6 +31353,7 @@ function MovableCircle(props) {
31516
31353
  snapStep,
31517
31354
  disableKeyboardInteraction
31518
31355
  } = useGraphConfig();
31356
+ const [focused, setFocused] = React.useState(false);
31519
31357
  const draggableRef = useRef(null);
31520
31358
  const {
31521
31359
  dragging
@@ -31534,7 +31372,9 @@ function MovableCircle(props) {
31534
31372
  ref: draggableRef,
31535
31373
  role: "button",
31536
31374
  tabIndex: disableKeyboardInteraction ? -1 : 0,
31537
- className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31375
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`,
31376
+ onFocus: () => setFocused(true),
31377
+ onBlur: () => setFocused(false)
31538
31378
  }, /*#__PURE__*/React.createElement("ellipse", {
31539
31379
  className: "focus-ring",
31540
31380
  cx: centerPx[X],
@@ -31549,19 +31389,29 @@ function MovableCircle(props) {
31549
31389
  rx: radiiPx[X],
31550
31390
  ry: radiiPx[Y]
31551
31391
  }), /*#__PURE__*/React.createElement(DragHandle, {
31552
- center: center
31392
+ center: center,
31393
+ dragging: dragging,
31394
+ focused: focused
31553
31395
  }));
31554
31396
  }
31555
31397
  const dragHandleDimensions = [24, 14];
31556
31398
  const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
31557
31399
  function DragHandle(props) {
31558
31400
  const {
31559
- center
31401
+ center,
31402
+ dragging,
31403
+ focused
31560
31404
  } = props;
31561
- const cornerRadius = Math.min(...dragHandleDimensions) / 2;
31562
31405
  const [centerPx] = useTransformVectorsToPixels(center);
31406
+ const {
31407
+ markings
31408
+ } = useGraphConfig();
31409
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
31563
31410
  const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
31564
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
31411
+ const showHairlines = (dragging || focused) && markings !== "none";
31412
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement(Hairlines, {
31413
+ point: center
31414
+ }), /*#__PURE__*/React.createElement("rect", {
31565
31415
  className: "movable-circle-handle",
31566
31416
  x: topLeft[X],
31567
31417
  y: topLeft[Y],
@@ -31588,13 +31438,7 @@ function crossProduct(as, bs) {
31588
31438
  }
31589
31439
  return result;
31590
31440
  }
31591
- function CircleGraphDescription({
31592
- state
31593
- }) {
31594
- // The reason that CircleGraphDescription is a component (rather than a
31595
- // function that returns a string) is because it needs to use a
31596
- // hook: `usePerseusI18n`.
31597
- const i18n = usePerseusI18n();
31441
+ function getCircleGraphDescription(state, i18n) {
31598
31442
  const strings = describeCircleGraph(state, i18n);
31599
31443
  return strings.srCircleInteractiveElement;
31600
31444
  }
@@ -31647,15 +31491,13 @@ function describeCircleGraph(state, i18n) {
31647
31491
  };
31648
31492
  }
31649
31493
 
31650
- function renderLinearGraph(state, dispatch) {
31494
+ function renderLinearGraph(state, dispatch, i18n) {
31651
31495
  return {
31652
31496
  graph: /*#__PURE__*/React.createElement(LinearGraph, {
31653
31497
  graphState: state,
31654
31498
  dispatch: dispatch
31655
31499
  }),
31656
- interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearGraphDescription, {
31657
- state: state
31658
- })
31500
+ interactiveElementsDescription: getLinearGraphDescription(state, i18n)
31659
31501
  };
31660
31502
  }
31661
31503
  const LinearGraph = (props, key) => {
@@ -31720,13 +31562,7 @@ const LinearGraph = (props, key) => {
31720
31562
  style: a11y.srOnly
31721
31563
  }, slopeString));
31722
31564
  };
31723
- function LinearGraphDescription({
31724
- state
31725
- }) {
31726
- // The reason that LinearGraphDescription is a component (rather than a
31727
- // function that returns a string) is because it needs to use a
31728
- // hook: `usePerseusI18n`.
31729
- const i18n = usePerseusI18n();
31565
+ function getLinearGraphDescription(state, i18n) {
31730
31566
  const strings = describeLinearGraph(state, i18n);
31731
31567
  return strings.srLinearInteractiveElement;
31732
31568
  }
@@ -31770,15 +31606,13 @@ function describeLinearGraph(state, i18n) {
31770
31606
  };
31771
31607
  }
31772
31608
 
31773
- function renderLinearSystemGraph(state, dispatch) {
31609
+ function renderLinearSystemGraph(state, dispatch, i18n) {
31774
31610
  return {
31775
31611
  graph: /*#__PURE__*/React.createElement(LinearSystemGraph, {
31776
31612
  graphState: state,
31777
31613
  dispatch: dispatch
31778
31614
  }),
31779
- interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearSystemGraphDescription, {
31780
- state: state
31781
- })
31615
+ interactiveElementsDescription: getLinearSystemGraphDescription(state, i18n)
31782
31616
  };
31783
31617
  }
31784
31618
  const LinearSystemGraph = props => {
@@ -31865,18 +31699,9 @@ const LinearSystemGraph = props => {
31865
31699
  style: a11y.srOnly
31866
31700
  }, slopeDescription))));
31867
31701
  };
31868
- function LinearSystemGraphDescription({
31869
- state
31870
- }) {
31871
- // The reason that LinearSystemGraphDescription is a component (rather
31872
- // than a function that returns a string) is because it needs to use a
31873
- // hook: `usePerseusI18n`.
31874
- const i18n = usePerseusI18n();
31875
- return describeLinearSystemGraph(state, i18n);
31876
- }
31877
31702
 
31878
31703
  // Exported for testing
31879
- function describeLinearSystemGraph(state, i18n) {
31704
+ function getLinearSystemGraphDescription(state, i18n) {
31880
31705
  const {
31881
31706
  strings,
31882
31707
  locale
@@ -31902,15 +31727,13 @@ function describeLinearSystemGraph(state, i18n) {
31902
31727
  });
31903
31728
  }
31904
31729
 
31905
- function renderPointGraph(state, dispatch) {
31730
+ function renderPointGraph(state, dispatch, i18n) {
31906
31731
  return {
31907
31732
  graph: /*#__PURE__*/React.createElement(PointGraph, {
31908
31733
  graphState: state,
31909
31734
  dispatch: dispatch
31910
31735
  }),
31911
- interactiveElementsDescription: /*#__PURE__*/React.createElement(PointGraphDescription, {
31912
- state: state
31913
- })
31736
+ interactiveElementsDescription: getPointGraphDescription(state, i18n)
31914
31737
  };
31915
31738
  }
31916
31739
  function PointGraph(props) {
@@ -31933,7 +31756,9 @@ function PointGraph(props) {
31933
31756
  var _pointsRef$current$fo;
31934
31757
  (_pointsRef$current$fo = pointsRef.current[focusedIndex]) == null || _pointsRef$current$fo.focus();
31935
31758
  }
31936
- }, [props.graphState.focusedPointIndex, pointsRef]);
31759
+ }, [props.graphState.focusedPointIndex,
31760
+ // Ensure that we re-focus if a point is deleted.
31761
+ props.graphState.coords.length, pointsRef]);
31937
31762
  const statefulProps = _extends({}, props, {
31938
31763
  graphConfig,
31939
31764
  pointsRef,
@@ -32004,17 +31829,9 @@ function UnlimitedPointGraph(statefulProps) {
32004
31829
  }
32005
31830
  })));
32006
31831
  }
32007
- function PointGraphDescription({
32008
- state
32009
- }) {
32010
- // PointGraphDescription needs to `usePerseusI18n`, so it has to be a
32011
- // component rather than a function that simply returns a string.
32012
- const i18n = usePerseusI18n();
32013
- return describePointGraph(state, i18n);
32014
- }
32015
31832
 
32016
31833
  // Exported for testing
32017
- function describePointGraph(state, i18n) {
31834
+ function getPointGraphDescription(state, i18n) {
32018
31835
  const {
32019
31836
  strings,
32020
31837
  locale
@@ -32101,7 +31918,9 @@ const PolygonGraph = props => {
32101
31918
  var _pointsRef$current$fo;
32102
31919
  (_pointsRef$current$fo = pointsRef.current[focusedIndex]) == null || _pointsRef$current$fo.focus();
32103
31920
  }
32104
- }, [props.graphState.focusedPointIndex, pointsRef]);
31921
+ }, [props.graphState.focusedPointIndex,
31922
+ // Ensure that we re-focus if a point is deleted.
31923
+ props.graphState.coords.length, pointsRef]);
32105
31924
 
32106
31925
  // If the unlimited polygon is rendered with 3 or more coordinates
32107
31926
  // Close the polygon, but only on first render.
@@ -32681,15 +32500,13 @@ function describeQuadraticGraph(state, i18n) {
32681
32500
  };
32682
32501
  }
32683
32502
 
32684
- function renderRayGraph(state, dispatch) {
32503
+ function renderRayGraph(state, dispatch, i18n) {
32685
32504
  return {
32686
32505
  graph: /*#__PURE__*/React.createElement(RayGraph, {
32687
32506
  graphState: state,
32688
32507
  dispatch: dispatch
32689
32508
  }),
32690
- interactiveElementsDescription: /*#__PURE__*/React.createElement(RayGraphDescription, {
32691
- state: state
32692
- })
32509
+ interactiveElementsDescription: getRayGraphDescription(state, i18n)
32693
32510
  };
32694
32511
  }
32695
32512
  const RayGraph = props => {
@@ -32743,13 +32560,7 @@ const RayGraph = props => {
32743
32560
  style: a11y.srOnly
32744
32561
  }, srRayPoints));
32745
32562
  };
32746
- function RayGraphDescription({
32747
- state
32748
- }) {
32749
- // The reason that RayGraphDescription is a component (rather than a
32750
- // function that returns a string) is because it needs to use a
32751
- // hook: `usePerseusI18n`.
32752
- const i18n = usePerseusI18n();
32563
+ function getRayGraphDescription(state, i18n) {
32753
32564
  const strings = describeRayGraph(state, i18n);
32754
32565
  return strings.srRayInteractiveElement;
32755
32566
  }
@@ -32799,15 +32610,13 @@ function describeRayGraph(state, i18n) {
32799
32610
  };
32800
32611
  }
32801
32612
 
32802
- function renderSegmentGraph(state, dispatch) {
32613
+ function renderSegmentGraph(state, dispatch, i18n) {
32803
32614
  return {
32804
32615
  graph: /*#__PURE__*/React.createElement(SegmentGraph, {
32805
32616
  graphState: state,
32806
32617
  dispatch: dispatch
32807
32618
  }),
32808
- interactiveElementsDescription: /*#__PURE__*/React.createElement(SegmentGraphDescription, {
32809
- state: state
32810
- })
32619
+ interactiveElementsDescription: getSegmentGraphDescription(state, i18n)
32811
32620
  };
32812
32621
  }
32813
32622
  const SegmentGraph = ({
@@ -32899,18 +32708,9 @@ const SegmentGraph = ({
32899
32708
  function getLengthOfSegment(segment) {
32900
32709
  return point.distanceToPoint(...segment);
32901
32710
  }
32902
- function SegmentGraphDescription({
32903
- state
32904
- }) {
32905
- // The reason that SegmentGraphDescription is a component (rather than a
32906
- // function that returns a string) is because it needs to use a
32907
- // hook: `usePerseusI18n`.
32908
- const i18n = usePerseusI18n();
32909
- return describeSegmentGraph(state, i18n);
32910
- }
32911
32711
 
32912
32712
  // Exported for testing
32913
- function describeSegmentGraph(state, i18n) {
32713
+ function getSegmentGraphDescription(state, i18n) {
32914
32714
  const {
32915
32715
  strings,
32916
32716
  locale
@@ -32927,13 +32727,13 @@ function describeSegmentGraph(state, i18n) {
32927
32727
  });
32928
32728
  }
32929
32729
 
32930
- function renderSinusoidGraph(state, dispatch) {
32730
+ function renderSinusoidGraph(state, dispatch, i18n) {
32931
32731
  return {
32932
32732
  graph: /*#__PURE__*/React.createElement(SinusoidGraph, {
32933
32733
  graphState: state,
32934
32734
  dispatch: dispatch
32935
32735
  }),
32936
- interactiveElementsDescription: null
32736
+ interactiveElementsDescription: getSinusoidDescription(state, i18n)
32937
32737
  };
32938
32738
  }
32939
32739
  function SinusoidGraph(props) {
@@ -32941,6 +32741,9 @@ function SinusoidGraph(props) {
32941
32741
  dispatch,
32942
32742
  graphState
32943
32743
  } = props;
32744
+ const i18n = usePerseusI18n();
32745
+ const id = React.useId();
32746
+ const descriptionId = id + "-description";
32944
32747
 
32945
32748
  // Destructure the coordinates from the graph state
32946
32749
  // Note: The order of the coordinates is important:
@@ -32965,15 +32768,30 @@ function SinusoidGraph(props) {
32965
32768
  if (coeffs !== undefined) {
32966
32769
  coeffRef.current = coeffs;
32967
32770
  }
32968
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
32771
+
32772
+ // Aria strings
32773
+ const {
32774
+ srSinusoidGraph,
32775
+ srSinusoidDescription,
32776
+ srSinusoidRootPoint,
32777
+ srSinusoidPeakPoint
32778
+ } = describeSinusoidGraph(graphState, i18n);
32779
+ return /*#__PURE__*/React.createElement("g", {
32780
+ // Outer graph minimal description
32781
+ "aria-label": srSinusoidGraph,
32782
+ "aria-describedby": descriptionId
32783
+ }, /*#__PURE__*/React.createElement(Plot$2.OfX, {
32969
32784
  y: x => computeSine(x, coeffRef.current),
32970
32785
  color: color.blue
32971
32786
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
32787
+ ariaLabel: i === 0 ? srSinusoidRootPoint : srSinusoidPeakPoint,
32972
32788
  key: "point-" + i,
32973
32789
  point: coord,
32974
32790
  sequenceNumber: i + 1,
32975
32791
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
32976
- })));
32792
+ })), /*#__PURE__*/React.createElement("g", {
32793
+ id: descriptionId
32794
+ }, srSinusoidDescription));
32977
32795
  }
32978
32796
 
32979
32797
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
@@ -33011,6 +32829,52 @@ const getSinusoidCoefficients$1 = coords => {
33011
32829
  verticalOffset
33012
32830
  };
33013
32831
  };
32832
+ function getSinusoidDescription(state, i18n) {
32833
+ const strings = describeSinusoidGraph(state, i18n);
32834
+ return strings.srSinusoidInteractiveElements;
32835
+ }
32836
+ function describeSinusoidGraph(state, i18n) {
32837
+ const {
32838
+ strings,
32839
+ locale
32840
+ } = i18n;
32841
+ const {
32842
+ coords
32843
+ } = state;
32844
+ const [root, peak] = coords;
32845
+ const diffX = Math.abs(peak[X] - root[X]);
32846
+ const diffY = Math.abs(peak[Y] - root[Y]);
32847
+ const srSinusoidGraph = strings.srSinusoidGraph;
32848
+ const srSinusoidDescription = strings.srSinusoidDescription({
32849
+ minValue: srFormatNumber(root[Y] - diffY, locale),
32850
+ maxValue: srFormatNumber(root[Y] + diffY, locale),
32851
+ cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
32852
+ cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
32853
+ });
32854
+ const srSinusoidRootPoint = strings.srSinusoidRootPoint({
32855
+ x: srFormatNumber(root[X], locale),
32856
+ y: srFormatNumber(root[Y], locale)
32857
+ });
32858
+ const srSinusoidPeakPoint = strings.srSinusoidPeakPoint({
32859
+ x: srFormatNumber(peak[X], locale),
32860
+ y: srFormatNumber(peak[Y], locale)
32861
+ });
32862
+ const srSinusoidInteractiveElements = strings.srInteractiveElements({
32863
+ elements: strings.srSinusoidInteractiveElements({
32864
+ point1X: srFormatNumber(root[X], locale),
32865
+ point1Y: srFormatNumber(root[Y], locale),
32866
+ point2X: srFormatNumber(peak[X], locale),
32867
+ point2Y: srFormatNumber(peak[Y], locale)
32868
+ })
32869
+ });
32870
+ return {
32871
+ srSinusoidGraph,
32872
+ srSinusoidDescription,
32873
+ srSinusoidRootPoint,
32874
+ srSinusoidPeakPoint,
32875
+ srSinusoidInteractiveElements
32876
+ };
32877
+ }
33014
32878
 
33015
32879
  const {
33016
32880
  calculateAngleInDegrees,
@@ -33181,6 +33045,7 @@ const MafsGraph = props => {
33181
33045
  const descriptionId = `interactive-graph-description-${uniqueId}`;
33182
33046
  const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
33183
33047
  const unlimitedGraphKeyboardPromptId = `unlimited-graph-keyboard-prompt-${uniqueId}`;
33048
+ const instructionsId = `instructions-${uniqueId}`;
33184
33049
  const graphRef = React.useRef(null);
33185
33050
  const {
33186
33051
  analytics
@@ -33208,6 +33073,7 @@ const MafsGraph = props => {
33208
33073
  const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
33209
33074
  useOnMountEffect(() => {
33210
33075
  analytics.onAnalyticsEvent({
33076
+ // TODO(LEMS-2827): Remove analytics event in LEMS-2827 in favor of ti below.
33211
33077
  type: "perseus:interactive-graph-widget:rendered",
33212
33078
  payload: {
33213
33079
  type,
@@ -33215,6 +33081,14 @@ const MafsGraph = props => {
33215
33081
  widgetId: "interactive-graph"
33216
33082
  }
33217
33083
  });
33084
+ analytics.onAnalyticsEvent({
33085
+ type: "perseus:widget:rendered:ti",
33086
+ payload: {
33087
+ widgetSubType: type,
33088
+ widgetType: "INTERACTIVE_GRAPH",
33089
+ widgetId: "interactive-graph"
33090
+ }
33091
+ });
33218
33092
  });
33219
33093
  const {
33220
33094
  graph,
@@ -33258,7 +33132,7 @@ const MafsGraph = props => {
33258
33132
  handleKeyboardEvent(event, state, dispatch);
33259
33133
  },
33260
33134
  "aria-label": fullGraphAriaLabel,
33261
- "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
33135
+ "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId, state.type !== "none" && instructionsId),
33262
33136
  ref: graphRef,
33263
33137
  tabIndex: 0,
33264
33138
  onFocus: event => {
@@ -33275,7 +33149,11 @@ const MafsGraph = props => {
33275
33149
  id: interactiveElementsDescriptionId,
33276
33150
  tabIndex: -1,
33277
33151
  className: "mafs-sr-only"
33278
- }, interactiveElementsDescription), /*#__PURE__*/React.createElement(LegacyGrid, {
33152
+ }, interactiveElementsDescription), state.type !== "none" && /*#__PURE__*/React.createElement(View, {
33153
+ id: instructionsId,
33154
+ tabIndex: -1,
33155
+ className: "mafs-sr-only"
33156
+ }, isUnlimitedGraphState(state) ? strings.srUnlimitedGraphInstructions : strings.srGraphInstructions), /*#__PURE__*/React.createElement(LegacyGrid, {
33279
33157
  box: props.box,
33280
33158
  backgroundImage: props.backgroundImage
33281
33159
  }), /*#__PURE__*/React.createElement(View, {
@@ -33284,7 +33162,9 @@ const MafsGraph = props => {
33284
33162
  bottom: 0,
33285
33163
  left: 0
33286
33164
  }
33287
- }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(View
33165
+ }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, {
33166
+ i18n: i18n
33167
+ })), /*#__PURE__*/React.createElement(View
33288
33168
  // If we have locked figures, we set aria-hidden
33289
33169
  // to false so that screen readers can read the
33290
33170
  // locked figures. If we don't have any locked
@@ -33622,25 +33502,25 @@ const renderGraphElements = props => {
33622
33502
  } = state;
33623
33503
  switch (type) {
33624
33504
  case "angle":
33625
- return renderAngleGraph(state, dispatch);
33505
+ return renderAngleGraph(state, dispatch, i18n);
33626
33506
  case "segment":
33627
- return renderSegmentGraph(state, dispatch);
33507
+ return renderSegmentGraph(state, dispatch, i18n);
33628
33508
  case "linear-system":
33629
- return renderLinearSystemGraph(state, dispatch);
33509
+ return renderLinearSystemGraph(state, dispatch, i18n);
33630
33510
  case "linear":
33631
- return renderLinearGraph(state, dispatch);
33511
+ return renderLinearGraph(state, dispatch, i18n);
33632
33512
  case "ray":
33633
- return renderRayGraph(state, dispatch);
33513
+ return renderRayGraph(state, dispatch, i18n);
33634
33514
  case "polygon":
33635
33515
  return renderPolygonGraph(state, dispatch, i18n, markings);
33636
33516
  case "point":
33637
- return renderPointGraph(state, dispatch);
33517
+ return renderPointGraph(state, dispatch, i18n);
33638
33518
  case "circle":
33639
- return renderCircleGraph(state, dispatch);
33519
+ return renderCircleGraph(state, dispatch, i18n);
33640
33520
  case "quadratic":
33641
33521
  return renderQuadraticGraph(state, dispatch, i18n);
33642
33522
  case "sinusoid":
33643
- return renderSinusoidGraph(state, dispatch);
33523
+ return renderSinusoidGraph(state, dispatch, i18n);
33644
33524
  case "none":
33645
33525
  return {
33646
33526
  graph: null,
@@ -34037,13 +33917,13 @@ const {
34037
33917
  polar
34038
33918
  } = angles;
34039
33919
  const {
34040
- angleMeasures: angleMeasures$1,
34041
- ccw: ccw$1,
34042
- lawOfCosines: lawOfCosines$1,
33920
+ angleMeasures,
33921
+ ccw,
33922
+ lawOfCosines,
34043
33923
  magnitude: magnitude$1,
34044
33924
  polygonSidesIntersect,
34045
33925
  reverseVector,
34046
- sign: sign$1,
33926
+ sign,
34047
33927
  vector: vector$1
34048
33928
  } = geometry;
34049
33929
  const {
@@ -34527,7 +34407,7 @@ function doAddPoint(state, action) {
34527
34407
  return _extends({}, state, {
34528
34408
  hasBeenInteractedWith: true,
34529
34409
  coords: newCoords,
34530
- showRemovePointButton: false,
34410
+ showRemovePointButton: true,
34531
34411
  focusedPointIndex: newCoords.length - 1
34532
34412
  });
34533
34413
  }
@@ -34535,16 +34415,11 @@ function doRemovePoint(state, action) {
34535
34415
  if (!isUnlimitedGraphState(state)) {
34536
34416
  return state;
34537
34417
  }
34538
- let nextFocusedPointIndex;
34539
- if (state.interactionMode === "mouse") {
34540
- nextFocusedPointIndex = null;
34541
- } else {
34542
- nextFocusedPointIndex = state.coords.length > 1 ? state.coords.length - 2 : null;
34543
- }
34418
+ const nextFocusedPointIndex = state.coords.length > 1 ? state.coords.length - 2 : null;
34544
34419
  return _extends({}, state, {
34545
34420
  coords: state.coords.filter((_, i) => i !== action.index),
34546
34421
  focusedPointIndex: nextFocusedPointIndex,
34547
- showRemovePointButton: false
34422
+ showRemovePointButton: nextFocusedPointIndex !== null ? true : false
34548
34423
  });
34549
34424
  }
34550
34425
  const getDeltaVertex = (maxMoves, minMoves, delta) => {
@@ -34568,7 +34443,7 @@ const getChange = (coords, delta, constraintOpts) => {
34568
34443
  return [dx, dy];
34569
34444
  };
34570
34445
  // Less than or approximately equal
34571
- function leq$1(a, b) {
34446
+ function leq(a, b) {
34572
34447
  return a < b || approximateEqual(a, b);
34573
34448
  }
34574
34449
  function boundAndSnapToGrid(point, {
@@ -34713,7 +34588,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
34713
34588
  });
34714
34589
 
34715
34590
  // Gets the radian angles between the coords and maps them to degrees
34716
- const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
34591
+ const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
34717
34592
 
34718
34593
  // Gets the relative index of a point
34719
34594
  const rel = j => {
@@ -34733,7 +34608,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
34733
34608
 
34734
34609
  // Avoid degenerate triangles
34735
34610
  if (innerAngles.some(function (angle) {
34736
- return leq$1(angle, 1);
34611
+ return leq(angle, 1);
34737
34612
  })) {
34738
34613
  return startingPoint;
34739
34614
  }
@@ -34747,7 +34622,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
34747
34622
  // we want to add the inner angle to the outer angle, and if it's on the right,
34748
34623
  // we want to subtract the inner angle from the outer angle. The angle solved
34749
34624
  // for is then used in the polar function to determine the new point.
34750
- const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34625
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34751
34626
 
34752
34627
  // Solve for side by using the law of sines
34753
34628
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
@@ -34794,13 +34669,13 @@ function boundAndSnapToSides(destinationPoint, {
34794
34669
  });
34795
34670
 
34796
34671
  // Avoid degenerate triangles
34797
- if (leq$1(sides[1] + sides[2], sides[0]) || leq$1(sides[0] + sides[2], sides[1]) || leq$1(sides[0] + sides[1], sides[2])) {
34672
+ if (leq(sides[1] + sides[2], sides[0]) || leq(sides[0] + sides[2], sides[1]) || leq(sides[0] + sides[1], sides[2])) {
34798
34673
  return startingPoint;
34799
34674
  }
34800
34675
 
34801
34676
  // Solve for angle by using the law of cosines
34802
34677
  // Angle at the first vertex of the polygon
34803
- const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
34678
+ const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
34804
34679
 
34805
34680
  // Angle at the second vertex of the polygon
34806
34681
  const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
@@ -34811,7 +34686,7 @@ function boundAndSnapToSides(destinationPoint, {
34811
34686
  // we want to add the inner angle to the outer angle, and if it's on the right,
34812
34687
  // we want to subtract the inner angle from the outer angle. The angle solved
34813
34688
  // for is then used in the polar function to determine the new point.
34814
- const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34689
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34815
34690
 
34816
34691
  // Uses the length of the first side of the polygon (radial coordinate)
34817
34692
  // and the angle between the first and second sides of the
@@ -34946,19 +34821,11 @@ const {
34946
34821
  getClockwiseAngle
34947
34822
  } = angles;
34948
34823
  const {
34949
- angleMeasures,
34950
- ccw,
34951
- collinear,
34952
34824
  getLineEquation,
34953
34825
  getLineIntersection,
34954
- intersects,
34955
- lawOfCosines,
34956
34826
  magnitude,
34957
- rotate,
34958
- sign,
34959
34827
  vector
34960
34828
  } = geometry;
34961
- const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
34962
34829
  const defaultBackgroundImage = {
34963
34830
  url: null
34964
34831
  };
@@ -34967,31 +34834,9 @@ const UNLIMITED = "unlimited";
34967
34834
  // Sample background image:
34968
34835
  // https://ka-perseus-graphie.s3.amazonaws.com/29c1b0fcd17fe63df0f148fe357044d5d5c7d0bb.png
34969
34836
 
34970
- // default to defaultValue if actual is null or undefined
34971
- function defaultVal(actual, defaultValue) {
34972
- return actual == null ? defaultValue : actual;
34973
- }
34974
-
34975
- // Less than or approximately equal
34976
- function leq(a, b) {
34977
- return a < b || approximateEqual(a, b);
34978
- }
34979
- function capitalize(str) {
34980
- return str.replace(/(?:^|-)(.)/g, function (match, letter) {
34981
- return letter.toUpperCase();
34982
- });
34983
- }
34984
34837
  function numSteps(range, step) {
34985
34838
  return Math.floor((range[1] - range[0]) / step);
34986
34839
  }
34987
- const _getShouldShowInstructions = props => {
34988
- return _isClickToAddPoints(props) && (
34989
- // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
34990
- props.graph.coords == null || props.graph.coords.length === 0);
34991
- };
34992
- const _isClickToAddPoints = props => {
34993
- return props.graph.type === "point" && props.graph.numPoints === UNLIMITED || props.graph.type === "polygon" && props.graph.numSides === UNLIMITED;
34994
- };
34995
34840
  const makeInvalidTypeError = (functionName, graphType) => {
34996
34841
  return new PerseusError(`${functionName} called but current graph type is not a '${graphType}'`, Errors.NotAllowed, {
34997
34842
  metadata: {
@@ -35032,1236 +34877,16 @@ function getQuadraticCoefficients(coords) {
35032
34877
  const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
35033
34878
  return [a, b, c];
35034
34879
  }
35035
-
35036
- // (LEMS-2190): Move the Mafs Angle Graph coordinate reversal logic in interactive-graph-state.ts
35037
- // to this file when we remove the legacy graph. This logic allows us to support bi-directional angles
35038
- // for the new (non-reflexive) Mafs graphs, while maintaining the same scoring behaviour as the legacy graph.
35039
- // Once the legacy graph is removed, we should move this logic directly into the validate function below.
35040
- class LegacyInteractiveGraph extends React.Component {
35041
- constructor(...args) {
35042
- super(...args);
35043
- this.angle = void 0;
35044
- this.circle = void 0;
35045
- this.graphie = void 0;
35046
- this.horizHairline = void 0;
35047
- this.line = void 0;
35048
- this.lines = void 0;
35049
- this.parabola = void 0;
35050
- this.pointA = void 0;
35051
- this.pointB = void 0;
35052
- this.pointC = void 0;
35053
- // @ts-expect-error - TS2564 - Property 'points' has no initializer and is not definitely assigned in the constructor.
35054
- this.points = void 0;
35055
- this.polygon = void 0;
35056
- // @ts-expect-error - TS2564 - Property 'shouldResetGraphie' has no initializer and is not definitely assigned in the constructor.
35057
- this.shouldResetGraphie = void 0;
35058
- this.sinusoid = void 0;
35059
- this.trashCan = void 0;
35060
- this.vertHairline = void 0;
35061
- this.state = {
35062
- shouldShowInstructions: _getShouldShowInstructions(this.props)
35063
- };
35064
- this._getShouldShowInstructions = props => {
35065
- props = props || this.props;
35066
- return this.isClickToAddPoints(props) && (
35067
- // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
35068
- props.graph.coords == null || props.graph.coords.length === 0);
35069
- };
35070
- this.setGraphie = newGraphie => {
35071
- this.graphie = newGraphie;
35072
- this.setupGraphie();
35073
- };
35074
- this.handleAddPointsMouseDown = coord => {
35075
- // This function should only be called when this.isClickToAddPoints()
35076
- // is true
35077
- if (!this.isClickToAddPoints()) {
35078
- throw new PerseusError("handleAddPointsClick should not be registered" + "when isClickToAddPoints() is false", Errors.NotAllowed);
35079
- }
35080
- if (!this.isCoordInTrash(coord)) {
35081
- let point;
35082
- if (this.props.graph.type === "point") {
35083
- point = this.createPointForPointsType(coord, this.points.length);
35084
- if (!point.constrain()) {
35085
- point.remove();
35086
- return;
35087
- }
35088
- this.points.push(point);
35089
-
35090
- // interactive2 allows us to grab the point
35091
- const idx = this.points.length - 1;
35092
- this.points[idx].grab(coord);
35093
- this.updateCoordsFromPoints();
35094
- } else if (this.props.graph.type === "polygon") {
35095
- var _this$polygon;
35096
- if ((_this$polygon = this.polygon) != null && _this$polygon.closed()) {
35097
- return;
35098
- }
35099
- point = this.createPointForPolygonType(coord, this.points.length);
35100
- this.points.push(point);
35101
- const idx = this.points.length - 1;
35102
- this.points[idx].grab(coord);
35103
-
35104
- // We don't call updateCoordsFromPoints for
35105
- // polygons, since the polygon won't be
35106
- // closed yet.
35107
- this.updatePolygon();
35108
- }
35109
- this.setState({
35110
- shouldShowInstructions: false
35111
- });
35112
- }
35113
- };
35114
- this.resetGraphie = () => {
35115
- var _this$refs$graph;
35116
- this.shouldResetGraphie = false;
35117
- this.parabola = null;
35118
- this.sinusoid = null;
35119
- // eslint-disable-next-line react/no-string-refs
35120
- // @ts-expect-error - TS2339 - Property 'reset' does not exist on type 'ReactInstance'.
35121
- (_this$refs$graph = this.refs.graph) == null || _this$refs$graph.reset();
35122
- };
35123
- this.setupGraphie = () => {
35124
- this.setTrashCanVisibility(0);
35125
- if (this.isClickToAddPoints()) {
35126
- this.setTrashCanVisibility(0.5);
35127
- }
35128
- if (this.props.apiOptions.isMobile) {
35129
- var _this$horizHairline, _this$vertHairline;
35130
- this.horizHairline = new WrappedLine(this.graphie, [0, 0], [0, 0], {
35131
- normalStyle: {
35132
- strokeWidth: 1
35133
- }
35134
- });
35135
- this.horizHairline.attr({
35136
- stroke: KhanColors.INTERACTIVE
35137
- });
35138
- (_this$horizHairline = this.horizHairline) == null || _this$horizHairline.hide();
35139
- this.vertHairline = new WrappedLine(this.graphie, [0, 0], [0, 0], {
35140
- normalStyle: {
35141
- strokeWidth: 1
35142
- }
35143
- });
35144
- this.vertHairline.attr({
35145
- stroke: KhanColors.INTERACTIVE
35146
- });
35147
- (_this$vertHairline = this.vertHairline) == null || _this$vertHairline.hide();
35148
- }
35149
- const type = this.props.graph.type;
35150
- this["add" + capitalize(type) + "Controls"]();
35151
- };
35152
- this.showHairlines = point => {
35153
- if (this.props.apiOptions.isMobile && this.props.markings !== "none") {
35154
- var _this$horizHairline2, _this$horizHairline3, _this$vertHairline2, _this$vertHairline3;
35155
- // Hairlines are already initialized when the graph is loaded, so
35156
- // here we just move them to the updated location and make them
35157
- // visible.
35158
- (_this$horizHairline2 = this.horizHairline) == null || _this$horizHairline2.moveTo([this.props.range[0][0], point[1]], [this.props.range[0][1], point[1]]);
35159
- (_this$horizHairline3 = this.horizHairline) == null || _this$horizHairline3.show();
35160
- (_this$vertHairline2 = this.vertHairline) == null || _this$vertHairline2.moveTo([point[0], this.props.range[1][0]], [point[0], this.props.range[1][1]]);
35161
- (_this$vertHairline3 = this.vertHairline) == null || _this$vertHairline3.show();
35162
- }
35163
- };
35164
- this.hideHairlines = () => {
35165
- if (this.props.apiOptions.isMobile) {
35166
- var _this$horizHairline4, _this$vertHairline4;
35167
- (_this$horizHairline4 = this.horizHairline) == null || _this$horizHairline4.hide();
35168
- (_this$vertHairline4 = this.vertHairline) == null || _this$vertHairline4.hide();
35169
- }
35170
- };
35171
- this.setTrashCanVisibility = opacity => {
35172
- const graphie = this.graphie;
35173
- if (number.equal(opacity, 0)) {
35174
- if (this.trashCan) {
35175
- this.trashCan.remove();
35176
- this.trashCan = null;
35177
- }
35178
- } else if (!this.props.apiOptions.isMobile) {
35179
- var _this$trashCan;
35180
- // Only if trash tooltips are not being used, we initialize the old
35181
- // trash can area.
35182
- if (!this.trashCan) {
35183
- var _graphie$raphael;
35184
- this.trashCan = graphie == null || (_graphie$raphael = graphie.raphael) == null ? void 0 : _graphie$raphael.image(TRASH_ICON_URI, graphie.xpixels - 40, graphie.ypixels - 40, 40, 40);
35185
- }
35186
- (_this$trashCan = this.trashCan) == null || _this$trashCan.attr({
35187
- opacity: opacity
35188
- });
35189
- }
35190
- };
35191
- this.isClickToAddPoints = props => {
35192
- props = props || this.props;
35193
- return _isClickToAddPoints(props);
35194
- };
35195
- this._lineStroke = () => {
35196
- // This should probably use: this.props.apiOptions.isMobile
35197
- // @ts-expect-error - TS2339 - Property 'isMobile' does not exist on type 'Readonly<Props> & Readonly<{ children?: ReactNode; }>'.
35198
- return this.props.isMobile ? {
35199
- "stroke-width": 3
35200
- } : {};
35201
- };
35202
- this.addLine = type => {
35203
- const self = this;
35204
- const graphie = self.graphie;
35205
- const coords = InteractiveGraph.getLineCoords(self.props.graph, self.props);
35206
- const points = self.points = _.map(coords, coord => {
35207
- return Interactive2.addMaybeMobileMovablePoint(this, {
35208
- coord: coord,
35209
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap()],
35210
- onMove: () => {
35211
- const graph = _.extend({}, self.props.graph, {
35212
- coords: _.invoke(points, "coord")
35213
- });
35214
- self.onChange({
35215
- graph: graph
35216
- });
35217
- }
35218
- });
35219
- });
35220
- const lineConfig = {
35221
- points: points,
35222
- static: true,
35223
- normalStyle: _extends({
35224
- stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTIVE
35225
- }, this._lineStroke())
35226
- };
35227
- if (type === "line") {
35228
- // @ts-expect-error - TS2339 - Property 'extendLine' does not exist on type '{ readonly points: any[]; readonly static: true; readonly normalStyle: { readonly "stroke-width"?: number | undefined; readonly stroke: "#71B307" | "#63D9EA"; }; }'.
35229
- lineConfig.extendLine = true;
35230
- } else if (type === "ray") {
35231
- // @ts-expect-error - TS2339 - Property 'extendRay' does not exist on type '{ readonly points: any[]; readonly static: true; readonly normalStyle: { readonly "stroke-width"?: number | undefined; readonly stroke: "#71B307" | "#63D9EA"; }; }'.
35232
- lineConfig.extendRay = true;
35233
- }
35234
- self.line = Interactive2.addMovableLine(graphie, lineConfig);
35235
-
35236
- // A and B can't be in the same place
35237
- points[0].listen("constraints", "isLine", coord => {
35238
- return !point.equal(coord, points[1].coord());
35239
- });
35240
- points[1].listen("constraints", "isLine", coord => {
35241
- return !point.equal(coord, points[0].coord());
35242
- });
35243
- };
35244
- this.removeLine = () => {
35245
- var _this$line;
35246
- _.invoke(this.points, "remove");
35247
- (_this$line = this.line) == null || _this$line.remove();
35248
- };
35249
- this.addNoneControls = () => {};
35250
- this.removeNoneControls = () => {};
35251
- this.addLinearControls = () => {
35252
- this.addLine("line");
35253
- };
35254
- this.removeLinearControls = () => {
35255
- this.removeLine();
35256
- };
35257
- this.addQuadraticControls = () => {
35258
- if (this.props.graph.type !== "quadratic") {
35259
- throw makeInvalidTypeError("addQuadraticControls", "quadratic");
35260
- }
35261
- let coords = this.props.graph.coords;
35262
- if (!coords) {
35263
- coords = InteractiveGraph.defaultQuadraticCoords(this.props);
35264
- }
35265
- const onMoveHandler = () => {
35266
- const graph = _.extend({}, this.props.graph, {
35267
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2531 - Object is possibly 'null'. | TS2531 - Object is possibly 'null'.
35268
- coords: [pointA.coord(), pointB.coord(), pointC.coord()]
35269
- });
35270
- this.onChange({
35271
- graph
35272
- });
35273
- this.updateQuadratic();
35274
- };
35275
-
35276
- // NOTE(jeresig): This code attempts to access the variable while it's
35277
- // still being defined, it should be refactored! The callbacks appear
35278
- // to be executed synchronously, which causes this issue.
35279
- let pointA = null;
35280
- pointA = this.pointA = Interactive2.addMaybeMobileMovablePoint(this, {
35281
- coord: coords[0],
35282
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
35283
- return !pointA ||
35284
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35285
- coord[0] !== pointB.coord()[0] &&
35286
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35287
- coord[0] !== pointC.coord()[0];
35288
- }],
35289
- onMove: onMoveHandler
35290
- });
35291
-
35292
- // NOTE(jeresig): This code attempts to access the variable while it's
35293
- // still being defined, it should be refactored! The callbacks appear
35294
- // to be executed synchronously, which causes this issue.
35295
- let pointB = null;
35296
- pointB = this.pointB = Interactive2.addMaybeMobileMovablePoint(this, {
35297
- coord: coords[1],
35298
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
35299
- return !pointB ||
35300
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35301
- coord[0] !== pointA.coord()[0] &&
35302
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35303
- coord[0] !== pointC.coord()[0];
35304
- }],
35305
- onMove: onMoveHandler
35306
- });
35307
-
35308
- // NOTE(jeresig): This code attempts to access the variable while it's
35309
- // still being defined, it should be refactored! The callbacks appear
35310
- // to be executed synchronously, which causes this issue.
35311
- let pointC = null;
35312
- pointC = this.pointC = Interactive2.addMaybeMobileMovablePoint(this, {
35313
- coord: coords[2],
35314
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
35315
- return !pointC ||
35316
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35317
- coord[0] !== pointA.coord()[0] &&
35318
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35319
- coord[0] !== pointB.coord()[0];
35320
- }],
35321
- onMove: onMoveHandler
35322
- });
35323
- this.updateQuadratic();
35324
- };
35325
- this.updateQuadratic = () => {
35326
- const coeffs = InteractiveGraph.getCurrentQuadraticCoefficients(this.props);
35327
- if (!coeffs) {
35328
- return;
35329
- }
35330
-
35331
- // Extract coefficients the parabola
35332
- const a = coeffs[0];
35333
- const b = coeffs[1];
35334
- const c = coeffs[2];
35335
-
35336
- // Plot and style
35337
- if (this.parabola) {
35338
- var _this$graphie, _this$parabola;
35339
- const path = (_this$graphie = this.graphie) == null ? void 0 : _this$graphie.svgParabolaPath(a, b, c);
35340
- (_this$parabola = this.parabola) == null || _this$parabola.attr({
35341
- path: path
35342
- });
35343
- } else {
35344
- var _this$graphie2, _this$parabola2, _this$parabola3;
35345
- this.parabola = (_this$graphie2 = this.graphie) == null ? void 0 : _this$graphie2.parabola(a, b, c);
35346
- (_this$parabola2 = this.parabola) == null || _this$parabola2.attr(_extends({
35347
- stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.DYNAMIC
35348
- }, this._lineStroke()));
35349
- (_this$parabola3 = this.parabola) == null || _this$parabola3.toBack();
35350
- }
35351
- };
35352
- this.removeQuadraticControls = () => {
35353
- var _this$pointA, _this$pointB, _this$pointC;
35354
- (_this$pointA = this.pointA) == null || _this$pointA.remove();
35355
- (_this$pointB = this.pointB) == null || _this$pointB.remove();
35356
- (_this$pointC = this.pointC) == null || _this$pointC.remove();
35357
- if (this.parabola) {
35358
- this.parabola.remove();
35359
- this.parabola = null;
35360
- }
35361
- };
35362
- this.addSinusoidControls = () => {
35363
- if (this.props.graph.type !== "sinusoid") {
35364
- throw makeInvalidTypeError("addSinusoidControls", "sinusoid");
35365
- }
35366
- let coords = this.props.graph.coords;
35367
- if (!coords) {
35368
- coords = InteractiveGraph.defaultSinusoidCoords(this.props);
35369
- }
35370
- const onMoveHandler = () => {
35371
- const graph = _.extend({}, this.props.graph, {
35372
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35373
- coords: [pointA.coord(), pointB.coord()]
35374
- });
35375
- this.onChange({
35376
- graph: graph
35377
- });
35378
- this.updateSinusoid();
35379
- };
35380
-
35381
- // NOTE(jeresig): This code attempts to access the variable while it's
35382
- // still being defined, it should be refactored! The callbacks appear
35383
- // to be executed synchronously, which causes this issue.
35384
- let pointA = null;
35385
- pointA = this.pointA = Interactive2.addMaybeMobileMovablePoint(this, {
35386
- coord: coords[0],
35387
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
35388
- return !pointA || coord[0] !== pointB.coord()[0];
35389
- }],
35390
- onMove: onMoveHandler
35391
- });
35392
- const pointB = this.pointB = Interactive2.addMaybeMobileMovablePoint(this, {
35393
- coord: coords[1],
35394
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
35395
- // @ts-expect-error - TS2339 - Property 'coord' does not exist on type 'never'.
35396
- return !pointA || coord[0] !== pointA.coord()[0];
35397
- }],
35398
- onMove: onMoveHandler
35399
- });
35400
- this.updateSinusoid();
35401
- };
35402
- this.updateSinusoid = () => {
35403
- const coeffs = InteractiveGraph.getCurrentSinusoidCoefficients(this.props);
35404
- if (!coeffs) {
35405
- return;
35406
- }
35407
- const a = coeffs[0];
35408
- const b = coeffs[1];
35409
- const c = coeffs[2];
35410
- const d = coeffs[3];
35411
-
35412
- // Plot and style
35413
- if (this.sinusoid) {
35414
- var _this$graphie3, _this$sinusoid;
35415
- const path = (_this$graphie3 = this.graphie) == null ? void 0 : _this$graphie3.svgSinusoidPath(a, b, c, d);
35416
- (_this$sinusoid = this.sinusoid) == null || _this$sinusoid.attr({
35417
- path: path
35418
- });
35419
- } else {
35420
- var _this$graphie4, _this$sinusoid2, _this$sinusoid3;
35421
- this.sinusoid = (_this$graphie4 = this.graphie) == null ? void 0 : _this$graphie4.sinusoid(a, b, c, d);
35422
- (_this$sinusoid2 = this.sinusoid) == null || _this$sinusoid2.attr(_extends({
35423
- stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.DYNAMIC
35424
- }, this._lineStroke()));
35425
- (_this$sinusoid3 = this.sinusoid) == null || _this$sinusoid3.toBack();
35426
- }
35427
- };
35428
- this.removeSinusoidControls = () => {
35429
- var _this$pointA2, _this$pointB2;
35430
- (_this$pointA2 = this.pointA) == null || _this$pointA2.remove();
35431
- (_this$pointB2 = this.pointB) == null || _this$pointB2.remove();
35432
- if (this.sinusoid) {
35433
- this.sinusoid.remove();
35434
- this.sinusoid = null;
35435
- }
35436
- };
35437
- this.addCircleControls = () => {
35438
- const graphie = this.graphie;
35439
- const minSnap = _.min(graphie.snap);
35440
- const circle = this.circle = graphie == null ? void 0 : graphie.addCircleGraph({
35441
- // @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'.
35442
- center: this.props.graph.center || [0, 0],
35443
- // @ts-expect-error - TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
35444
- radius: this.props.graph.radius || _.min(this.props.step),
35445
- snapX: graphie.snap[0],
35446
- snapY: graphie.snap[1],
35447
- minRadius: minSnap,
35448
- snapRadius: minSnap
35449
- });
35450
- $(circle).on("move", () => {
35451
- const graph = _.extend({}, this.props.graph, {
35452
- center: circle.center,
35453
- radius: circle.radius
35454
- });
35455
- this.onChange({
35456
- graph: graph
35457
- });
35458
- });
35459
- };
35460
- this.removeCircleControls = () => {
35461
- var _this$circle;
35462
- (_this$circle = this.circle) == null || _this$circle.remove();
35463
- };
35464
- this.addLinearSystemControls = () => {
35465
- const graphie = this.graphie;
35466
- const coords = InteractiveGraph.getLinearSystemCoords(this.props.graph, this.props);
35467
- const segmentColors = [KhanColors.INTERACTIVE, KhanColors.GREEN];
35468
- const points = this.points = _.map(coords, (segmentCoords, segmentIndex) => {
35469
- // NOTE(jeresig): This code attempts to access the variable
35470
- // while it's still being defined, it should be refactored!
35471
- // The callbacks appear to be executed synchronously, which
35472
- // causes this issue.
35473
- const segmentPoints = [];
35474
- for (let i = 0; i < segmentCoords.length; i += 1) {
35475
- const coord = segmentCoords[i];
35476
- segmentPoints.push(Interactive2.addMaybeMobileMovablePoint(this, {
35477
- coord: coord,
35478
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
35479
- const otherSegment = segmentPoints[1 - i];
35480
- if (!otherSegment) {
35481
- // segment hasn't been defined yet
35482
- // because we're still creating them
35483
- return;
35484
- }
35485
- return !point.equal(coord, otherSegment.coord());
35486
- }],
35487
- onMove: () => {
35488
- const graph = _.extend({}, this.props.graph, {
35489
- coords: _.map(this.points, segment => _.invoke(segment, "coord"))
35490
- });
35491
- this.onChange({
35492
- graph: graph
35493
- });
35494
- },
35495
- normalStyle: {
35496
- fill: segmentColors[segmentIndex]
35497
- },
35498
- highlightStyle: {
35499
- fill: segmentColors[segmentIndex]
35500
- }
35501
- }));
35502
- }
35503
- return segmentPoints;
35504
- });
35505
- this.lines = _.map(points, (segmentPoints, segmentIndex) => {
35506
- return Interactive2.addMovableLine(graphie, {
35507
- points: segmentPoints,
35508
- static: true,
35509
- extendLine: true,
35510
- normalStyle: {
35511
- stroke: segmentColors[segmentIndex]
35512
- }
35513
- });
35514
- });
35515
- };
35516
- this.removeLinearSystemControls = () => {
35517
- // @ts-expect-error - TS2345 - Argument of type 'readonly (readonly Coord[])[] | null | undefined' is not assignable to parameter of type 'Collection<any>'.
35518
- _.invoke(this.lines, "remove");
35519
- _.map(this.points, segment => _.invoke(segment, "remove"));
35520
- };
35521
- this.isCoordInTrash = coord => {
35522
- if (this.props.apiOptions.isMobile) {
35523
- return false;
35524
- }
35525
- const graphie = this.graphie;
35526
- const screenPoint = graphie == null ? void 0 : graphie.scalePoint(coord);
35527
- return screenPoint[0] >= graphie.xpixels - 40 && screenPoint[1] >= graphie.ypixels - 40;
35528
- };
35529
- this.createPointForPointsType = (coord, i) => {
35530
- const self = this;
35531
- const remove = () => {
35532
- self.points = _.filter(self.points, function (pt) {
35533
- return pt !== point$1;
35534
- });
35535
- // update the correct answer box
35536
- self.updateCoordsFromPoints();
35537
-
35538
- // remove this movablePoint from graphie.
35539
- // we wait to do this until we're not inside of
35540
- // said point's onMoveEnd method so its state is
35541
- // consistent throughout this method call
35542
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
35543
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
35544
- setTimeout(point$1.remove.bind(point$1), 0); // eslint-disable-line no-restricted-syntax
35545
- };
35546
-
35547
- // NOTE(jeresig): This code attempts to access the variable while it's
35548
- // still being defined, it should be refactored! The callbacks appear
35549
- // to be executed synchronously, which causes this issue.
35550
- let point$1 = null;
35551
- point$1 = Interactive2.addMaybeMobileMovablePoint(this, _extends({
35552
- coord: coord,
35553
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), function (coord) {
35554
- // TODO(jack): There ought to be a
35555
- // MovablePoint.constraints.avoid
35556
- // default that lets you do things like this
35557
- return _.all(self.points, function (pt) {
35558
- return point$1 === pt || !point.equal(coord, pt.coord());
35559
- });
35560
- }],
35561
- onMoveStart: function () {
35562
- if (self.isClickToAddPoints()) {
35563
- self.setTrashCanVisibility(1);
35564
- }
35565
- },
35566
- onMove: self.updateCoordsFromPoints,
35567
- onMoveEnd: function (coord) {
35568
- if (self.isClickToAddPoints()) {
35569
- if (self.isCoordInTrash(coord)) {
35570
- remove();
35571
- }
35572
- // In case we mouseup'd off the graphie and that
35573
- // stopped the move (in which case, we might not
35574
- // be in isCoordInTrash()
35575
- self.setTrashCanVisibility(0.5);
35576
- }
35577
- }
35578
- }, this.props.apiOptions.isMobile && self.isClickToAddPoints() ? {
35579
- onRemove: remove
35580
- } : {}));
35581
- return point$1;
35582
- };
35583
- this.removePoint = point => {
35584
- let index = null;
35585
- this.points = _.filter(this.points, function (pt, i) {
35586
- if (pt === point) {
35587
- // @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
35588
- index = i;
35589
- return false;
35590
- }
35591
- return true;
35592
- });
35593
- return index;
35594
- };
35595
- this.createPointForPolygonType = (coord, i) => {
35596
- if (this.props.graph.type !== "polygon") {
35597
- throw makeInvalidTypeError("createPointForPolygonType", "polygon");
35598
- }
35599
-
35600
- // TODO(alex): check against "grid" instead, use constants
35601
- const snapToGrid = !_.contains(["angles", "sides"], this.props.graph.snapTo);
35602
-
35603
- // Index relative to current point -> absolute index
35604
- // NOTE: This does not work when isClickToAddPoints() == true,
35605
- // as `i` can be changed by dragging a point to the trash
35606
- // Currently this function is only called when !isClickToAddPoints()
35607
- const rel = j => {
35608
- return (i + j + this.points.length) % this.points.length;
35609
- };
35610
- const remove = () => {
35611
- var _this$polygon2;
35612
- // remove this point from points
35613
- const index = this.removePoint(point$1);
35614
- if ((_this$polygon2 = this.polygon) != null && _this$polygon2.closed()) {
35615
- var _this$polygon3;
35616
- // We should be checking if this.points is defined before rotating them.
35617
- // @ts-expect-error - TS4104 - The type 'readonly any[]' is 'readonly' and cannot be assigned to the mutable type 'any[]'. | TS2345 - Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | undefined'.
35618
- this.points = rotate(this.points, index);
35619
- (_this$polygon3 = this.polygon) == null || _this$polygon3.update({
35620
- closed: false
35621
- });
35622
- }
35623
- this.updatePolygon();
35624
- // the polygon is now unclosed, so we need to
35625
- // remove any points props
35626
- this.clearCoords();
35627
-
35628
- // remove this movablePoint from graphie.
35629
- // wait to do this until we're not inside of
35630
- // said point's onMoveEnd method so state is
35631
- // consistent throughout the method call
35632
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
35633
- // eslint-disable-next-line no-restricted-syntax
35634
- setTimeout(point$1.remove.bind(point$1), 0);
35635
- };
35636
- const setInitialMove = debounce$2((point, bool) => {
35637
- point.state.isInitialMove = bool;
35638
- }, 1000);
35639
- const onMoveEndHandler = coord => {
35640
- // If the user clicked and dragged a point over endpoint,
35641
- // join the them
35642
- const overlappedPoints = this.points.length > 1 && (point$1 === this.points[0] && point.equal(coord, _.last(this.points).coord()) || point$1 === _.last(this.points) && point.equal(coord, this.points[0].coord()));
35643
- if (this.isClickToAddPoints()) {
35644
- if (this.isCoordInTrash(coord)) {
35645
- remove();
35646
- } else if (overlappedPoints) {
35647
- const pointToRemove = this.points.pop();
35648
- if (this.points.length > 2) {
35649
- var _this$polygon4;
35650
- (_this$polygon4 = this.polygon) == null || _this$polygon4.update({
35651
- closed: true
35652
- });
35653
- this.updateCoordsFromPoints();
35654
- } else {
35655
- var _this$polygon5;
35656
- (_this$polygon5 = this.polygon) == null || _this$polygon5.update({
35657
- closed: false
35658
- });
35659
- this.clearCoords();
35660
- }
35661
- this.updatePolygon();
35662
- // remove this movablePoint from graphie.
35663
- // wait to do this until we're not inside of
35664
- // said point's onMoveEnd method so state is
35665
- // consistent throughout the method call
35666
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
35667
- // eslint-disable-next-line no-restricted-syntax
35668
- setTimeout(pointToRemove.remove.bind(pointToRemove), 0);
35669
- } else {
35670
- // If the user clicked and dragged a point over any other
35671
- // existing point, fix shape
35672
- const shouldRemove = _.any(this.points, function (pt) {
35673
- return pt !== point$1 && point.equal(pt.coord(), coord);
35674
- });
35675
- if (shouldRemove) {
35676
- var _this$polygon7;
35677
- this.removePoint(point$1);
35678
- if (this.points.length < 3) {
35679
- var _this$polygon6;
35680
- (_this$polygon6 = this.polygon) == null || _this$polygon6.update({
35681
- closed: false
35682
- });
35683
- this.clearCoords();
35684
- } else if ((_this$polygon7 = this.polygon) != null && _this$polygon7.closed()) {
35685
- this.updateCoordsFromPoints();
35686
- }
35687
- this.updatePolygon();
35688
- // remove this movablePoint from graphie.
35689
- // wait to do this until we're not inside
35690
- // said point's onMoveEnd method so state
35691
- // is consistent throughout the method call
35692
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
35693
- // eslint-disable-next-line no-restricted-syntax
35694
- setTimeout(point$1.remove.bind(point$1), 0);
35695
- } else {
35696
- var _this$polygon8;
35697
- // If this was
35698
- // * a click on the first or last point
35699
- // * and not a deletion
35700
- // * and not a drag,
35701
- // * and not a creation of a new point
35702
- // (see !point.state.isInitialMove, below),
35703
- // * and our polygon is not closed,
35704
- // * and we can close it (we need at least the same
35705
- // number of points as coordinates asked for in the
35706
- // question),
35707
- // then close it
35708
- const isFirstOrLastPoint = point$1 === _.head(this.points) || point$1 === _.last(this.points);
35709
- const pointNotMoved = !point$1.hasMoved();
35710
- // NOTE (jsatk): Currently different on mobile versus
35711
- // destkop
35712
- const notANewPoint = !point$1.state.isInitialMove;
35713
- const polygonNotClosed = !((_this$polygon8 = this.polygon) != null && _this$polygon8.closed());
35714
- const allPointsPlotted = this.points.length > 2;
35715
- const canCloseUnclosedPolygon = isFirstOrLastPoint && pointNotMoved && notANewPoint && polygonNotClosed && allPointsPlotted;
35716
- if (canCloseUnclosedPolygon) {
35717
- var _this$polygon9;
35718
- (_this$polygon9 = this.polygon) == null || _this$polygon9.update({
35719
- closed: true
35720
- });
35721
- this.updatePolygon();
35722
-
35723
- // We finally have a closed polygon, so save our
35724
- // points to props
35725
- this.updateCoordsFromPoints();
35726
- }
35727
- }
35728
- }
35729
-
35730
- // In case we mouseup'd off the graphie and that
35731
- // stopped the move
35732
- this.setTrashCanVisibility(0.5);
35733
- }
35734
- setInitialMove(point$1, false);
35735
- };
35736
- const graphConstraint = coord => {
35737
- // These constraints are all relative to the other points, so if
35738
- // we're creating the initial points and haven't added any others
35739
- // to the graph, we can't enforce them.
35740
- if (this.points == null || this.points.length === 0) {
35741
- return true;
35742
- }
35743
- const coords = _.invoke(this.points, "coord");
35744
- coords[i] = coord;
35745
-
35746
- // Check for invalid positioning, but only if we aren't adding
35747
- // points one click at a time, since those added points could
35748
- // have already violated these constraints
35749
- if (!this.isClickToAddPoints()) {
35750
- // Polygons can't have consecutive collinear points
35751
- if (collinear(coords[rel(-2)], coords[rel(-1)], coords[i]) || collinear(coords[rel(-1)], coords[i], coords[rel(1)]) || collinear(coords[i], coords[rel(1)], coords[rel(2)])) {
35752
- return false;
35753
- }
35754
- const segments = _.zip(coords, rotate(coords));
35755
- if (this.points.length > 3) {
35756
- // Constrain to simple (non self-intersecting) polygon by
35757
- // testing whether adjacent segments intersect any others
35758
- for (let j = -1; j <= 0; j++) {
35759
- const segment = segments[rel(j)];
35760
- const others = _.without(segments, segment, segments[rel(j - 1)], segments[rel(j + 1)]);
35761
- for (let k = 0; k < others.length; k++) {
35762
- const other = others[k];
35763
- // @ts-expect-error - TS2345 - Argument of type 'any[]' is not assignable to parameter of type 'Line'.
35764
- if (intersects(segment, other)) {
35765
- return false;
35766
- }
35767
- }
35768
- }
35769
- }
35770
- }
35771
- if (
35772
- // @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
35773
- this.props.graph.snapTo === "angles" && this.points.length > 2) {
35774
- var _this$graphie5;
35775
- // Snap to whole degree interior angles
35776
-
35777
- const angles = _.map(angleMeasures(coords), function (rad) {
35778
- return rad * 180 / Math.PI;
35779
- });
35780
- _.each([-1, 1], function (j) {
35781
- angles[rel(j)] = Math.round(angles[rel(j)]);
35782
- });
35783
- const getAngle = function getAngle(a, vertex, b) {
35784
- const angle = GraphUtils.findAngleDeprecated(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
35785
- return (angle + 360) % 360;
35786
- };
35787
- const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
35788
- innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);
35789
-
35790
- // Avoid degenerate triangles
35791
- if (_.any(innerAngles, function (angle) {
35792
- return leq(angle, 1);
35793
- })) {
35794
- return false;
35795
- }
35796
- const knownSide = magnitude(
35797
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
35798
- vector(coords[rel(-1)], coords[rel(1)]));
35799
- const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
35800
-
35801
- // Solve for side by using the law of sines
35802
- const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
35803
- const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
35804
- const offset = polar$2(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
35805
- return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
35806
- }
35807
- // @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
35808
- if (this.props.graph.snapTo === "sides" && this.points.length > 1) {
35809
- var _this$graphie6;
35810
- // Snap to whole unit side measures
35811
-
35812
- const sides = _.map([[coords[rel(-1)], coords[i]], [coords[i], coords[rel(1)]], [coords[rel(-1)], coords[rel(1)]]], function (coords) {
35813
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
35814
- return magnitude(vector(...coords));
35815
- });
35816
- _.each([0, 1], function (j) {
35817
- sides[j] = Math.round(sides[j]);
35818
- });
35819
-
35820
- // Avoid degenerate triangles
35821
- if (leq(sides[1] + sides[2], sides[0]) || leq(sides[0] + sides[2], sides[1]) || leq(sides[0] + sides[1], sides[2])) {
35822
- return false;
35823
- }
35824
-
35825
- // Solve for angle by using the law of cosines
35826
- const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
35827
- const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
35828
- const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
35829
- const offset = polar$2(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
35830
- return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
35831
- }
35832
- // Snap to grid (already done)
35833
- return true;
35834
- };
35835
- const point$1 = Interactive2.addMaybeMobileMovablePoint(this, _extends({
35836
- coord: coord,
35837
- constraints: [Interactive2.MovablePoint.constraints.bound(), snapToGrid ? Interactive2.MovablePoint.constraints.snap() : null, graphConstraint],
35838
- onMoveStart: () => {
35839
- if (this.isClickToAddPoints()) {
35840
- this.setTrashCanVisibility(1);
35841
- }
35842
- },
35843
- onMove: () => {
35844
- var _this$polygon10;
35845
- if ((_this$polygon10 = this.polygon) != null && _this$polygon10.closed()) {
35846
- this.updateCoordsFromPoints();
35847
- }
35848
- },
35849
- onMoveEnd: onMoveEndHandler
35850
- }, this.props.apiOptions.isMobile && this.isClickToAddPoints() ? {
35851
- onRemove: remove
35852
- } : {}));
35853
- point$1.state.isInitialMove = true;
35854
- return point$1;
35855
- };
35856
- this.updateCoordsFromPoints = () => {
35857
- const graph = _.extend({}, this.props.graph, {
35858
- // Handle old movable points with .coord, or
35859
- // Interactive2.MovablePoint's with .coord()
35860
- coords: _.map(this.points, function (point) {
35861
- return _.result(point, "coord");
35862
- })
35863
- });
35864
- this.onChange({
35865
- graph: graph
35866
- });
35867
- };
35868
- this.clearCoords = () => {
35869
- const graph = _.extend({}, this.props.graph, {
35870
- coords: null
35871
- });
35872
- this.onChange({
35873
- graph: graph
35874
- });
35875
- };
35876
- this.onChange = data => {
35877
- this.props.onChange(data);
35878
- this.props.trackInteraction();
35879
- };
35880
- this.addPointControls = () => {
35881
- if (this.props.graph.type !== "point") {
35882
- throw makeInvalidTypeError("addPointControls", "point");
35883
- }
35884
- const coords = InteractiveGraph.getPointCoords(this.props.graph, this.props);
35885
- // Clear out our old points so that newly added points don't
35886
- // "collide" with them and reposition when being added
35887
- // Without this, when added, each point checks whether it is on top
35888
- // of a point in this.points, which (a) shouldn't matter since
35889
- // we're clearing out this.points anyways, and (b) can cause problems
35890
- // if each of this.points is a MovablePoint instead of an
35891
- // Interactive2.MovablePoint, since one has a .coord and the other
35892
- // has .coord()
35893
- // TODO(jack): Figure out a better way to do this
35894
- this.points = [];
35895
- this.points = _.map(coords, this.createPointForPointsType, this);
35896
- };
35897
- this.removePointControls = () => {
35898
- _.invoke(this.points, "remove");
35899
- };
35900
- this.addSegmentControls = () => {
35901
- if (this.props.graph.type !== "segment") {
35902
- throw makeInvalidTypeError("addSegmentControls", "segment");
35903
- }
35904
- const self = this;
35905
- const graphie = this.graphie;
35906
- const coords = InteractiveGraph.getSegmentCoords(this.props.graph, this.props);
35907
- const createPoint = options => Interactive2.addMaybeMobileMovablePoint(this, options);
35908
- this.points = [];
35909
- this.lines = _.map(coords, function (segment, i) {
35910
- const updateCoordProps = function updateCoordProps() {
35911
- const graph = _.extend({}, self.props.graph, {
35912
- // @ts-expect-error - TS2345 - Argument of type 'readonly (readonly Coord[])[] | null | undefined' is not assignable to parameter of type 'Collection<any>'.
35913
- coords: _.invoke(self.lines, "coords")
35914
- });
35915
- self.onChange({
35916
- graph: graph
35917
- });
35918
- };
35919
-
35920
- // NOTE(jeresig): This code attempts to access the variable
35921
- // while it's still being defined, it should be refactored!
35922
- // The callbacks appear to be executed synchronously, which
35923
- // causes this issue.
35924
- const points = [];
35925
- for (let _i = 0; _i < segment.length; _i += 1) {
35926
- const coord = segment[_i];
35927
- points.push(createPoint({
35928
- coord: coord,
35929
- constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
35930
- const otherPoint = points[1 - _i];
35931
- if (!otherPoint) {
35932
- // point hasn't been defined yet
35933
- // because we're still creating them
35934
- // We return false as the points aren't
35935
- // going to be in the same position at
35936
- // this point.
35937
- return false;
35938
- }
35939
- return !point.equal(coord, otherPoint.coord());
35940
- }],
35941
- onMove: updateCoordProps
35942
- }));
35943
- }
35944
- self.points = self.points.concat(points);
35945
- const line = Interactive2.addMovableLine(graphie, {
35946
- points: points,
35947
- static: false,
35948
- constraints: [
35949
- // @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => void'.
35950
- Interactive2.MovableLine.constraints.bound(),
35951
- // @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => void'.
35952
- Interactive2.MovableLine.constraints.snap()],
35953
- onMove: [
35954
- // @ts-expect-error - TS2339 - Property 'onMove' does not exist on type '(graphie: any, movable: any, options: any) => void'.
35955
- Interactive2.MovableLine.onMove.updatePoints, updateCoordProps],
35956
- normalStyle: _extends({
35957
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
35958
- stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTIVE
35959
- }, this._lineStroke()),
35960
- highlightStyle: _extends({
35961
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
35962
- stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTING
35963
- }, this._lineStroke())
35964
- });
35965
- _.invoke(points, "toFront");
35966
- return line;
35967
- }, this);
35968
- };
35969
- this.removeSegmentControls = () => {
35970
- _.invoke(this.points, "remove");
35971
- // @ts-expect-error - TS2345 - Argument of type 'readonly (readonly Coord[])[] | null | undefined' is not assignable to parameter of type 'Collection<any>'.
35972
- _.invoke(this.lines, "remove");
35973
- };
35974
- this.addRayControls = () => {
35975
- this.addLine("ray");
35976
- };
35977
- this.removeRayControls = () => {
35978
- this.removeLine();
35979
- };
35980
- this.addPolygonControls = () => {
35981
- this.polygon = null;
35982
- const coords = InteractiveGraph.getPolygonCoords(this.props.graph, this.props);
35983
- // Clear out our old points so that newly added points don't
35984
- // "collide", as in `addPointControls`
35985
- this.points = [];
35986
- this.points = _.map(coords, this.createPointForPolygonType, this);
35987
- this.updatePolygon();
35988
- };
35989
- this.updatePolygon = () => {
35990
- if (this.props.graph.type !== "polygon") {
35991
- throw makeInvalidTypeError("updatePolygon", "polygon");
35992
- }
35993
- let closed;
35994
- if (this.polygon) {
35995
- closed = this.polygon.closed();
35996
- } else if (this.points.length >= 3) {
35997
- closed = true;
35998
- } else {
35999
- // There will only be fewer than 3 points in click-to-add-vertices
36000
- // mode, so we don't need to explicitly check for that here.
36001
- closed = false;
36002
- }
36003
- const graphie = this.graphie;
36004
- const n = this.points.length;
36005
-
36006
- // TODO(alex): check against "grid" instead, use constants
36007
- const snapToGrid = !_.contains(["angles", "sides"], this.props.graph.snapTo);
36008
- const angleLabels = _.times(n, function (i) {
36009
- if (
36010
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36011
- !this.props.graph.showAngles || !closed && (i === 0 || i === n - 1)) {
36012
- return "";
36013
- }
36014
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36015
- if (this.props.graph.snapTo === "angles") {
36016
- return "$deg0";
36017
- }
36018
- return "$deg1";
36019
- }, this);
36020
- const showRightAngleMarkers = _.times(n, function (i) {
36021
- return closed || i !== 0 && i !== n - 1;
36022
- }, this);
36023
- const numArcs = _.times(n, function (i) {
36024
- if (
36025
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36026
- this.props.graph.showAngles && (closed || i !== 0 && i !== n - 1)) {
36027
- return 1;
36028
- }
36029
- return 0;
36030
- }, this);
36031
- const sideLabels = _.times(n, function (i) {
36032
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36033
- if (!this.props.graph.showSides || !closed && i === n - 1) {
36034
- return "";
36035
- }
36036
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36037
- if (this.props.graph.snapTo === "sides") {
36038
- return "$len0";
36039
- }
36040
- return "$len1";
36041
- }, this);
36042
- if (this.polygon == null) {
36043
- const self = this;
36044
- self.polygon = Interactive2.addMovablePolygon(graphie, {
36045
- constraints: [
36046
- // @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => undefined'.
36047
- Interactive2.MovablePolygon.constraints.bound(), snapToGrid ?
36048
- // @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => undefined'.
36049
- Interactive2.MovablePolygon.constraints.snap() : null],
36050
- closed: closed,
36051
- points: self.points,
36052
- angleLabels: angleLabels,
36053
- showRightAngleMarkers: showRightAngleMarkers,
36054
- numArcs: numArcs,
36055
- sideLabels: sideLabels,
36056
- onMove: [
36057
- // @ts-expect-error - TS2339 - Property 'onMove' does not exist on type '(graphie: any, movable: any, options: any) => undefined'.
36058
- Interactive2.MovablePolygon.onMove.updatePoints, function () {
36059
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36060
- if (this.closed()) {
36061
- self.updateCoordsFromPoints();
36062
- }
36063
- }],
36064
- normalStyle: _extends({
36065
- stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTIVE
36066
- }, this._lineStroke())
36067
- });
36068
- } else {
36069
- // We only need to pass in the properties that might've changed
36070
- this.polygon.update({
36071
- closed: closed,
36072
- points: this.points,
36073
- angleLabels: angleLabels,
36074
- showRightAngleMarkers: showRightAngleMarkers,
36075
- numArcs: numArcs,
36076
- sideLabels: sideLabels
36077
- });
36078
- }
36079
- };
36080
- this.removePolygonControls = () => {
36081
- var _this$polygon11;
36082
- _.invoke(this.points, "remove");
36083
- (_this$polygon11 = this.polygon) == null || _this$polygon11.remove();
36084
- };
36085
- this.addAngleControls = () => {
36086
- if (this.props.graph.type !== "angle") {
36087
- throw makeInvalidTypeError("addAngleControls", "angle");
36088
- }
36089
- const graph = this.props.graph;
36090
- const graphie = this.graphie;
36091
- const coords = InteractiveGraph.getAngleCoords(this.props.graph, this.props);
36092
-
36093
- // The vertex snaps to the grid, but the rays don't...
36094
- this.points = coords.map(function (coord, i) {
36095
- return graphie == null ? void 0 : graphie.addMovablePoint(_.extend({
36096
- coord: coord,
36097
- normalStyle: {
36098
- stroke: KhanColors.INTERACTIVE,
36099
- fill: KhanColors.INTERACTIVE
36100
- }
36101
- }, i === 1 ? {
36102
- snapX: graphie.snap[0],
36103
- snapY: graphie.snap[1]
36104
- } : {}));
36105
- });
36106
-
36107
- // ...they snap to whole-degree angles from the vertex.
36108
- this.angle = graphie == null ? void 0 : graphie.addMovableAngle({
36109
- points: this.points,
36110
- snapDegrees: graph.snapDegrees || 1,
36111
- snapOffsetDeg: graph.angleOffsetDeg || 0,
36112
- angleLabel: graph.showAngles ? "$deg0" : "",
36113
- pushOut: 2,
36114
- allowReflex: defaultVal(graph.allowReflexAngles, true)
36115
- });
36116
- $(this.angle).on("move", () => {
36117
- var _this$angle;
36118
- this.onChange({
36119
- graph: _extends({}, graph, {
36120
- coords: (_this$angle = this.angle) == null ? void 0 : _this$angle.getClockwiseCoords()
36121
- })
36122
- });
36123
- });
36124
- };
36125
- this.removeAngleControls = () => {
36126
- var _this$angle2;
36127
- _.invoke(this.points, "remove");
36128
- (_this$angle2 = this.angle) == null || _this$angle2.remove();
36129
- };
36130
- this.toggleShowSides = () => {
36131
- if (this.props.graph.type !== "polygon") {
36132
- throw makeInvalidTypeError("toggleShowSides", "polygon");
36133
- }
36134
- const graph = _.extend({}, this.props.graph, {
36135
- showSides: !this.props.graph.showSides
36136
- });
36137
- this.onChange({
36138
- graph: graph
36139
- });
36140
- };
36141
- this.focus = $.noop;
36142
- }
36143
- componentDidMount() {
36144
- if (this.refs.graph) {
36145
- // eslint-disable-next-line react/no-string-refs
36146
- // @ts-expect-error - TS2339 - Property 'graphie' does not exist on type 'ReactInstance'.
36147
- this.setGraphie(this.refs.graph.graphie());
36148
- }
36149
- }
36150
- UNSAFE_componentWillReceiveProps(nextProps) {
36151
- var _this$props$backgroun, _nextProps$background;
36152
- if (this.isClickToAddPoints() !== this.isClickToAddPoints(nextProps)) {
36153
- this.shouldResetGraphie = true;
36154
- this.setState({
36155
- shouldShowInstructions: _getShouldShowInstructions(nextProps)
36156
- });
36157
- }
36158
- if (((_this$props$backgroun = this.props.backgroundImage) == null ? void 0 : _this$props$backgroun.url) !== ((_nextProps$background = nextProps.backgroundImage) == null ? void 0 : _nextProps$background.url) || this.props.backgroundImage !== nextProps.backgroundImage || this.props.containerSizeClass !== nextProps.containerSizeClass) {
36159
- this.shouldResetGraphie = true;
36160
- }
36161
- }
36162
- componentDidUpdate(prevProps, prevState) {
36163
- // TODO(jeremy): This feels underspecified. For example, if the props
36164
- // change the coords of a segment graph, but not the numSegments, we'd
36165
- // not count that as an update.
36166
- const oldType = prevProps.graph.type;
36167
- const newType = this.props.graph.type;
36168
- if (oldType !== newType || prevProps.graph.type === "angle" && this.props.graph.type === "angle" && this.areAngleGraphsEqual(prevProps.graph, this.props.graph) || prevProps.graph.type === "point" && this.props.graph.type === "point" && this.arePointGraphsEqual(prevProps.graph, this.props.graph) || prevProps.graph.type === "polygon" && this.props.graph.type === "polygon" && this.arePolygonGraphsEqual(prevProps.graph, this.props.graph) || prevProps.graph.type === "segment" && this.props.graph.type === "segment" && this.areSegmentGraphsEqual(prevProps.graph, this.props.graph)) {
36169
- this["remove" + capitalize(oldType) + "Controls"]();
36170
- this["add" + capitalize(newType) + "Controls"]();
36171
- }
36172
- if (this.shouldResetGraphie) {
36173
- this.resetGraphie();
36174
- }
36175
- }
36176
- areAngleGraphsEqual(prevGraph, currentGraph) {
36177
- return prevGraph.allowReflexAngles !== currentGraph.allowReflexAngles || prevGraph.angleOffsetDeg !== currentGraph.angleOffsetDeg || prevGraph.snapDegrees !== currentGraph.snapDegrees;
36178
- }
36179
- arePointGraphsEqual(prevGraph, currentGraph) {
36180
- return prevGraph.numPoints !== currentGraph.numPoints;
36181
- }
36182
- arePolygonGraphsEqual(prevGraph, currentGraph) {
36183
- return prevGraph.numSides !== currentGraph.numSides || prevGraph.showAngles !== currentGraph.showAngles || prevGraph.showSides !== currentGraph.showSides || prevGraph.snapTo !== currentGraph.snapTo;
36184
- }
36185
- areSegmentGraphsEqual(prevGraph, currentGraph) {
36186
- return prevGraph.numSegments !== currentGraph.numSegments;
36187
- }
36188
- getUserInput() {
36189
- return InteractiveGraph.getUserInputFromProps(this.props);
36190
- }
36191
- render() {
36192
- const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
36193
- const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
36194
- const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
36195
- let instructions;
36196
- // isClickToAddPoints() only applies to points and polygons
36197
- if (this.isClickToAddPoints() && this.state.shouldShowInstructions) {
36198
- if (this.props.graph.type === "point") {
36199
- instructions = this.context.strings.addPoints;
36200
- } else if (this.props.graph.type === "polygon") {
36201
- instructions = this.context.strings.addVertices;
36202
- }
36203
- } else {
36204
- instructions = undefined;
36205
- }
36206
- let onMouseDown;
36207
- if (this.isClickToAddPoints()) {
36208
- onMouseDown = this.handleAddPointsMouseDown;
36209
- }
36210
- const isMobile = this.props.apiOptions.isMobile;
36211
- return /*#__PURE__*/React.createElement("div", {
36212
- className: "perseus-widget " + "perseus-widget-interactive-graph",
36213
- style: {
36214
- width: box[0],
36215
- height: box[1]
36216
- }
36217
- }, /*#__PURE__*/React.createElement(Graph, {
36218
- instructions: instructions
36219
- // eslint-disable-next-line react/no-string-refs
36220
- ,
36221
- ref: "graph",
36222
- box: box,
36223
- labels: this.props.labels,
36224
- range: this.props.range,
36225
- step: isMobile ? Util.constrainedTickStepsFromTickSteps(this.props.step, this.props.range) : this.props.step,
36226
- gridStep: gridStep,
36227
- snapStep: snapStep,
36228
- markings: this.props.markings,
36229
- backgroundImage: this.props.backgroundImage,
36230
- showProtractor: this.props.showProtractor,
36231
- onMouseDown: onMouseDown,
36232
- onGraphieUpdated: this.setGraphie,
36233
- setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
36234
- isMobile: isMobile
36235
- }));
36236
- }
36237
- }
36238
- LegacyInteractiveGraph.contextType = PerseusI18nContext;
36239
- LegacyInteractiveGraph.defaultProps = {
36240
- labels: ["x", "y"],
36241
- range: [[-10, 10], [-10, 10]],
36242
- step: [1, 1],
36243
- backgroundImage: defaultBackgroundImage,
36244
- markings: "graph",
36245
- showTooltips: false,
36246
- showProtractor: false,
36247
- graph: {
36248
- type: "linear"
36249
- }
36250
- };
36251
34880
  class InteractiveGraph extends React.Component {
36252
34881
  constructor(...args) {
36253
34882
  super(...args);
36254
- this.legacyGraphRef = /*#__PURE__*/React.createRef();
36255
34883
  this.mafsRef = /*#__PURE__*/React.createRef();
36256
34884
  }
36257
34885
  getUserInput() {
36258
- var _this$mafsRef$current, _this$legacyGraphRef$;
34886
+ var _this$mafsRef$current;
36259
34887
  if ((_this$mafsRef$current = this.mafsRef.current) != null && _this$mafsRef$current.getUserInput) {
36260
34888
  return this.mafsRef.current.getUserInput();
36261
34889
  }
36262
- if ((_this$legacyGraphRef$ = this.legacyGraphRef.current) != null && _this$legacyGraphRef$.getUserInput) {
36263
- return this.legacyGraphRef.current.getUserInput();
36264
- }
36265
34890
  throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
36266
34891
  }
36267
34892
  getPromptJSON() {
@@ -36456,8 +35081,32 @@ class InteractiveGraph extends React.Component {
36456
35081
  }
36457
35082
  static getEquationString(props) {
36458
35083
  const type = props.graph.type;
36459
- const funcName = "get" + capitalize(type) + "EquationString";
36460
- return InteractiveGraph[funcName](props);
35084
+ switch (type) {
35085
+ case "none":
35086
+ return InteractiveGraph.getNoneEquationString();
35087
+ case "linear":
35088
+ return InteractiveGraph.getLinearEquationString(props);
35089
+ case "quadratic":
35090
+ return InteractiveGraph.getQuadraticEquationString(props);
35091
+ case "sinusoid":
35092
+ return InteractiveGraph.getSinusoidEquationString(props);
35093
+ case "circle":
35094
+ return InteractiveGraph.getCircleEquationString(props);
35095
+ case "linear-system":
35096
+ return InteractiveGraph.getLinearSystemEquationString(props);
35097
+ case "point":
35098
+ return InteractiveGraph.getPointEquationString(props);
35099
+ case "segment":
35100
+ return InteractiveGraph.getSegmentEquationString(props);
35101
+ case "ray":
35102
+ return InteractiveGraph.getRayEquationString(props);
35103
+ case "polygon":
35104
+ return InteractiveGraph.getPolygonEquationString(props);
35105
+ case "angle":
35106
+ return InteractiveGraph.getAngleEquationString(props);
35107
+ default:
35108
+ throw new UnreachableCaseError(type);
35109
+ }
36461
35110
  }
36462
35111
  static pointsFromNormalized(props, coordsList, noSnap) {
36463
35112
  // @ts-expect-error - TS2322 - Type 'number[][]' is not assignable to type 'readonly Coord[]'.
@@ -36613,8 +35262,7 @@ var InteractiveGraph$1 = {
36613
35262
  name: "interactive-graph",
36614
35263
  displayName: "Interactive graph (Assessments only)",
36615
35264
  widget: InteractiveGraph,
36616
- staticTransform: staticTransform$3,
36617
- getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
35265
+ staticTransform: staticTransform$3
36618
35266
  };
36619
35267
 
36620
35268
  // TODO: can we use WB typography?
@@ -37241,11 +35889,16 @@ class LabelImage extends React.Component {
37241
35889
  });
37242
35890
  }
37243
35891
  activateMarker(index, opened) {
37244
- var _this$props$analytics;
35892
+ var _this$props$analytics, _this$props$analytics2;
35893
+ // TODO(LEMS-2830): Remove analytics event in LEMS-2830 in favor of ti below.
37245
35894
  (_this$props$analytics = this.props.analytics) == null || _this$props$analytics.onAnalyticsEvent({
37246
35895
  type: "perseus:label-image:marker-interacted-with",
37247
35896
  payload: null
37248
35897
  });
35898
+ (_this$props$analytics2 = this.props.analytics) == null || _this$props$analytics2.onAnalyticsEvent({
35899
+ type: "perseus:label-image:marker-interacted-with:ti",
35900
+ payload: null
35901
+ });
37249
35902
  const {
37250
35903
  activeMarkerIndex
37251
35904
  } = this.state;
@@ -37388,11 +36041,16 @@ class LabelImage extends React.Component {
37388
36041
  })),
37389
36042
  multipleSelect: this.props.multipleAnswers,
37390
36043
  onChange: selection => {
37391
- var _this$props$analytics2;
37392
- (_this$props$analytics2 = this.props.analytics) == null || _this$props$analytics2.onAnalyticsEvent({
36044
+ var _this$props$analytics3, _this$props$analytics4;
36045
+ // TODO(LEMS-2829): Remove analytics event in LEMS-2829 in favor of ti below.
36046
+ (_this$props$analytics3 = this.props.analytics) == null || _this$props$analytics3.onAnalyticsEvent({
37393
36047
  type: "perseus:label-image:choiced-interacted-with",
37394
36048
  payload: null
37395
36049
  });
36050
+ (_this$props$analytics4 = this.props.analytics) == null || _this$props$analytics4.onAnalyticsEvent({
36051
+ type: "perseus:label-image:choiced-interacted-with:ti",
36052
+ payload: null
36053
+ });
37396
36054
  this.handleAnswerChoicesChangeForMarker(index, selection);
37397
36055
  },
37398
36056
  onToggle: opened => this.activateMarker(index, opened)
@@ -37488,11 +36146,16 @@ class LabelImage extends React.Component {
37488
36146
  }))), this.renderMarkers()), /*#__PURE__*/React.createElement(HideAnswersToggle, {
37489
36147
  areAnswersHidden: this.state.hideAnswers,
37490
36148
  onChange: hideAnswers => {
37491
- var _this$props$analytics3;
37492
- (_this$props$analytics3 = this.props.analytics) == null || _this$props$analytics3.onAnalyticsEvent({
36149
+ var _this$props$analytics5, _this$props$analytics6;
36150
+ // TODO(LEMS-2831): Remove analytics event in LEMS-2831 in favor of ti below.
36151
+ (_this$props$analytics5 = this.props.analytics) == null || _this$props$analytics5.onAnalyticsEvent({
37493
36152
  type: "perseus:label-image:toggle-answers-hidden",
37494
36153
  payload: null
37495
36154
  });
36155
+ (_this$props$analytics6 = this.props.analytics) == null || _this$props$analytics6.onAnalyticsEvent({
36156
+ type: "perseus:label-image:toggle-answers-hidden:ti",
36157
+ payload: null
36158
+ });
37496
36159
  this.setState({
37497
36160
  hideAnswers
37498
36161
  });
@@ -37555,8 +36218,7 @@ var LabelImage$1 = {
37555
36218
  displayName: "Label Image",
37556
36219
  widget: LabelImageWithDependencies,
37557
36220
  accessible: true,
37558
- isLintable: true,
37559
- getPublicWidgetOptions: getLabelImagePublicWidgetOptions
36221
+ isLintable: true
37560
36222
  };
37561
36223
 
37562
36224
  var ItemState = /*#__PURE__*/function (ItemState) {
@@ -37811,20 +36473,24 @@ class Draggable extends React.Component {
37811
36473
  if (this.props.margin != null) {
37812
36474
  style.margin = this.props.margin;
37813
36475
  }
37814
- return /*#__PURE__*/React.createElement("li", {
37815
- className: className,
37816
- style: style,
37817
- onMouseDown: this.onMouseDown,
37818
- onTouchStart: this.onMouseDown,
37819
- onTouchMove: this.onMouseMove,
37820
- onTouchEnd: this.onMouseUp,
37821
- onTouchCancel: this.onMouseUp
37822
- }, /*#__PURE__*/React.createElement(Renderer, {
37823
- content: this.props.content,
37824
- linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "draggable"),
37825
- onRender: this.props.onRender,
37826
- strings: this.context.strings
37827
- }));
36476
+ return (
36477
+ /*#__PURE__*/
36478
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
36479
+ React.createElement("li", {
36480
+ className: className,
36481
+ style: style,
36482
+ onMouseDown: this.onMouseDown,
36483
+ onTouchStart: this.onMouseDown,
36484
+ onTouchMove: this.onMouseMove,
36485
+ onTouchEnd: this.onMouseUp,
36486
+ onTouchCancel: this.onMouseUp
36487
+ }, /*#__PURE__*/React.createElement(Renderer, {
36488
+ content: this.props.content,
36489
+ linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "draggable"),
36490
+ onRender: this.props.onRender,
36491
+ strings: this.context.strings
36492
+ }))
36493
+ );
37828
36494
  }
37829
36495
  }
37830
36496
  Draggable.contextType = PerseusI18nContext;
@@ -38287,10 +36953,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
38287
36953
  };
38288
36954
  };
38289
36955
 
38290
- const {
38291
- shuffle: shuffle$1,
38292
- seededRNG
38293
- } = Util;
38294
36956
  const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
38295
36957
  class Matcher extends React.Component {
38296
36958
  constructor(...args) {
@@ -38382,16 +37044,16 @@ class Matcher extends React.Component {
38382
37044
  }, "1")));
38383
37045
  }
38384
37046
 
38385
- // Use the same random() function to shuffle both columns sequentially
38386
- const rng = seededRNG(this.props.problemNum);
38387
- let left;
38388
- if (!this.props.orderMatters) {
38389
- // If the order doesn't matter, don't shuffle the left column
38390
- left = this.props.left;
38391
- } else {
38392
- left = shuffle$1(this.props.left, rng, /* ensurePermuted */true);
38393
- }
38394
- const right = shuffle$1(this.props.right, rng, /* ensurePermuted */true);
37047
+ /* TODO(LEMS-2841):
37048
+ Once the getMatcherPublicWidgetOptions function gets connected to the
37049
+ widget, we'll need to update this to the line below to only shuffle
37050
+ on the server.
37051
+ const {left, right} = this.props;
37052
+ */
37053
+ const {
37054
+ left,
37055
+ right
37056
+ } = shuffleMatcher(this.props);
38395
37057
  const showLabels = _.any(this.props.labels);
38396
37058
  const constraints = {
38397
37059
  height: _.max([this.state.leftHeight, this.state.rightHeight])
@@ -38894,8 +37556,7 @@ var Matrix$1 = {
38894
37556
  widget: Matrix,
38895
37557
  transform: propTransform$1,
38896
37558
  staticTransform: staticTransform$2,
38897
- isLintable: true,
38898
- getPublicWidgetOptions: getMatrixPublicWidgetOptions
37559
+ isLintable: true
38899
37560
  };
38900
37561
 
38901
37562
  const getPromptJSON$7 = () => {
@@ -40468,8 +39129,7 @@ var NumberLine$1 = {
40468
39129
  displayName: "Number line",
40469
39130
  widget: NumberLine,
40470
39131
  transform: numberLineTransform,
40471
- staticTransform: staticTransform$1,
40472
- getPublicWidgetOptions: getNumberLinePublicWidgetOptions
39132
+ staticTransform: staticTransform$1
40473
39133
  };
40474
39134
 
40475
39135
  const getPromptJSON$5 = (renderProps, userInput) => {
@@ -40637,19 +39297,23 @@ class Card extends React.Component {
40637
39297
  // Pull out the content to get rendered
40638
39298
  const rendererProps = _.pick(this.props, "content");
40639
39299
  const onMouseDown = this.props.animating ? $.noop : this.onMouseDown;
40640
- return /*#__PURE__*/React.createElement("div", {
40641
- className: "card-wrap",
40642
- style: style,
40643
- onMouseDown: onMouseDown,
40644
- onTouchStart: onMouseDown,
40645
- onTouchEnd: this.onMouseUp,
40646
- onTouchCancel: this.onMouseUp
40647
- }, /*#__PURE__*/React.createElement("div", {
40648
- className: className.join(" ")
40649
- }, /*#__PURE__*/React.createElement(Renderer, _extends({}, rendererProps, {
40650
- linterContext: this.props.linterContext,
40651
- strings: this.context.strings
40652
- }))));
39300
+ return (
39301
+ /*#__PURE__*/
39302
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
39303
+ React.createElement("div", {
39304
+ className: "card-wrap",
39305
+ style: style,
39306
+ onMouseDown: onMouseDown,
39307
+ onTouchStart: onMouseDown,
39308
+ onTouchEnd: this.onMouseUp,
39309
+ onTouchCancel: this.onMouseUp
39310
+ }, /*#__PURE__*/React.createElement("div", {
39311
+ className: className.join(" ")
39312
+ }, /*#__PURE__*/React.createElement(Renderer, _extends({}, rendererProps, {
39313
+ linterContext: this.props.linterContext,
39314
+ strings: this.context.strings
39315
+ }))))
39316
+ );
40653
39317
  }
40654
39318
  }
40655
39319
  Card.contextType = PerseusI18nContext;
@@ -41029,8 +39693,7 @@ var Orderer$1 = {
41029
39693
  displayName: "Orderer",
41030
39694
  hidden: true,
41031
39695
  widget: Orderer,
41032
- isLintable: true,
41033
- getPublicWidgetOptions: getOrdererPublicWidgetOptions
39696
+ isLintable: true
41034
39697
  };
41035
39698
 
41036
39699
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -42090,8 +40753,7 @@ var Plotter$1 = {
42090
40753
  displayName: "Plotter",
42091
40754
  hidden: true,
42092
40755
  widget: Plotter,
42093
- staticTransform: staticTransform,
42094
- getPublicWidgetOptions: getPlotterPublicWidgetOptions
40756
+ staticTransform: staticTransform
42095
40757
  };
42096
40758
 
42097
40759
  const getPromptJSON$2 = () => {
@@ -42167,9 +40829,6 @@ const getPromptJSON$1 = userInput => {
42167
40829
  };
42168
40830
  };
42169
40831
 
42170
- const {
42171
- shuffle
42172
- } = Util;
42173
40832
  class Sorter extends React.Component {
42174
40833
  constructor(...args) {
42175
40834
  super(...args);
@@ -42221,6 +40880,10 @@ class Sorter extends React.Component {
42221
40880
  return getPromptJSON$1(this.getUserInput());
42222
40881
  }
42223
40882
  render() {
40883
+ // TODO(LEMS-2841): Remove client-side shuffle once receiving public
40884
+ // options. The correct field will already be shuffled.
40885
+ // Probably easiest to replace "options" with "this.props.correct" when
40886
+ // setting up Sortable below. Or use the assignment to rename for clarity.
42224
40887
  const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
42225
40888
  const {
42226
40889
  apiOptions
@@ -42253,8 +40916,7 @@ var Sorter$1 = {
42253
40916
  name: "sorter",
42254
40917
  displayName: "Sorter",
42255
40918
  widget: Sorter,
42256
- isLintable: true,
42257
- getPublicWidgetOptions: getSorterPublicWidgetOptions
40919
+ isLintable: true
42258
40920
  };
42259
40921
 
42260
40922
  const {
@@ -42473,8 +41135,7 @@ var Table$1 = {
42473
41135
  widget: Table,
42474
41136
  transform: propTransform,
42475
41137
  hidden: true,
42476
- isLintable: true,
42477
- getPublicWidgetOptions: getTablePublicWidgetOptions
41138
+ isLintable: true
42478
41139
  };
42479
41140
 
42480
41141
  const getPromptJSON = () => {
@@ -42653,7 +41314,7 @@ const init = function init() {
42653
41314
 
42654
41315
  // This file is processed by a Rollup plugin (replace) to inject the production
42655
41316
  const libName = "@khanacademy/perseus";
42656
- const libVersion = "53.1.0";
41317
+ const libVersion = "54.1.0";
42657
41318
  addLibraryVersionToPerseusDebug(libName, libVersion);
42658
41319
 
42659
41320
  const apiVersion = {
@@ -44140,6 +42801,18 @@ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
44140
42801
  const widget = widgetMap[WidgetId];
44141
42802
  return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
44142
42803
  }
42804
+ function getWidgetSubTypeByWidgetId(widgetId, widgetMap) {
42805
+ var _widget$type2, _graph$type;
42806
+ const widget = widgetMap[widgetId];
42807
+ const widgetType = (_widget$type2 = widget == null ? void 0 : widget.type) != null ? _widget$type2 : null;
42808
+ switch (widgetType) {
42809
+ case "interactive-graph":
42810
+ const graph = widget.options.graph;
42811
+ return (_graph$type = graph == null ? void 0 : graph.type) != null ? _graph$type : null;
42812
+ default:
42813
+ return null;
42814
+ }
42815
+ }
44143
42816
 
44144
42817
  /**
44145
42818
  * Does the content have a specific type of widget?
@@ -44932,5 +43605,5 @@ const ScoringUtil = {
44932
43605
  * Types
44933
43606
  */
44934
43607
 
44935
- 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, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, preprocessTex, registerAllWidgetsForTesting, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
43608
+ 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, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetSubTypeByWidgetId, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, preprocessTex, registerAllWidgetsForTesting, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
44936
43609
  //# sourceMappingURL=index.js.map