@khanacademy/perseus 53.1.0 → 54.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) 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 +603 -1867
  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 +678 -691
  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 +3 -12
  15. package/dist/util/graphie.d.ts +1 -1
  16. package/dist/widget-ai-utils/radio/radio-ai-utils.d.ts +2 -1
  17. package/dist/widget-type-utils.d.ts +1 -0
  18. package/dist/widgets/categorizer/categorizer.d.ts +1 -2
  19. package/dist/widgets/cs-program/cs-program.d.ts +0 -2
  20. package/dist/widgets/dropdown/dropdown.d.ts +0 -2
  21. package/dist/widgets/expression/expression.d.ts +3 -4
  22. package/dist/widgets/grapher/grapher.d.ts +0 -2
  23. package/dist/widgets/iframe/iframe.d.ts +1 -2
  24. package/dist/widgets/interactive-graph.d.ts +2 -87
  25. package/dist/widgets/interactive-graphs/backgrounds/axis-labels.d.ts +4 -1
  26. package/dist/widgets/interactive-graphs/graphs/angle.d.ts +2 -1
  27. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +6 -2
  28. package/dist/widgets/interactive-graphs/graphs/components/hairlines.d.ts +7 -0
  29. package/dist/widgets/interactive-graphs/graphs/components/movable-point-view.d.ts +1 -0
  30. package/dist/widgets/interactive-graphs/graphs/components/svg-line.d.ts +2 -1
  31. package/dist/widgets/interactive-graphs/graphs/linear-system.d.ts +2 -2
  32. package/dist/widgets/interactive-graphs/graphs/linear.d.ts +1 -1
  33. package/dist/widgets/interactive-graphs/graphs/point.d.ts +3 -2
  34. package/dist/widgets/interactive-graphs/graphs/ray.d.ts +1 -1
  35. package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +1 -0
  36. package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -2
  37. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +2 -1
  38. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +0 -3
  39. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -2
  40. package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +3 -3
  41. package/dist/widgets/label-image/label-image.d.ts +1 -3
  42. package/dist/widgets/matrix/matrix.d.ts +1 -2
  43. package/dist/widgets/number-line/number-line.d.ts +0 -2
  44. package/dist/widgets/numeric-input/numeric-input.d.ts +0 -2
  45. package/dist/widgets/orderer/orderer.d.ts +0 -2
  46. package/dist/widgets/plotter/plotter.d.ts +0 -2
  47. package/dist/widgets/radio/radio.d.ts +1 -2
  48. package/dist/widgets/sorter/sorter.d.ts +1 -2
  49. package/dist/widgets/table/table.d.ts +0 -2
  50. package/dist/widgets.d.ts +1 -2
  51. package/package.json +10 -10
  52. 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, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, 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) {
@@ -15699,12 +15737,17 @@ class ImageLoader extends React.Component {
15699
15737
  };
15700
15738
  }
15701
15739
  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));
15740
+ return (
15741
+ /*#__PURE__*/
15742
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
15743
+ React.createElement("img", _extends({
15744
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- TODO(LEMS-2871): Address a11y error
15745
+ tabIndex: 0,
15746
+ src: staticUrl(src),
15747
+ onKeyUp: onKeyUp,
15748
+ onKeyDown: onKeyDown
15749
+ }, imgProps))
15750
+ );
15708
15751
  };
15709
15752
  this.state = {
15710
15753
  status: props.src ? Status.LOADING : Status.PENDING
@@ -16389,16 +16432,20 @@ class Zoomable extends React.Component {
16389
16432
  opacity: opacity,
16390
16433
  WebkitTapHighlightColor: "transparent"
16391
16434
  }, 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);
16435
+ return (
16436
+ /*#__PURE__*/
16437
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
16438
+ React.createElement("span", {
16439
+ onClick: this.handleClick,
16440
+ onClickCapture: this.handleClickIfZoomed,
16441
+ onTouchCancelCapture: this.stopPropagationIfZoomed,
16442
+ onTouchEndCapture: this.stopPropagationIfZoomed,
16443
+ onTouchStartCapture: this.stopPropagationIfZoomed
16444
+ // @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'.
16445
+ ,
16446
+ style: style
16447
+ }, this.props.children)
16448
+ );
16402
16449
  }
16403
16450
  }
16404
16451
  Zoomable.defaultProps = {
@@ -17619,10 +17666,6 @@ const getWidgetExport = name => {
17619
17666
  var _widgets$name3;
17620
17667
  return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
17621
17668
  };
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
17669
  const getEditor = name => {
17627
17670
  return _.has(editors, name) ? editors[name] : null;
17628
17671
  };
@@ -17759,7 +17802,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
17759
17802
  replaceDeprecatedEditors: replaceDeprecatedEditors,
17760
17803
  getWidget: getWidget,
17761
17804
  getWidgetExport: getWidgetExport,
17762
- getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
17763
17805
  getEditor: getEditor,
17764
17806
  getTransform: getTransform,
17765
17807
  getVersion: getVersion,
@@ -17835,6 +17877,7 @@ class WidgetContainer extends React.Component {
17835
17877
  "perseus-widget__definition": this.props.type === "definition"
17836
17878
  });
17837
17879
  const type = this.props.type;
17880
+ const userAgent = navigator.userAgent;
17838
17881
  const WidgetType = getWidget(type);
17839
17882
  if (WidgetType == null) {
17840
17883
  // This is for the good of all people!!
@@ -17845,6 +17888,12 @@ class WidgetContainer extends React.Component {
17845
17888
  className: className
17846
17889
  });
17847
17890
  }
17891
+ let subType = "null";
17892
+ if (type === "interactive-graph") {
17893
+ var _props$graph$type, _props$graph;
17894
+ const props = this.state.widgetProps;
17895
+ subType = (_props$graph$type = (_props$graph = props.graph) == null ? void 0 : _props$graph.type) != null ? _props$graph$type : "null";
17896
+ }
17848
17897
  let alignment = this.state.widgetProps.alignment;
17849
17898
  if (alignment === "default") {
17850
17899
  alignment = CoreWidgetRegistry.getDefaultAlignment(type);
@@ -17898,12 +17947,26 @@ class WidgetContainer extends React.Component {
17898
17947
  widget_type: type,
17899
17948
  widget_id: this.props.id
17900
17949
  },
17901
- onError: () => {
17950
+ onError: error => {
17951
+ // TODO(LEMS-2826): Remove analytics event in LEMS-2826 in favor of ti below.
17902
17952
  analytics.onAnalyticsEvent({
17903
17953
  type: "perseus:widget-rendering-error",
17904
17954
  payload: {
17955
+ widgetSubType: subType,
17956
+ widgetType: type,
17957
+ widgetId: this.props.id,
17958
+ message: error.message,
17959
+ userAgent: userAgent
17960
+ }
17961
+ });
17962
+ analytics.onAnalyticsEvent({
17963
+ type: "perseus:widget-rendering-error:ti",
17964
+ payload: {
17965
+ widgetSubType: subType,
17905
17966
  widgetType: type,
17906
- widgetId: this.props.id
17967
+ widgetId: this.props.id,
17968
+ message: error.message,
17969
+ userAgent: userAgent
17907
17970
  }
17908
17971
  });
17909
17972
  }
@@ -20327,7 +20390,6 @@ var NumericInput$1 = {
20327
20390
  widget: NumericInput,
20328
20391
  transform: propsTransform,
20329
20392
  isLintable: true,
20330
- getPublicWidgetOptions: getNumericInputPublicWidgetOptions,
20331
20393
  // TODO(LEMS-2656): remove TS suppression
20332
20394
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
20333
20395
  getOneCorrectAnswerFromRubric(rubric) {
@@ -21132,7 +21194,10 @@ class HighlightTooltip extends React.PureComponent {
21132
21194
  // using div instead of TooltipContent because
21133
21195
  // TooltipContent wouldn't let me overwrite
21134
21196
  // user-select and onClick
21135
- const content = /*#__PURE__*/React.createElement("div", {
21197
+ const content =
21198
+ /*#__PURE__*/
21199
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
21200
+ React.createElement("div", {
21136
21201
  className: css(styles$x.tooltipLabel),
21137
21202
  onClick: this.props.onClick
21138
21203
  }, this.props.label);
@@ -21143,14 +21208,18 @@ class HighlightTooltip extends React.PureComponent {
21143
21208
  top: `calc(${Math.round(focusRect.top)}px + 0.95em)`,
21144
21209
  height: 0
21145
21210
  };
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)));
21211
+ return (
21212
+ /*#__PURE__*/
21213
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
21214
+ React.createElement("div", {
21215
+ style: style,
21216
+ onMouseEnter: this.props.onMouseEnter,
21217
+ onMouseLeave: this.props.onMouseLeave
21218
+ }, /*#__PURE__*/React.createElement(Tooltip$1, {
21219
+ content: content,
21220
+ opened: true
21221
+ }, /*#__PURE__*/React.createElement("div", null)))
21222
+ );
21154
21223
  }
21155
21224
  }
21156
21225
  const styles$x = StyleSheet.create({
@@ -23853,15 +23922,19 @@ const BaseRadio = function BaseRadio({
23853
23922
  // content somehow? Would changing our choice of key
23854
23923
  // somehow break something happening inside a choice's
23855
23924
  // 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
- })));
23925
+ return (
23926
+ /*#__PURE__*/
23927
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
23928
+ React.createElement("li", {
23929
+ key: i,
23930
+ ref: e => listElem = e,
23931
+ className: className,
23932
+ onClick: clickHandler,
23933
+ onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
23934
+ }, /*#__PURE__*/React.createElement(Element, _extends({}, elementProps, {
23935
+ ref: ref
23936
+ })))
23937
+ );
23865
23938
  })));
23866
23939
 
23867
23940
  // Allow for horizontal scrolling if content is too wide, which may be
@@ -24391,8 +24464,7 @@ var Radio = {
24391
24464
  staticTransform: transform,
24392
24465
  version: radioLogic.version,
24393
24466
  propUpgrades: radioLogic.widgetOptionsUpgrades,
24394
- isLintable: true,
24395
- getPublicWidgetOptions: getRadioPublicWidgetOptions
24467
+ isLintable: true
24396
24468
  };
24397
24469
 
24398
24470
  /* eslint-disable import/no-named-as-default */
@@ -24678,8 +24750,7 @@ var Categorizer$1 = {
24678
24750
  staticTransform: editorProps => {
24679
24751
  return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
24680
24752
  },
24681
- isLintable: true,
24682
- getPublicWidgetOptions: getCategorizerPublicWidgetOptions
24753
+ isLintable: true
24683
24754
  };
24684
24755
 
24685
24756
  /**
@@ -24885,8 +24956,7 @@ var CSProgram$1 = {
24885
24956
  name: "cs-program",
24886
24957
  displayName: "CS Program",
24887
24958
  widget: CSProgram,
24888
- hidden: true,
24889
- getPublicWidgetOptions: getCSProgramPublicWidgetOptions
24959
+ hidden: true
24890
24960
  };
24891
24961
 
24892
24962
  const getPromptJSON$l = renderProps => {
@@ -25104,8 +25174,7 @@ var Dropdown$1 = {
25104
25174
  displayName: "Drop down",
25105
25175
  accessible: true,
25106
25176
  widget: Dropdown,
25107
- transform: optionsTransform,
25108
- getPublicWidgetOptions: getDropdownPublicWidgetOptions
25177
+ transform: optionsTransform
25109
25178
  };
25110
25179
 
25111
25180
  const getPromptJSON$j = renderProps => {
@@ -26579,8 +26648,7 @@ var Grapher$1 = {
26579
26648
  hidden: true,
26580
26649
  widget: Grapher,
26581
26650
  transform: propTransform$2,
26582
- staticTransform: staticTransform$4,
26583
- getPublicWidgetOptions: getGrapherPublicWidgetOptions
26651
+ staticTransform: staticTransform$4
26584
26652
  };
26585
26653
 
26586
26654
  const getPromptJSON$f = rendererJSON => {
@@ -26865,12 +26933,16 @@ class Iframe extends React.Component {
26865
26933
  // that we need. This makes it a bit safer in case some content
26866
26934
  // creator "went wild".
26867
26935
  // 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
- });
26936
+ return (
26937
+ /*#__PURE__*/
26938
+ // eslint-disable-next-line jsx-a11y/iframe-has-title -- TODO(LEMS-2871): Address a11y error
26939
+ React.createElement("iframe", {
26940
+ sandbox: sandboxProperties,
26941
+ style: style,
26942
+ src: url,
26943
+ allowFullScreen: this.props.allowFullScreen
26944
+ })
26945
+ );
26874
26946
  }
26875
26947
  }
26876
26948
  Iframe.defaultProps = {
@@ -26885,8 +26957,7 @@ var Iframe$1 = {
26885
26957
  displayName: "Iframe (deprecated)",
26886
26958
  widget: Iframe,
26887
26959
  // Let's not expose it to all content creators yet
26888
- hidden: true,
26889
- getPublicWidgetOptions: getIFramePublicWidgetOptions
26960
+ hidden: true
26890
26961
  };
26891
26962
 
26892
26963
  const getPromptJSON$d = renderProps => {
@@ -26903,7 +26974,7 @@ const getPromptJSON$d = renderProps => {
26903
26974
 
26904
26975
  const defaultBoxSize = 400;
26905
26976
  const defaultRange = [0, 10];
26906
- const defaultBackgroundImage$2 = {
26977
+ const defaultBackgroundImage$1 = {
26907
26978
  url: null,
26908
26979
  width: 0,
26909
26980
  height: 0
@@ -27060,7 +27131,7 @@ ImageWidget.defaultProps = {
27060
27131
  title: "",
27061
27132
  range: [defaultRange, defaultRange],
27062
27133
  box: [defaultBoxSize, defaultBoxSize],
27063
- backgroundImage: defaultBackgroundImage$2,
27134
+ backgroundImage: defaultBackgroundImage$1,
27064
27135
  labels: [],
27065
27136
  alt: "",
27066
27137
  caption: "",
@@ -27677,307 +27748,6 @@ var Interactive = {
27677
27748
  hidden: true
27678
27749
  };
27679
27750
 
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
27751
  const getPromptJSON$b = (props, userInput) => {
27982
27752
  var _props$backgroundImag;
27983
27753
  return {
@@ -28530,13 +28300,18 @@ function escapeIfUnescaped(character) {
28530
28300
  }
28531
28301
  }
28532
28302
 
28533
- function AxisLabels() {
28303
+ function AxisLabels({
28304
+ i18n
28305
+ }) {
28534
28306
  const {
28535
28307
  range,
28536
28308
  labels,
28537
28309
  width,
28538
28310
  height
28539
28311
  } = useGraphConfig();
28312
+ const {
28313
+ strings
28314
+ } = i18n;
28540
28315
  const yAxisLabelLocation = [0, range[Y][MAX]];
28541
28316
  const xAxisLabelLocation = [range[X][MAX], 0];
28542
28317
  const [xAxisLabelText, yAxisLabelText] = labels;
@@ -28554,6 +28329,7 @@ function AxisLabels() {
28554
28329
  TeX
28555
28330
  } = getDependencies();
28556
28331
  return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
28332
+ "aria-label": strings.xAxis,
28557
28333
  style: {
28558
28334
  position: "absolute",
28559
28335
  left: x1,
@@ -28562,6 +28338,7 @@ function AxisLabels() {
28562
28338
  transform: "translate(7px, -50%)"
28563
28339
  }
28564
28340
  }, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default.createElement("span", {
28341
+ "aria-label": strings.yAxis,
28565
28342
  style: {
28566
28343
  position: "absolute",
28567
28344
  left: x2,
@@ -29371,7 +29148,6 @@ RangeInput.defaultProps = {
29371
29148
  var components = /*#__PURE__*/Object.freeze({
29372
29149
  __proto__: null,
29373
29150
  ButtonGroup: ButtonGroup,
29374
- Graph: Graph,
29375
29151
  Graphie: Graphie,
29376
29152
  HUD: HUD,
29377
29153
  Icon: Icon,
@@ -29512,9 +29288,9 @@ const LockedFunction = props => {
29512
29288
  const {
29513
29289
  color,
29514
29290
  strokeStyle,
29515
- directionalAxis,
29516
- domain
29291
+ directionalAxis
29517
29292
  } = props;
29293
+ const domain = props.domain ? [Number.isFinite(props.domain[0]) ? props.domain[0] : -Infinity, Number.isFinite(props.domain[1]) ? props.domain[1] : Infinity] : undefined;
29518
29294
  const plotProps = {
29519
29295
  color: lockedFigureColors[color],
29520
29296
  style: strokeStyle,
@@ -29594,6 +29370,11 @@ function Vector(props) {
29594
29370
  }
29595
29371
 
29596
29372
  function srFormatNumber(a, locale, maximumFractionDigits) {
29373
+ const piBasedNumber = getPiMultiple(a);
29374
+ if (piBasedNumber) {
29375
+ return piBasedNumber;
29376
+ }
29377
+
29597
29378
  // adding zero here converts negative zero to positive zero.
29598
29379
  return (0 + a).toLocaleString(locale, {
29599
29380
  maximumFractionDigits: maximumFractionDigits != null ? maximumFractionDigits : 3,
@@ -29601,6 +29382,77 @@ function srFormatNumber(a, locale, maximumFractionDigits) {
29601
29382
  });
29602
29383
  }
29603
29384
 
29385
+ // Exported for testing
29386
+ function getPiMultiple(a) {
29387
+ // - Save some calculations by checking if this is an integer first.
29388
+ // If a is an integer, it's definitely not a multiple of π.
29389
+ // - 0 should not be presented as a multiple of π.
29390
+ // - If a is greater than 1e12, then it thinks that it's always a
29391
+ // multiple of π even when it's not, due to rounding errors.
29392
+ if (Number.isInteger(a) || a === 0 || a > 1e12) {
29393
+ return null;
29394
+ }
29395
+
29396
+ // Figure out the coefficient before the π.
29397
+ // Example: If a = π/2, then piCoefficient = 0.5
29398
+ // Example: If a = 2π, then piCoefficient = 2
29399
+ const piCoefficient = a / Math.PI;
29400
+ // Truncate the coefficient to account for floating point errors.
29401
+ // π calculations are only accurate up to precision of 1e-12.
29402
+ const truncatedCoefficient = parseFloat(piCoefficient.toFixed(12));
29403
+
29404
+ // If the coefficient is already an integer, then the number
29405
+ // is a multiple of π. Return it here.
29406
+ // Example: If a = 2π, then truncatedCoefficient = 2, so return "2π"
29407
+ if (Number.isInteger(truncatedCoefficient)) {
29408
+ // Return "π" rather than "1π".
29409
+ if (truncatedCoefficient === 1) {
29410
+ return "π";
29411
+ }
29412
+
29413
+ // Return "-π" rather than "-1π".
29414
+ if (truncatedCoefficient === -1) {
29415
+ return "-π";
29416
+ }
29417
+ return truncatedCoefficient + "π";
29418
+ }
29419
+
29420
+ // If the coefficient is not an integer, then we need to
29421
+ // check if it's a multiple of π/2, π/3, π/4, or π/6.
29422
+ // These are π-based values on a unit circle.
29423
+ const acceptableDivisors = [2, 3, 4, 6];
29424
+
29425
+ // Loop through the acceptable divisors and check if the
29426
+ // coefficient is a multiple of (1/divisor).
29427
+ // Example: If a = 5π/6, the coeff is 5/6, which is a multiple of 1/6.
29428
+ // So we return "5π/6".
29429
+ for (const divisor of acceptableDivisors) {
29430
+ // Check if the coefficient is a multiple of (1/divisor) by
29431
+ // multiplying the coefficient by the divisor and checking if
29432
+ // the result is an integer.
29433
+ // Example: If a = 5π/6, then piCoefficient = 5/6. We multiply
29434
+ // by 6 (divisor) to get 5, which is an integer.
29435
+ const coefficientNumerator = parseFloat(
29436
+ // π calculations are only accurate up to precision of 1e-12.
29437
+ (piCoefficient * divisor).toFixed(12));
29438
+ if (Number.isInteger(coefficientNumerator)) {
29439
+ // Handle the case where the coefficient numberator is just 1.
29440
+ // We don't want to write "π/6" as "1π/6"
29441
+ if (coefficientNumerator === 1) {
29442
+ return "π/" + divisor;
29443
+ }
29444
+
29445
+ // Handle the case where the coefficient numberator is just -1.
29446
+ // We don't want to write "π/6" as "-1π/6"
29447
+ if (coefficientNumerator === -1) {
29448
+ return "-π/" + divisor;
29449
+ }
29450
+ return coefficientNumerator + "π/" + divisor;
29451
+ }
29452
+ }
29453
+ return null;
29454
+ }
29455
+
29604
29456
  /**
29605
29457
  * Given a ray and a rectangular box, find the point where the ray intersects
29606
29458
  * the edge of the box. Assumes the `initialPoint` is inside the box.
@@ -30756,6 +30608,34 @@ function range(min, max, step = 1) {
30756
30608
  return result;
30757
30609
  }
30758
30610
 
30611
+ function Hairlines(props) {
30612
+ const {
30613
+ point
30614
+ } = props;
30615
+ const {
30616
+ range
30617
+ } = useGraphConfig();
30618
+ const [[xMin, xMax], [yMin, yMax]] = range;
30619
+ const [[x, y]] = useTransformVectorsToPixels(point);
30620
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
30621
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
30622
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
30623
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
30624
+ return /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
30625
+ x1: verticalStartX,
30626
+ y1: y,
30627
+ x2: verticalEndX,
30628
+ y2: y,
30629
+ stroke: color.blue
30630
+ }), /*#__PURE__*/React.createElement("line", {
30631
+ x1: x,
30632
+ y1: horizontalStartY,
30633
+ x2: x,
30634
+ y2: horizontalEndY,
30635
+ stroke: color.blue
30636
+ }));
30637
+ }
30638
+
30759
30639
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
30760
30640
  // graph.
30761
30641
  const hitboxSizePx = 48;
@@ -30766,7 +30646,6 @@ const hitboxSizePx = 48;
30766
30646
  const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30767
30647
  var _Object$entries$find$, _Object$entries$find;
30768
30648
  const {
30769
- range,
30770
30649
  markings,
30771
30650
  showTooltips
30772
30651
  } = useGraphConfig();
@@ -30774,6 +30653,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30774
30653
  point,
30775
30654
  color: color$1 = color.blue,
30776
30655
  dragging,
30656
+ focused,
30777
30657
  cursor,
30778
30658
  showFocusRing,
30779
30659
  onClick = () => {}
@@ -30784,25 +30664,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30784
30664
  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
30665
  const pointClasses = classNames("movable-point", dragging && "movable-point--dragging", showFocusRing && "movable-point--focus");
30786
30666
  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
- }));
30667
+ const showHairlines = (dragging || focused) && markings !== "none";
30806
30668
  const svgForPoint = /*#__PURE__*/React.createElement("g", {
30807
30669
  ref: hitboxRef,
30808
30670
  className: pointClasses,
@@ -30838,7 +30700,9 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30838
30700
  },
30839
30701
  "data-testid": "movable-point__center"
30840
30702
  }));
30841
- return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
30703
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement(Hairlines, {
30704
+ point: point
30705
+ }), showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
30842
30706
  autoUpdate: true,
30843
30707
  backgroundColor: wbColorName,
30844
30708
  content: `(${point[X]}, ${point[Y]})`,
@@ -30932,6 +30796,7 @@ function useControlPoint(params) {
30932
30796
  },
30933
30797
  point: point,
30934
30798
  dragging: dragging,
30799
+ focused: focused,
30935
30800
  color: color,
30936
30801
  ref: visiblePointRef,
30937
30802
  showFocusRing: focused
@@ -31148,13 +31013,13 @@ const {
31148
31013
  getClockwiseAngle: getClockwiseAngle$2,
31149
31014
  polar: polar$1
31150
31015
  } = angles;
31151
- function renderAngleGraph(state, dispatch) {
31016
+ function renderAngleGraph(state, dispatch, i18n) {
31152
31017
  return {
31153
31018
  graph: /*#__PURE__*/React.createElement(AngleGraph, {
31154
31019
  graphState: state,
31155
31020
  dispatch: dispatch
31156
31021
  }),
31157
- interactiveElementsDescription: null
31022
+ interactiveElementsDescription: getAngleGraphDescription(state, i18n)
31158
31023
  };
31159
31024
  }
31160
31025
  function AngleGraph(props) {
@@ -31165,6 +31030,9 @@ function AngleGraph(props) {
31165
31030
  const {
31166
31031
  graphDimensionsInPixels
31167
31032
  } = useGraphConfig();
31033
+ const i18n = usePerseusI18n();
31034
+ const id = React.useId();
31035
+ const descriptionId = id + "-description";
31168
31036
  const {
31169
31037
  coords,
31170
31038
  showAngles,
@@ -31213,76 +31081,106 @@ function AngleGraph(props) {
31213
31081
  showAngles: showAngles || false // Whether to show the angle or not
31214
31082
  };
31215
31083
 
31084
+ // Aria strings
31216
31085
  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]);
31086
+ srAngleGraphAriaLabel,
31087
+ srAngleGraphAriaDescription,
31088
+ srAngleStartingSide,
31089
+ srAngleEndingSide,
31090
+ srAngleVertex
31091
+ } = describeAngleGraph(graphState, i18n);
31259
31092
 
31260
31093
  // Render the lines, angle, and then movable points
31261
31094
  return /*#__PURE__*/React.createElement("g", {
31262
- "aria-label": wholeAngleAriaLabel,
31263
- "aria-describedby": "angle-description"
31095
+ "aria-label": srAngleGraphAriaLabel,
31096
+ "aria-describedby": descriptionId
31264
31097
  }, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
31265
31098
  point: coords[1],
31266
31099
  sequenceNumber: 1,
31267
31100
  constrain: p => p,
31268
31101
  onMove: destination => dispatch(actions.angle.movePoint(1, destination)),
31269
- ariaLabel: vertexAriaLabel
31102
+ ariaLabel: srAngleVertex
31270
31103
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
31271
31104
  point: coords[0],
31272
31105
  sequenceNumber: 2,
31273
31106
  constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
31274
31107
  onMove: destination => dispatch(actions.angle.movePoint(0, destination)),
31275
- ariaLabel: terminalSideAriaLabel
31108
+ ariaLabel: srAngleEndingSide
31276
31109
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
31277
31110
  point: coords[2],
31278
31111
  sequenceNumber: 3,
31279
31112
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
31280
31113
  onMove: destination => dispatch(actions.angle.movePoint(2, destination)),
31281
- ariaLabel: initialSideAriaLabel
31114
+ ariaLabel: srAngleStartingSide
31282
31115
  }), /*#__PURE__*/React.createElement("g", {
31283
- id: "angle-description",
31116
+ id: descriptionId,
31284
31117
  style: a11y.srOnly
31285
- }, wholeAngleDescription));
31118
+ }, srAngleGraphAriaDescription));
31119
+ }
31120
+ function getAngleGraphDescription(state, i18n) {
31121
+ const {
31122
+ strings,
31123
+ locale
31124
+ } = i18n;
31125
+ const {
31126
+ coords
31127
+ } = state;
31128
+ return strings.srInteractiveElements({
31129
+ elements: strings.srAngleInteractiveElements({
31130
+ vertexX: srFormatNumber(coords[1][X], locale),
31131
+ vertexY: srFormatNumber(coords[1][Y], locale),
31132
+ startingSideX: srFormatNumber(coords[2][X], locale),
31133
+ startingSideY: srFormatNumber(coords[2][Y], locale),
31134
+ endingSideX: srFormatNumber(coords[0][X], locale),
31135
+ endingSideY: srFormatNumber(coords[0][Y], locale)
31136
+ })
31137
+ });
31138
+ }
31139
+ function describeAngleGraph(state, i18n) {
31140
+ const {
31141
+ strings,
31142
+ locale
31143
+ } = i18n;
31144
+ const {
31145
+ coords,
31146
+ allowReflexAngles,
31147
+ showAngles
31148
+ } = state;
31149
+ const [endingSide, vertex, startingSide] = coords;
31150
+ const angleMeasure = srFormatNumber(getClockwiseAngle$2(coords, allowReflexAngles), locale);
31151
+ const srAngleGraphAriaLabel = strings.srAngleGraphAriaLabel;
31152
+ const srAngleGraphAriaDescription = strings.srAngleGraphAriaDescription({
31153
+ angleMeasure,
31154
+ vertexX: srFormatNumber(vertex[X], locale),
31155
+ vertexY: srFormatNumber(vertex[Y], locale),
31156
+ startingSideX: srFormatNumber(startingSide[X], locale),
31157
+ startingSideY: srFormatNumber(startingSide[Y], locale),
31158
+ endingSideX: srFormatNumber(endingSide[X], locale),
31159
+ endingSideY: srFormatNumber(endingSide[Y], locale)
31160
+ });
31161
+ const srAngleStartingSide = strings.srAngleStartingSide({
31162
+ x: srFormatNumber(startingSide[X], locale),
31163
+ y: srFormatNumber(startingSide[Y], locale)
31164
+ });
31165
+ const srAngleEndingSide = strings.srAngleEndingSide({
31166
+ x: srFormatNumber(endingSide[X], locale),
31167
+ y: srFormatNumber(endingSide[Y], locale)
31168
+ });
31169
+ const srAngleVertex = showAngles ? strings.srAngleVertexWithAngleMeasure({
31170
+ x: srFormatNumber(vertex[X], locale),
31171
+ y: srFormatNumber(vertex[Y], locale),
31172
+ angleMeasure
31173
+ }) : strings.srAngleVertex({
31174
+ x: srFormatNumber(vertex[X], locale),
31175
+ y: srFormatNumber(vertex[Y], locale)
31176
+ });
31177
+ return {
31178
+ srAngleGraphAriaLabel,
31179
+ srAngleGraphAriaDescription,
31180
+ srAngleStartingSide,
31181
+ srAngleEndingSide,
31182
+ srAngleVertex
31183
+ };
31286
31184
  }
31287
31185
  const positiveX = [1, 0];
31288
31186
  const negativeX = [-1, 0];
@@ -31413,17 +31311,16 @@ function getRadius(graph) {
31413
31311
  return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
31414
31312
  }
31415
31313
 
31416
- function renderCircleGraph(state, dispatch) {
31314
+ function renderCircleGraph(state, dispatch, i18n) {
31417
31315
  return {
31418
31316
  graph: /*#__PURE__*/React.createElement(CircleGraph, {
31419
31317
  graphState: state,
31420
31318
  dispatch: dispatch
31421
31319
  }),
31422
- interactiveElementsDescription: /*#__PURE__*/React.createElement(CircleGraphDescription, {
31423
- state: state
31424
- })
31320
+ interactiveElementsDescription: getCircleGraphDescription(state, i18n)
31425
31321
  };
31426
31322
  }
31323
+ // Exported for testing
31427
31324
  function CircleGraph(props) {
31428
31325
  const {
31429
31326
  dispatch,
@@ -31516,6 +31413,7 @@ function MovableCircle(props) {
31516
31413
  snapStep,
31517
31414
  disableKeyboardInteraction
31518
31415
  } = useGraphConfig();
31416
+ const [focused, setFocused] = React.useState(false);
31519
31417
  const draggableRef = useRef(null);
31520
31418
  const {
31521
31419
  dragging
@@ -31534,7 +31432,9 @@ function MovableCircle(props) {
31534
31432
  ref: draggableRef,
31535
31433
  role: "button",
31536
31434
  tabIndex: disableKeyboardInteraction ? -1 : 0,
31537
- className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31435
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`,
31436
+ onFocus: () => setFocused(true),
31437
+ onBlur: () => setFocused(false)
31538
31438
  }, /*#__PURE__*/React.createElement("ellipse", {
31539
31439
  className: "focus-ring",
31540
31440
  cx: centerPx[X],
@@ -31549,19 +31449,29 @@ function MovableCircle(props) {
31549
31449
  rx: radiiPx[X],
31550
31450
  ry: radiiPx[Y]
31551
31451
  }), /*#__PURE__*/React.createElement(DragHandle, {
31552
- center: center
31452
+ center: center,
31453
+ dragging: dragging,
31454
+ focused: focused
31553
31455
  }));
31554
31456
  }
31555
31457
  const dragHandleDimensions = [24, 14];
31556
31458
  const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
31557
31459
  function DragHandle(props) {
31558
31460
  const {
31559
- center
31461
+ center,
31462
+ dragging,
31463
+ focused
31560
31464
  } = props;
31561
- const cornerRadius = Math.min(...dragHandleDimensions) / 2;
31562
31465
  const [centerPx] = useTransformVectorsToPixels(center);
31466
+ const {
31467
+ markings
31468
+ } = useGraphConfig();
31469
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
31563
31470
  const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
31564
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
31471
+ const showHairlines = (dragging || focused) && markings !== "none";
31472
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement(Hairlines, {
31473
+ point: center
31474
+ }), /*#__PURE__*/React.createElement("rect", {
31565
31475
  className: "movable-circle-handle",
31566
31476
  x: topLeft[X],
31567
31477
  y: topLeft[Y],
@@ -31588,13 +31498,7 @@ function crossProduct(as, bs) {
31588
31498
  }
31589
31499
  return result;
31590
31500
  }
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();
31501
+ function getCircleGraphDescription(state, i18n) {
31598
31502
  const strings = describeCircleGraph(state, i18n);
31599
31503
  return strings.srCircleInteractiveElement;
31600
31504
  }
@@ -31647,15 +31551,13 @@ function describeCircleGraph(state, i18n) {
31647
31551
  };
31648
31552
  }
31649
31553
 
31650
- function renderLinearGraph(state, dispatch) {
31554
+ function renderLinearGraph(state, dispatch, i18n) {
31651
31555
  return {
31652
31556
  graph: /*#__PURE__*/React.createElement(LinearGraph, {
31653
31557
  graphState: state,
31654
31558
  dispatch: dispatch
31655
31559
  }),
31656
- interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearGraphDescription, {
31657
- state: state
31658
- })
31560
+ interactiveElementsDescription: getLinearGraphDescription(state, i18n)
31659
31561
  };
31660
31562
  }
31661
31563
  const LinearGraph = (props, key) => {
@@ -31720,13 +31622,7 @@ const LinearGraph = (props, key) => {
31720
31622
  style: a11y.srOnly
31721
31623
  }, slopeString));
31722
31624
  };
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();
31625
+ function getLinearGraphDescription(state, i18n) {
31730
31626
  const strings = describeLinearGraph(state, i18n);
31731
31627
  return strings.srLinearInteractiveElement;
31732
31628
  }
@@ -31770,15 +31666,13 @@ function describeLinearGraph(state, i18n) {
31770
31666
  };
31771
31667
  }
31772
31668
 
31773
- function renderLinearSystemGraph(state, dispatch) {
31669
+ function renderLinearSystemGraph(state, dispatch, i18n) {
31774
31670
  return {
31775
31671
  graph: /*#__PURE__*/React.createElement(LinearSystemGraph, {
31776
31672
  graphState: state,
31777
31673
  dispatch: dispatch
31778
31674
  }),
31779
- interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearSystemGraphDescription, {
31780
- state: state
31781
- })
31675
+ interactiveElementsDescription: getLinearSystemGraphDescription(state, i18n)
31782
31676
  };
31783
31677
  }
31784
31678
  const LinearSystemGraph = props => {
@@ -31865,18 +31759,9 @@ const LinearSystemGraph = props => {
31865
31759
  style: a11y.srOnly
31866
31760
  }, slopeDescription))));
31867
31761
  };
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
31762
 
31878
31763
  // Exported for testing
31879
- function describeLinearSystemGraph(state, i18n) {
31764
+ function getLinearSystemGraphDescription(state, i18n) {
31880
31765
  const {
31881
31766
  strings,
31882
31767
  locale
@@ -31902,15 +31787,13 @@ function describeLinearSystemGraph(state, i18n) {
31902
31787
  });
31903
31788
  }
31904
31789
 
31905
- function renderPointGraph(state, dispatch) {
31790
+ function renderPointGraph(state, dispatch, i18n) {
31906
31791
  return {
31907
31792
  graph: /*#__PURE__*/React.createElement(PointGraph, {
31908
31793
  graphState: state,
31909
31794
  dispatch: dispatch
31910
31795
  }),
31911
- interactiveElementsDescription: /*#__PURE__*/React.createElement(PointGraphDescription, {
31912
- state: state
31913
- })
31796
+ interactiveElementsDescription: getPointGraphDescription(state, i18n)
31914
31797
  };
31915
31798
  }
31916
31799
  function PointGraph(props) {
@@ -31933,7 +31816,9 @@ function PointGraph(props) {
31933
31816
  var _pointsRef$current$fo;
31934
31817
  (_pointsRef$current$fo = pointsRef.current[focusedIndex]) == null || _pointsRef$current$fo.focus();
31935
31818
  }
31936
- }, [props.graphState.focusedPointIndex, pointsRef]);
31819
+ }, [props.graphState.focusedPointIndex,
31820
+ // Ensure that we re-focus if a point is deleted.
31821
+ props.graphState.coords.length, pointsRef]);
31937
31822
  const statefulProps = _extends({}, props, {
31938
31823
  graphConfig,
31939
31824
  pointsRef,
@@ -32004,17 +31889,9 @@ function UnlimitedPointGraph(statefulProps) {
32004
31889
  }
32005
31890
  })));
32006
31891
  }
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
31892
 
32016
31893
  // Exported for testing
32017
- function describePointGraph(state, i18n) {
31894
+ function getPointGraphDescription(state, i18n) {
32018
31895
  const {
32019
31896
  strings,
32020
31897
  locale
@@ -32101,7 +31978,9 @@ const PolygonGraph = props => {
32101
31978
  var _pointsRef$current$fo;
32102
31979
  (_pointsRef$current$fo = pointsRef.current[focusedIndex]) == null || _pointsRef$current$fo.focus();
32103
31980
  }
32104
- }, [props.graphState.focusedPointIndex, pointsRef]);
31981
+ }, [props.graphState.focusedPointIndex,
31982
+ // Ensure that we re-focus if a point is deleted.
31983
+ props.graphState.coords.length, pointsRef]);
32105
31984
 
32106
31985
  // If the unlimited polygon is rendered with 3 or more coordinates
32107
31986
  // Close the polygon, but only on first render.
@@ -32681,15 +32560,13 @@ function describeQuadraticGraph(state, i18n) {
32681
32560
  };
32682
32561
  }
32683
32562
 
32684
- function renderRayGraph(state, dispatch) {
32563
+ function renderRayGraph(state, dispatch, i18n) {
32685
32564
  return {
32686
32565
  graph: /*#__PURE__*/React.createElement(RayGraph, {
32687
32566
  graphState: state,
32688
32567
  dispatch: dispatch
32689
32568
  }),
32690
- interactiveElementsDescription: /*#__PURE__*/React.createElement(RayGraphDescription, {
32691
- state: state
32692
- })
32569
+ interactiveElementsDescription: getRayGraphDescription(state, i18n)
32693
32570
  };
32694
32571
  }
32695
32572
  const RayGraph = props => {
@@ -32743,13 +32620,7 @@ const RayGraph = props => {
32743
32620
  style: a11y.srOnly
32744
32621
  }, srRayPoints));
32745
32622
  };
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();
32623
+ function getRayGraphDescription(state, i18n) {
32753
32624
  const strings = describeRayGraph(state, i18n);
32754
32625
  return strings.srRayInteractiveElement;
32755
32626
  }
@@ -32799,15 +32670,13 @@ function describeRayGraph(state, i18n) {
32799
32670
  };
32800
32671
  }
32801
32672
 
32802
- function renderSegmentGraph(state, dispatch) {
32673
+ function renderSegmentGraph(state, dispatch, i18n) {
32803
32674
  return {
32804
32675
  graph: /*#__PURE__*/React.createElement(SegmentGraph, {
32805
32676
  graphState: state,
32806
32677
  dispatch: dispatch
32807
32678
  }),
32808
- interactiveElementsDescription: /*#__PURE__*/React.createElement(SegmentGraphDescription, {
32809
- state: state
32810
- })
32679
+ interactiveElementsDescription: getSegmentGraphDescription(state, i18n)
32811
32680
  };
32812
32681
  }
32813
32682
  const SegmentGraph = ({
@@ -32899,18 +32768,9 @@ const SegmentGraph = ({
32899
32768
  function getLengthOfSegment(segment) {
32900
32769
  return point.distanceToPoint(...segment);
32901
32770
  }
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
32771
 
32912
32772
  // Exported for testing
32913
- function describeSegmentGraph(state, i18n) {
32773
+ function getSegmentGraphDescription(state, i18n) {
32914
32774
  const {
32915
32775
  strings,
32916
32776
  locale
@@ -32927,13 +32787,13 @@ function describeSegmentGraph(state, i18n) {
32927
32787
  });
32928
32788
  }
32929
32789
 
32930
- function renderSinusoidGraph(state, dispatch) {
32790
+ function renderSinusoidGraph(state, dispatch, i18n) {
32931
32791
  return {
32932
32792
  graph: /*#__PURE__*/React.createElement(SinusoidGraph, {
32933
32793
  graphState: state,
32934
32794
  dispatch: dispatch
32935
32795
  }),
32936
- interactiveElementsDescription: null
32796
+ interactiveElementsDescription: getSinusoidDescription(state, i18n)
32937
32797
  };
32938
32798
  }
32939
32799
  function SinusoidGraph(props) {
@@ -32941,6 +32801,9 @@ function SinusoidGraph(props) {
32941
32801
  dispatch,
32942
32802
  graphState
32943
32803
  } = props;
32804
+ const i18n = usePerseusI18n();
32805
+ const id = React.useId();
32806
+ const descriptionId = id + "-description";
32944
32807
 
32945
32808
  // Destructure the coordinates from the graph state
32946
32809
  // Note: The order of the coordinates is important:
@@ -32965,15 +32828,30 @@ function SinusoidGraph(props) {
32965
32828
  if (coeffs !== undefined) {
32966
32829
  coeffRef.current = coeffs;
32967
32830
  }
32968
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
32831
+
32832
+ // Aria strings
32833
+ const {
32834
+ srSinusoidGraph,
32835
+ srSinusoidDescription,
32836
+ srSinusoidRootPoint,
32837
+ srSinusoidPeakPoint
32838
+ } = describeSinusoidGraph(graphState, i18n);
32839
+ return /*#__PURE__*/React.createElement("g", {
32840
+ // Outer graph minimal description
32841
+ "aria-label": srSinusoidGraph,
32842
+ "aria-describedby": descriptionId
32843
+ }, /*#__PURE__*/React.createElement(Plot$2.OfX, {
32969
32844
  y: x => computeSine(x, coeffRef.current),
32970
32845
  color: color.blue
32971
32846
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
32847
+ ariaLabel: i === 0 ? srSinusoidRootPoint : srSinusoidPeakPoint,
32972
32848
  key: "point-" + i,
32973
32849
  point: coord,
32974
32850
  sequenceNumber: i + 1,
32975
32851
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
32976
- })));
32852
+ })), /*#__PURE__*/React.createElement("g", {
32853
+ id: descriptionId
32854
+ }, srSinusoidDescription));
32977
32855
  }
32978
32856
 
32979
32857
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
@@ -33011,6 +32889,52 @@ const getSinusoidCoefficients$1 = coords => {
33011
32889
  verticalOffset
33012
32890
  };
33013
32891
  };
32892
+ function getSinusoidDescription(state, i18n) {
32893
+ const strings = describeSinusoidGraph(state, i18n);
32894
+ return strings.srSinusoidInteractiveElements;
32895
+ }
32896
+ function describeSinusoidGraph(state, i18n) {
32897
+ const {
32898
+ strings,
32899
+ locale
32900
+ } = i18n;
32901
+ const {
32902
+ coords
32903
+ } = state;
32904
+ const [root, peak] = coords;
32905
+ const diffX = Math.abs(peak[X] - root[X]);
32906
+ const diffY = Math.abs(peak[Y] - root[Y]);
32907
+ const srSinusoidGraph = strings.srSinusoidGraph;
32908
+ const srSinusoidDescription = strings.srSinusoidDescription({
32909
+ minValue: srFormatNumber(root[Y] - diffY, locale),
32910
+ maxValue: srFormatNumber(root[Y] + diffY, locale),
32911
+ cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
32912
+ cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
32913
+ });
32914
+ const srSinusoidRootPoint = strings.srSinusoidRootPoint({
32915
+ x: srFormatNumber(root[X], locale),
32916
+ y: srFormatNumber(root[Y], locale)
32917
+ });
32918
+ const srSinusoidPeakPoint = strings.srSinusoidPeakPoint({
32919
+ x: srFormatNumber(peak[X], locale),
32920
+ y: srFormatNumber(peak[Y], locale)
32921
+ });
32922
+ const srSinusoidInteractiveElements = strings.srInteractiveElements({
32923
+ elements: strings.srSinusoidInteractiveElements({
32924
+ point1X: srFormatNumber(root[X], locale),
32925
+ point1Y: srFormatNumber(root[Y], locale),
32926
+ point2X: srFormatNumber(peak[X], locale),
32927
+ point2Y: srFormatNumber(peak[Y], locale)
32928
+ })
32929
+ });
32930
+ return {
32931
+ srSinusoidGraph,
32932
+ srSinusoidDescription,
32933
+ srSinusoidRootPoint,
32934
+ srSinusoidPeakPoint,
32935
+ srSinusoidInteractiveElements
32936
+ };
32937
+ }
33014
32938
 
33015
32939
  const {
33016
32940
  calculateAngleInDegrees,
@@ -33181,6 +33105,7 @@ const MafsGraph = props => {
33181
33105
  const descriptionId = `interactive-graph-description-${uniqueId}`;
33182
33106
  const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
33183
33107
  const unlimitedGraphKeyboardPromptId = `unlimited-graph-keyboard-prompt-${uniqueId}`;
33108
+ const instructionsId = `instructions-${uniqueId}`;
33184
33109
  const graphRef = React.useRef(null);
33185
33110
  const {
33186
33111
  analytics
@@ -33208,6 +33133,7 @@ const MafsGraph = props => {
33208
33133
  const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
33209
33134
  useOnMountEffect(() => {
33210
33135
  analytics.onAnalyticsEvent({
33136
+ // TODO(LEMS-2827): Remove analytics event in LEMS-2827 in favor of ti below.
33211
33137
  type: "perseus:interactive-graph-widget:rendered",
33212
33138
  payload: {
33213
33139
  type,
@@ -33215,6 +33141,14 @@ const MafsGraph = props => {
33215
33141
  widgetId: "interactive-graph"
33216
33142
  }
33217
33143
  });
33144
+ analytics.onAnalyticsEvent({
33145
+ type: "perseus:widget:rendered:ti",
33146
+ payload: {
33147
+ widgetSubType: type,
33148
+ widgetType: "INTERACTIVE_GRAPH",
33149
+ widgetId: "interactive-graph"
33150
+ }
33151
+ });
33218
33152
  });
33219
33153
  const {
33220
33154
  graph,
@@ -33258,7 +33192,7 @@ const MafsGraph = props => {
33258
33192
  handleKeyboardEvent(event, state, dispatch);
33259
33193
  },
33260
33194
  "aria-label": fullGraphAriaLabel,
33261
- "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
33195
+ "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId, state.type !== "none" && instructionsId),
33262
33196
  ref: graphRef,
33263
33197
  tabIndex: 0,
33264
33198
  onFocus: event => {
@@ -33275,7 +33209,11 @@ const MafsGraph = props => {
33275
33209
  id: interactiveElementsDescriptionId,
33276
33210
  tabIndex: -1,
33277
33211
  className: "mafs-sr-only"
33278
- }, interactiveElementsDescription), /*#__PURE__*/React.createElement(LegacyGrid, {
33212
+ }, interactiveElementsDescription), state.type !== "none" && /*#__PURE__*/React.createElement(View, {
33213
+ id: instructionsId,
33214
+ tabIndex: -1,
33215
+ className: "mafs-sr-only"
33216
+ }, isUnlimitedGraphState(state) ? strings.srUnlimitedGraphInstructions : strings.srGraphInstructions), /*#__PURE__*/React.createElement(LegacyGrid, {
33279
33217
  box: props.box,
33280
33218
  backgroundImage: props.backgroundImage
33281
33219
  }), /*#__PURE__*/React.createElement(View, {
@@ -33284,7 +33222,9 @@ const MafsGraph = props => {
33284
33222
  bottom: 0,
33285
33223
  left: 0
33286
33224
  }
33287
- }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(View
33225
+ }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, {
33226
+ i18n: i18n
33227
+ })), /*#__PURE__*/React.createElement(View
33288
33228
  // If we have locked figures, we set aria-hidden
33289
33229
  // to false so that screen readers can read the
33290
33230
  // locked figures. If we don't have any locked
@@ -33622,25 +33562,25 @@ const renderGraphElements = props => {
33622
33562
  } = state;
33623
33563
  switch (type) {
33624
33564
  case "angle":
33625
- return renderAngleGraph(state, dispatch);
33565
+ return renderAngleGraph(state, dispatch, i18n);
33626
33566
  case "segment":
33627
- return renderSegmentGraph(state, dispatch);
33567
+ return renderSegmentGraph(state, dispatch, i18n);
33628
33568
  case "linear-system":
33629
- return renderLinearSystemGraph(state, dispatch);
33569
+ return renderLinearSystemGraph(state, dispatch, i18n);
33630
33570
  case "linear":
33631
- return renderLinearGraph(state, dispatch);
33571
+ return renderLinearGraph(state, dispatch, i18n);
33632
33572
  case "ray":
33633
- return renderRayGraph(state, dispatch);
33573
+ return renderRayGraph(state, dispatch, i18n);
33634
33574
  case "polygon":
33635
33575
  return renderPolygonGraph(state, dispatch, i18n, markings);
33636
33576
  case "point":
33637
- return renderPointGraph(state, dispatch);
33577
+ return renderPointGraph(state, dispatch, i18n);
33638
33578
  case "circle":
33639
- return renderCircleGraph(state, dispatch);
33579
+ return renderCircleGraph(state, dispatch, i18n);
33640
33580
  case "quadratic":
33641
33581
  return renderQuadraticGraph(state, dispatch, i18n);
33642
33582
  case "sinusoid":
33643
- return renderSinusoidGraph(state, dispatch);
33583
+ return renderSinusoidGraph(state, dispatch, i18n);
33644
33584
  case "none":
33645
33585
  return {
33646
33586
  graph: null,
@@ -34037,13 +33977,13 @@ const {
34037
33977
  polar
34038
33978
  } = angles;
34039
33979
  const {
34040
- angleMeasures: angleMeasures$1,
34041
- ccw: ccw$1,
34042
- lawOfCosines: lawOfCosines$1,
33980
+ angleMeasures,
33981
+ ccw,
33982
+ lawOfCosines,
34043
33983
  magnitude: magnitude$1,
34044
33984
  polygonSidesIntersect,
34045
33985
  reverseVector,
34046
- sign: sign$1,
33986
+ sign,
34047
33987
  vector: vector$1
34048
33988
  } = geometry;
34049
33989
  const {
@@ -34527,7 +34467,7 @@ function doAddPoint(state, action) {
34527
34467
  return _extends({}, state, {
34528
34468
  hasBeenInteractedWith: true,
34529
34469
  coords: newCoords,
34530
- showRemovePointButton: false,
34470
+ showRemovePointButton: true,
34531
34471
  focusedPointIndex: newCoords.length - 1
34532
34472
  });
34533
34473
  }
@@ -34535,16 +34475,11 @@ function doRemovePoint(state, action) {
34535
34475
  if (!isUnlimitedGraphState(state)) {
34536
34476
  return state;
34537
34477
  }
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
- }
34478
+ const nextFocusedPointIndex = state.coords.length > 1 ? state.coords.length - 2 : null;
34544
34479
  return _extends({}, state, {
34545
34480
  coords: state.coords.filter((_, i) => i !== action.index),
34546
34481
  focusedPointIndex: nextFocusedPointIndex,
34547
- showRemovePointButton: false
34482
+ showRemovePointButton: nextFocusedPointIndex !== null ? true : false
34548
34483
  });
34549
34484
  }
34550
34485
  const getDeltaVertex = (maxMoves, minMoves, delta) => {
@@ -34568,7 +34503,7 @@ const getChange = (coords, delta, constraintOpts) => {
34568
34503
  return [dx, dy];
34569
34504
  };
34570
34505
  // Less than or approximately equal
34571
- function leq$1(a, b) {
34506
+ function leq(a, b) {
34572
34507
  return a < b || approximateEqual(a, b);
34573
34508
  }
34574
34509
  function boundAndSnapToGrid(point, {
@@ -34713,7 +34648,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
34713
34648
  });
34714
34649
 
34715
34650
  // Gets the radian angles between the coords and maps them to degrees
34716
- const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
34651
+ const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
34717
34652
 
34718
34653
  // Gets the relative index of a point
34719
34654
  const rel = j => {
@@ -34733,7 +34668,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
34733
34668
 
34734
34669
  // Avoid degenerate triangles
34735
34670
  if (innerAngles.some(function (angle) {
34736
- return leq$1(angle, 1);
34671
+ return leq(angle, 1);
34737
34672
  })) {
34738
34673
  return startingPoint;
34739
34674
  }
@@ -34747,7 +34682,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
34747
34682
  // we want to add the inner angle to the outer angle, and if it's on the right,
34748
34683
  // we want to subtract the inner angle from the outer angle. The angle solved
34749
34684
  // 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;
34685
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34751
34686
 
34752
34687
  // Solve for side by using the law of sines
34753
34688
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
@@ -34794,13 +34729,13 @@ function boundAndSnapToSides(destinationPoint, {
34794
34729
  });
34795
34730
 
34796
34731
  // 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])) {
34732
+ if (leq(sides[1] + sides[2], sides[0]) || leq(sides[0] + sides[2], sides[1]) || leq(sides[0] + sides[1], sides[2])) {
34798
34733
  return startingPoint;
34799
34734
  }
34800
34735
 
34801
34736
  // Solve for angle by using the law of cosines
34802
34737
  // Angle at the first vertex of the polygon
34803
- const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
34738
+ const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
34804
34739
 
34805
34740
  // Angle at the second vertex of the polygon
34806
34741
  const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
@@ -34811,7 +34746,7 @@ function boundAndSnapToSides(destinationPoint, {
34811
34746
  // we want to add the inner angle to the outer angle, and if it's on the right,
34812
34747
  // we want to subtract the inner angle from the outer angle. The angle solved
34813
34748
  // 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;
34749
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34815
34750
 
34816
34751
  // Uses the length of the first side of the polygon (radial coordinate)
34817
34752
  // and the angle between the first and second sides of the
@@ -34946,19 +34881,11 @@ const {
34946
34881
  getClockwiseAngle
34947
34882
  } = angles;
34948
34883
  const {
34949
- angleMeasures,
34950
- ccw,
34951
- collinear,
34952
34884
  getLineEquation,
34953
34885
  getLineIntersection,
34954
- intersects,
34955
- lawOfCosines,
34956
34886
  magnitude,
34957
- rotate,
34958
- sign,
34959
34887
  vector
34960
34888
  } = geometry;
34961
- const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
34962
34889
  const defaultBackgroundImage = {
34963
34890
  url: null
34964
34891
  };
@@ -34967,31 +34894,9 @@ const UNLIMITED = "unlimited";
34967
34894
  // Sample background image:
34968
34895
  // https://ka-perseus-graphie.s3.amazonaws.com/29c1b0fcd17fe63df0f148fe357044d5d5c7d0bb.png
34969
34896
 
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
34897
  function numSteps(range, step) {
34985
34898
  return Math.floor((range[1] - range[0]) / step);
34986
34899
  }
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
34900
  const makeInvalidTypeError = (functionName, graphType) => {
34996
34901
  return new PerseusError(`${functionName} called but current graph type is not a '${graphType}'`, Errors.NotAllowed, {
34997
34902
  metadata: {
@@ -35032,1236 +34937,16 @@ function getQuadraticCoefficients(coords) {
35032
34937
  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
34938
  return [a, b, c];
35034
34939
  }
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
34940
  class InteractiveGraph extends React.Component {
36252
34941
  constructor(...args) {
36253
34942
  super(...args);
36254
- this.legacyGraphRef = /*#__PURE__*/React.createRef();
36255
34943
  this.mafsRef = /*#__PURE__*/React.createRef();
36256
34944
  }
36257
34945
  getUserInput() {
36258
- var _this$mafsRef$current, _this$legacyGraphRef$;
34946
+ var _this$mafsRef$current;
36259
34947
  if ((_this$mafsRef$current = this.mafsRef.current) != null && _this$mafsRef$current.getUserInput) {
36260
34948
  return this.mafsRef.current.getUserInput();
36261
34949
  }
36262
- if ((_this$legacyGraphRef$ = this.legacyGraphRef.current) != null && _this$legacyGraphRef$.getUserInput) {
36263
- return this.legacyGraphRef.current.getUserInput();
36264
- }
36265
34950
  throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
36266
34951
  }
36267
34952
  getPromptJSON() {
@@ -36456,8 +35141,32 @@ class InteractiveGraph extends React.Component {
36456
35141
  }
36457
35142
  static getEquationString(props) {
36458
35143
  const type = props.graph.type;
36459
- const funcName = "get" + capitalize(type) + "EquationString";
36460
- return InteractiveGraph[funcName](props);
35144
+ switch (type) {
35145
+ case "none":
35146
+ return InteractiveGraph.getNoneEquationString();
35147
+ case "linear":
35148
+ return InteractiveGraph.getLinearEquationString(props);
35149
+ case "quadratic":
35150
+ return InteractiveGraph.getQuadraticEquationString(props);
35151
+ case "sinusoid":
35152
+ return InteractiveGraph.getSinusoidEquationString(props);
35153
+ case "circle":
35154
+ return InteractiveGraph.getCircleEquationString(props);
35155
+ case "linear-system":
35156
+ return InteractiveGraph.getLinearSystemEquationString(props);
35157
+ case "point":
35158
+ return InteractiveGraph.getPointEquationString(props);
35159
+ case "segment":
35160
+ return InteractiveGraph.getSegmentEquationString(props);
35161
+ case "ray":
35162
+ return InteractiveGraph.getRayEquationString(props);
35163
+ case "polygon":
35164
+ return InteractiveGraph.getPolygonEquationString(props);
35165
+ case "angle":
35166
+ return InteractiveGraph.getAngleEquationString(props);
35167
+ default:
35168
+ throw new UnreachableCaseError(type);
35169
+ }
36461
35170
  }
36462
35171
  static pointsFromNormalized(props, coordsList, noSnap) {
36463
35172
  // @ts-expect-error - TS2322 - Type 'number[][]' is not assignable to type 'readonly Coord[]'.
@@ -36613,8 +35322,7 @@ var InteractiveGraph$1 = {
36613
35322
  name: "interactive-graph",
36614
35323
  displayName: "Interactive graph (Assessments only)",
36615
35324
  widget: InteractiveGraph,
36616
- staticTransform: staticTransform$3,
36617
- getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
35325
+ staticTransform: staticTransform$3
36618
35326
  };
36619
35327
 
36620
35328
  // TODO: can we use WB typography?
@@ -37241,11 +35949,16 @@ class LabelImage extends React.Component {
37241
35949
  });
37242
35950
  }
37243
35951
  activateMarker(index, opened) {
37244
- var _this$props$analytics;
35952
+ var _this$props$analytics, _this$props$analytics2;
35953
+ // TODO(LEMS-2830): Remove analytics event in LEMS-2830 in favor of ti below.
37245
35954
  (_this$props$analytics = this.props.analytics) == null || _this$props$analytics.onAnalyticsEvent({
37246
35955
  type: "perseus:label-image:marker-interacted-with",
37247
35956
  payload: null
37248
35957
  });
35958
+ (_this$props$analytics2 = this.props.analytics) == null || _this$props$analytics2.onAnalyticsEvent({
35959
+ type: "perseus:label-image:marker-interacted-with:ti",
35960
+ payload: null
35961
+ });
37249
35962
  const {
37250
35963
  activeMarkerIndex
37251
35964
  } = this.state;
@@ -37388,11 +36101,16 @@ class LabelImage extends React.Component {
37388
36101
  })),
37389
36102
  multipleSelect: this.props.multipleAnswers,
37390
36103
  onChange: selection => {
37391
- var _this$props$analytics2;
37392
- (_this$props$analytics2 = this.props.analytics) == null || _this$props$analytics2.onAnalyticsEvent({
36104
+ var _this$props$analytics3, _this$props$analytics4;
36105
+ // TODO(LEMS-2829): Remove analytics event in LEMS-2829 in favor of ti below.
36106
+ (_this$props$analytics3 = this.props.analytics) == null || _this$props$analytics3.onAnalyticsEvent({
37393
36107
  type: "perseus:label-image:choiced-interacted-with",
37394
36108
  payload: null
37395
36109
  });
36110
+ (_this$props$analytics4 = this.props.analytics) == null || _this$props$analytics4.onAnalyticsEvent({
36111
+ type: "perseus:label-image:choiced-interacted-with:ti",
36112
+ payload: null
36113
+ });
37396
36114
  this.handleAnswerChoicesChangeForMarker(index, selection);
37397
36115
  },
37398
36116
  onToggle: opened => this.activateMarker(index, opened)
@@ -37488,11 +36206,16 @@ class LabelImage extends React.Component {
37488
36206
  }))), this.renderMarkers()), /*#__PURE__*/React.createElement(HideAnswersToggle, {
37489
36207
  areAnswersHidden: this.state.hideAnswers,
37490
36208
  onChange: hideAnswers => {
37491
- var _this$props$analytics3;
37492
- (_this$props$analytics3 = this.props.analytics) == null || _this$props$analytics3.onAnalyticsEvent({
36209
+ var _this$props$analytics5, _this$props$analytics6;
36210
+ // TODO(LEMS-2831): Remove analytics event in LEMS-2831 in favor of ti below.
36211
+ (_this$props$analytics5 = this.props.analytics) == null || _this$props$analytics5.onAnalyticsEvent({
37493
36212
  type: "perseus:label-image:toggle-answers-hidden",
37494
36213
  payload: null
37495
36214
  });
36215
+ (_this$props$analytics6 = this.props.analytics) == null || _this$props$analytics6.onAnalyticsEvent({
36216
+ type: "perseus:label-image:toggle-answers-hidden:ti",
36217
+ payload: null
36218
+ });
37496
36219
  this.setState({
37497
36220
  hideAnswers
37498
36221
  });
@@ -37555,8 +36278,7 @@ var LabelImage$1 = {
37555
36278
  displayName: "Label Image",
37556
36279
  widget: LabelImageWithDependencies,
37557
36280
  accessible: true,
37558
- isLintable: true,
37559
- getPublicWidgetOptions: getLabelImagePublicWidgetOptions
36281
+ isLintable: true
37560
36282
  };
37561
36283
 
37562
36284
  var ItemState = /*#__PURE__*/function (ItemState) {
@@ -37811,20 +36533,24 @@ class Draggable extends React.Component {
37811
36533
  if (this.props.margin != null) {
37812
36534
  style.margin = this.props.margin;
37813
36535
  }
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
- }));
36536
+ return (
36537
+ /*#__PURE__*/
36538
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
36539
+ React.createElement("li", {
36540
+ className: className,
36541
+ style: style,
36542
+ onMouseDown: this.onMouseDown,
36543
+ onTouchStart: this.onMouseDown,
36544
+ onTouchMove: this.onMouseMove,
36545
+ onTouchEnd: this.onMouseUp,
36546
+ onTouchCancel: this.onMouseUp
36547
+ }, /*#__PURE__*/React.createElement(Renderer, {
36548
+ content: this.props.content,
36549
+ linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "draggable"),
36550
+ onRender: this.props.onRender,
36551
+ strings: this.context.strings
36552
+ }))
36553
+ );
37828
36554
  }
37829
36555
  }
37830
36556
  Draggable.contextType = PerseusI18nContext;
@@ -38894,8 +37620,7 @@ var Matrix$1 = {
38894
37620
  widget: Matrix,
38895
37621
  transform: propTransform$1,
38896
37622
  staticTransform: staticTransform$2,
38897
- isLintable: true,
38898
- getPublicWidgetOptions: getMatrixPublicWidgetOptions
37623
+ isLintable: true
38899
37624
  };
38900
37625
 
38901
37626
  const getPromptJSON$7 = () => {
@@ -40468,8 +39193,7 @@ var NumberLine$1 = {
40468
39193
  displayName: "Number line",
40469
39194
  widget: NumberLine,
40470
39195
  transform: numberLineTransform,
40471
- staticTransform: staticTransform$1,
40472
- getPublicWidgetOptions: getNumberLinePublicWidgetOptions
39196
+ staticTransform: staticTransform$1
40473
39197
  };
40474
39198
 
40475
39199
  const getPromptJSON$5 = (renderProps, userInput) => {
@@ -40637,19 +39361,23 @@ class Card extends React.Component {
40637
39361
  // Pull out the content to get rendered
40638
39362
  const rendererProps = _.pick(this.props, "content");
40639
39363
  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
- }))));
39364
+ return (
39365
+ /*#__PURE__*/
39366
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
39367
+ React.createElement("div", {
39368
+ className: "card-wrap",
39369
+ style: style,
39370
+ onMouseDown: onMouseDown,
39371
+ onTouchStart: onMouseDown,
39372
+ onTouchEnd: this.onMouseUp,
39373
+ onTouchCancel: this.onMouseUp
39374
+ }, /*#__PURE__*/React.createElement("div", {
39375
+ className: className.join(" ")
39376
+ }, /*#__PURE__*/React.createElement(Renderer, _extends({}, rendererProps, {
39377
+ linterContext: this.props.linterContext,
39378
+ strings: this.context.strings
39379
+ }))))
39380
+ );
40653
39381
  }
40654
39382
  }
40655
39383
  Card.contextType = PerseusI18nContext;
@@ -41029,8 +39757,7 @@ var Orderer$1 = {
41029
39757
  displayName: "Orderer",
41030
39758
  hidden: true,
41031
39759
  widget: Orderer,
41032
- isLintable: true,
41033
- getPublicWidgetOptions: getOrdererPublicWidgetOptions
39760
+ isLintable: true
41034
39761
  };
41035
39762
 
41036
39763
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -42090,8 +40817,7 @@ var Plotter$1 = {
42090
40817
  displayName: "Plotter",
42091
40818
  hidden: true,
42092
40819
  widget: Plotter,
42093
- staticTransform: staticTransform,
42094
- getPublicWidgetOptions: getPlotterPublicWidgetOptions
40820
+ staticTransform: staticTransform
42095
40821
  };
42096
40822
 
42097
40823
  const getPromptJSON$2 = () => {
@@ -42253,8 +40979,7 @@ var Sorter$1 = {
42253
40979
  name: "sorter",
42254
40980
  displayName: "Sorter",
42255
40981
  widget: Sorter,
42256
- isLintable: true,
42257
- getPublicWidgetOptions: getSorterPublicWidgetOptions
40982
+ isLintable: true
42258
40983
  };
42259
40984
 
42260
40985
  const {
@@ -42473,8 +41198,7 @@ var Table$1 = {
42473
41198
  widget: Table,
42474
41199
  transform: propTransform,
42475
41200
  hidden: true,
42476
- isLintable: true,
42477
- getPublicWidgetOptions: getTablePublicWidgetOptions
41201
+ isLintable: true
42478
41202
  };
42479
41203
 
42480
41204
  const getPromptJSON = () => {
@@ -42653,7 +41377,7 @@ const init = function init() {
42653
41377
 
42654
41378
  // This file is processed by a Rollup plugin (replace) to inject the production
42655
41379
  const libName = "@khanacademy/perseus";
42656
- const libVersion = "53.1.0";
41380
+ const libVersion = "54.0.0";
42657
41381
  addLibraryVersionToPerseusDebug(libName, libVersion);
42658
41382
 
42659
41383
  const apiVersion = {
@@ -44140,6 +42864,18 @@ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
44140
42864
  const widget = widgetMap[WidgetId];
44141
42865
  return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
44142
42866
  }
42867
+ function getWidgetSubTypeByWidgetId(widgetId, widgetMap) {
42868
+ var _widget$type2, _graph$type;
42869
+ const widget = widgetMap[widgetId];
42870
+ const widgetType = (_widget$type2 = widget == null ? void 0 : widget.type) != null ? _widget$type2 : null;
42871
+ switch (widgetType) {
42872
+ case "interactive-graph":
42873
+ const graph = widget.options.graph;
42874
+ return (_graph$type = graph == null ? void 0 : graph.type) != null ? _graph$type : null;
42875
+ default:
42876
+ return null;
42877
+ }
42878
+ }
44143
42879
 
44144
42880
  /**
44145
42881
  * Does the content have a specific type of widget?
@@ -44932,5 +43668,5 @@ const ScoringUtil = {
44932
43668
  * Types
44933
43669
  */
44934
43670
 
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 };
43671
+ 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
43672
  //# sourceMappingURL=index.js.map