@khanacademy/perseus 24.2.0 → 25.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 (40) hide show
  1. package/dist/es/index.css +7 -0
  2. package/dist/es/index.js +845 -377
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.css +7 -0
  5. package/dist/index.d.ts +3 -4
  6. package/dist/index.js +927 -449
  7. package/dist/index.js.map +1 -1
  8. package/dist/logging/log.d.ts +1 -41
  9. package/dist/perseus-api.d.ts +1 -0
  10. package/dist/perseus-types.d.ts +17 -2
  11. package/dist/server-item-renderer.d.ts +2 -1
  12. package/dist/types.d.ts +1 -1
  13. package/dist/widgets/expression.d.ts +8 -2
  14. package/dist/widgets/interactive-graph.d.ts +1 -1
  15. package/dist/widgets/interactive-graphs/graphs/angle.d.ts +5 -0
  16. package/dist/widgets/interactive-graphs/graphs/components/{angle.d.ts → angle-indicators.d.ts} +12 -2
  17. package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +11 -0
  18. package/dist/widgets/interactive-graphs/graphs/components/text-label.d.ts +1 -1
  19. package/dist/widgets/interactive-graphs/graphs/index.d.ts +1 -0
  20. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +0 -3
  21. package/dist/widgets/interactive-graphs/index.d.ts +1 -1
  22. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +3 -1
  23. package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +4 -0
  24. package/dist/widgets/interactive-graphs/mafs-graph.d.ts +0 -19
  25. package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +1 -1
  26. package/dist/widgets/interactive-graphs/math/angles.d.ts +5 -0
  27. package/dist/widgets/interactive-graphs/math/box.d.ts +4 -0
  28. package/dist/widgets/interactive-graphs/math/clamp.d.ts +1 -0
  29. package/dist/widgets/interactive-graphs/math/coordinates.d.ts +2 -0
  30. package/dist/widgets/interactive-graphs/math/geometry.d.ts +3 -0
  31. package/dist/widgets/interactive-graphs/math/index.d.ts +8 -0
  32. package/dist/widgets/interactive-graphs/math/interpolation.d.ts +1 -0
  33. package/dist/widgets/interactive-graphs/math/interval.d.ts +5 -0
  34. package/dist/widgets/interactive-graphs/math/snap.d.ts +2 -0
  35. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +21 -0
  36. package/dist/widgets/interactive-graphs/types.d.ts +9 -1
  37. package/dist/widgets/interactive-graphs/utils.d.ts +0 -2
  38. package/dist/widgets/label-image.d.ts +2 -0
  39. package/package.json +8 -8
  40. package/dist/perseus-error.d.ts +0 -11
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var KAS = require('@khanacademy/kas');
6
6
  var mathInput = require('@khanacademy/math-input');
7
+ var perseusCore = require('@khanacademy/perseus-core');
7
8
  var PerseusLinter = require('@khanacademy/perseus-linter');
8
9
  var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
9
10
  var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
@@ -40,7 +41,6 @@ var Switch = require('@khanacademy/wonder-blocks-switch');
40
41
  var Pill = require('@khanacademy/wonder-blocks-pill');
41
42
  var reactPopper = require('react-popper');
42
43
  var Link = require('@khanacademy/wonder-blocks-link');
43
- var perseusCore = require('@khanacademy/perseus-core');
44
44
 
45
45
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
46
46
 
@@ -803,52 +803,9 @@ var dependencies = /*#__PURE__*/Object.freeze({
803
803
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
804
804
  // getDependencies() internally... simplify usage to just using a "singleton"
805
805
  // which get's the injected logger.
806
- /**
807
- * @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
808
- */
809
- const Errors = Object.freeze({
810
- /**
811
- * @property {ErrorKind} Unknown The kind of error is not known.
812
- */
813
- Unknown: "Unknown",
814
- /**
815
- * @property {ErrorKind} Internal The error is internal to the executing code.
816
- */
817
- Internal: "Internal",
818
- /**
819
- * @property {ErrorKind} InvalidInput There was a problem with the provided
820
- * input, such as the wrong format or a null value.
821
- */
822
- InvalidInput: "InvalidInput",
823
- /**
824
- * @property {ErrorKind} NotAllowed There was a problem due to the state of
825
- * the system not matching the requested operation or input. For example,
826
- * trying to create a username that is valid, but is already taken by
827
- * another user. Use {@link InvalidInput} instead when the input isn't
828
- * valid regardless of the state of the system. Use {@link NotFound} when
829
- * the failure is due to not being able to find a resource.
830
- */
831
- NotAllowed: "NotAllowed",
832
- /**
833
- * @property {ErrorKind} TransientService There was a problem when making a
834
- * request to a service.
835
- */
836
- TransientService: "TransientService",
837
- /**
838
- * @property {ErrorKind} Service There was a non-transient problem when
839
- * making a request to service.
840
- */
841
- Service: "Service"
842
- });
843
-
844
- /**
845
- * @type {ErrorKind} The kind of error being reported
846
- */
847
-
848
806
  /**
849
807
  * Provides logging infrastructure for Perseus
850
808
  */
851
-
852
809
  const Log = {
853
810
  // Logs a message
854
811
  log: (message, extra) => {
@@ -860,16 +817,6 @@ const Log = {
860
817
  }
861
818
  };
862
819
 
863
- class PerseusError extends Error {
864
- kind;
865
- metadata;
866
- constructor(message, kind, options) {
867
- super(message);
868
- this.kind = kind;
869
- this.metadata = options?.metadata;
870
- }
871
- }
872
-
873
820
  /**
874
821
  * These are things that widgets should exclude when serializing themselves.
875
822
  *
@@ -973,9 +920,9 @@ const change = function (newPropsOrSinglePropName, propValue, callback) {
973
920
  // string propName
974
921
  propValue, callback);
975
922
  }
976
- throw new PerseusError("Invalid types sent to this.change(): " +
923
+ throw new perseusCore.PerseusError("Invalid types sent to this.change(): " +
977
924
  // eslint-disable-next-line prefer-rest-params
978
- ___default["default"].toArray(arguments).join() + "\n" + USAGE, Errors.Internal);
925
+ ___default["default"].toArray(arguments).join() + "\n" + USAGE, perseusCore.Errors.Internal);
979
926
  };
980
927
  const propTypes = {
981
928
  onChange: PropTypes__default["default"].func.isRequired
@@ -2148,9 +2095,9 @@ const KhanAnswerTypes = {
2148
2095
  parseSolution: function (solutionString, options) {
2149
2096
  let solution = KAS__namespace.parse(solutionString, options);
2150
2097
  if (!solution.parsed) {
2151
- throw new PerseusError("The provided solution (" + solutionString + ") didn't parse.", Errors.InvalidInput);
2098
+ throw new perseusCore.PerseusError("The provided solution (" + solutionString + ") didn't parse.", perseusCore.Errors.InvalidInput);
2152
2099
  } else if (options.simplified && !solution.expr.isSimplified()) {
2153
- throw new PerseusError("The provided solution (" + solutionString + ") isn't fully expanded and simplified.", Errors.InvalidInput);
2100
+ throw new perseusCore.PerseusError("The provided solution (" + solutionString + ") isn't fully expanded and simplified.", perseusCore.Errors.InvalidInput);
2154
2101
  } else {
2155
2102
  solution = solution.expr;
2156
2103
  }
@@ -2341,7 +2288,7 @@ class Expression extends React__namespace.Component {
2341
2288
  // in the function variables list for the expression.
2342
2289
  if (!expression.parsed) {
2343
2290
  /* c8 ignore next */
2344
- Log.error("Unable to parse solution answer for expression", Errors.InvalidInput, {
2291
+ Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
2345
2292
  loggedMetadata: {
2346
2293
  rubric: JSON.stringify(rubric)
2347
2294
  }
@@ -3032,7 +2979,7 @@ function combineScores(scoreA, scoreB) {
3032
2979
  * The above checks cover all combinations of score type, so if we get here
3033
2980
  * then something is amiss with our inputs.
3034
2981
  */
3035
- throw new PerseusError("PerseusScore with unknown type encountered", Errors.InvalidInput, {
2982
+ throw new perseusCore.PerseusError("PerseusScore with unknown type encountered", perseusCore.Errors.InvalidInput, {
3036
2983
  metadata: {
3037
2984
  scoreA: JSON.stringify(scoreA),
3038
2985
  scoreB: JSON.stringify(scoreB)
@@ -3062,9 +3009,9 @@ function keScoreFromPerseusScore(score, guess, state) {
3062
3009
  state: state
3063
3010
  };
3064
3011
  }
3065
- throw new PerseusError(
3012
+ throw new perseusCore.PerseusError(
3066
3013
  // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
3067
- "Invalid score type: " + score.type, Errors.InvalidInput, {
3014
+ "Invalid score type: " + score.type, perseusCore.Errors.InvalidInput, {
3068
3015
  metadata: {
3069
3016
  score: JSON.stringify(score),
3070
3017
  guess: JSON.stringify(guess),
@@ -4433,7 +4380,7 @@ const MovableHelperMethods = {
4433
4380
  return memo;
4434
4381
  }
4435
4382
  // Anything else is an error
4436
- throw new PerseusError("Constraint returned invalid result: " + result, Errors.Internal);
4383
+ throw new perseusCore.PerseusError("Constraint returned invalid result: " + result, perseusCore.Errors.Internal);
4437
4384
  }, current, this);
4438
4385
  },
4439
4386
  /**
@@ -4539,7 +4486,7 @@ const FUNCTION_ARRAY_OPTIONS = ["add", "modify", "draw", "remove", "constraints"
4539
4486
  const InteractiveUtil = {
4540
4487
  assert: function (isTrue, message) {
4541
4488
  if (!isTrue) {
4542
- throw new PerseusError("Assertion Error" + (message ? ": " + message : ""), Errors.Internal);
4489
+ throw new perseusCore.PerseusError("Assertion Error" + (message ? ": " + message : ""), perseusCore.Errors.Internal);
4543
4490
  }
4544
4491
  },
4545
4492
  /**
@@ -9068,7 +9015,7 @@ var Tex$1 = {
9068
9015
  const {
9069
9016
  processMath: processMath$1
9070
9017
  } = Tex$1;
9071
- function polar(r, th) {
9018
+ function polar$1(r, th) {
9072
9019
  if (typeof r === "number") {
9073
9020
  r = [r, r];
9074
9021
  }
@@ -9120,7 +9067,7 @@ class Graphie$1 {
9120
9067
  // TODO(jeremy): This can be removed once we're confident that callers
9121
9068
  // obey the Typescript types.
9122
9069
  if (options.range == null) {
9123
- throw new PerseusError("range should be specified in graph init", Errors.Internal);
9070
+ throw new perseusCore.PerseusError("range should be specified in graph init", perseusCore.Errors.Internal);
9124
9071
  }
9125
9072
  this.#bounds = new GraphBounds(...options.range);
9126
9073
  this.#drawingTransform = new DrawingTransform(this.raphael, scale, this.bounds());
@@ -9404,8 +9351,8 @@ class Graphie$1 {
9404
9351
  endAngle = (endAngle % 360 + 360) % 360;
9405
9352
  const cent = this.scalePoint(center);
9406
9353
  const radii = this.scaleVector(radius);
9407
- const startVector = polar(radius, startAngle);
9408
- const endVector = polar(radius, endAngle);
9354
+ const startVector = polar$1(radius, startAngle);
9355
+ const endVector = polar$1(radius, endAngle);
9409
9356
 
9410
9357
  // We round the coordinates to make testing easier, because trig
9411
9358
  // operations return unround numbers.
@@ -13707,7 +13654,7 @@ function MovableAngle(graphie, options) {
13707
13654
 
13708
13655
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
13709
13656
  if (!this.points || this.points.length !== 3) {
13710
- throw new PerseusError("MovableAngle requires 3 points", Errors.InvalidInput);
13657
+ throw new perseusCore.PerseusError("MovableAngle requires 3 points", perseusCore.Errors.InvalidInput);
13711
13658
  }
13712
13659
 
13713
13660
  // Handle coordinates that are not MovablePoints (i.e. [2, 4])
@@ -13834,7 +13781,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13834
13781
  let angle = GraphUtils.findAngle(newPoint, vertex);
13835
13782
  angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
13836
13783
  const distance = GraphUtils.getDistance(newPoint, vertex);
13837
- return kmath.vector.add(vertex, polar(distance, angle));
13784
+ return kmath.vector.add(vertex, polar$1(distance, angle));
13838
13785
  }
13839
13786
  return true;
13840
13787
  };
@@ -13998,9 +13945,9 @@ function GraphieMovable$1(descriptor) {
13998
13945
  ___default["default"].extend(this, descriptor);
13999
13946
  }
14000
13947
  const abstractMethod = function () {
14001
- throw new PerseusError("Abstract method! Must be implemented by Graphie Movable" +
13948
+ throw new perseusCore.PerseusError("Abstract method! Must be implemented by Graphie Movable" +
14002
13949
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
14003
- this.constructor.displayName, Errors.NotAllowed);
13950
+ this.constructor.displayName, perseusCore.Errors.NotAllowed);
14004
13951
  };
14005
13952
  ___default["default"].extend(GraphieMovable$1.prototype, {
14006
13953
  movableProps: [],
@@ -14040,7 +13987,7 @@ const createClass = function (spec) {
14040
13987
  const GraphieClass = function (props) {
14041
13988
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
14042
13989
  if (!(this instanceof GraphieClass)) {
14043
- throw new PerseusError("Use createElement or JSX with graphie movables", Errors.NotAllowed);
13990
+ throw new perseusCore.PerseusError("Use createElement or JSX with graphie movables", perseusCore.Errors.NotAllowed);
14044
13991
  }
14045
13992
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
14046
13993
  this.props = rewriteProps(props, props.children || []);
@@ -14359,7 +14306,7 @@ class Movable {
14359
14306
  return memo;
14360
14307
  }
14361
14308
  // Anything else is an error
14362
- throw new PerseusError("Constraint returned invalid result: " + result, Errors.Internal);
14309
+ throw new perseusCore.PerseusError("Constraint returned invalid result: " + result, perseusCore.Errors.Internal);
14363
14310
  }, current);
14364
14311
  }
14365
14312
 
@@ -15709,7 +15656,7 @@ class MovablePoint$4 {
15709
15656
  return memo;
15710
15657
  }
15711
15658
  // Anything else is an error
15712
- throw new PerseusError("Constraint returned invalid result: " + result, Errors.Internal);
15659
+ throw new perseusCore.PerseusError("Constraint returned invalid result: " + result, perseusCore.Errors.Internal);
15713
15660
  }, current);
15714
15661
  }
15715
15662
 
@@ -16794,7 +16741,7 @@ const Path = GraphieClasses.createClass({
16794
16741
  this.path.toFront();
16795
16742
  }
16796
16743
  });
16797
- const Arc = GraphieClasses.createSimpleClass((graphie, props) => {
16744
+ const Arc$1 = GraphieClasses.createSimpleClass((graphie, props) => {
16798
16745
  let center = props.center;
16799
16746
  let radius = props.radius;
16800
16747
  if (props.unscaled) {
@@ -16810,7 +16757,7 @@ const Rect$1 = GraphieClasses.createSimpleClass((graphie, props) => {
16810
16757
  return graphie.rect(props.x, props.y, props.width, props.height, props.style);
16811
16758
  });
16812
16759
  var Movables = {
16813
- Arc: Arc,
16760
+ Arc: Arc$1,
16814
16761
  Circle: Circle,
16815
16762
  Label: Label$1,
16816
16763
  Line: Line$3,
@@ -16864,7 +16811,7 @@ class Graphie extends React__namespace.Component {
16864
16811
  // would have horrible performance implications. In order to avoid
16865
16812
  // those, we just warn here.
16866
16813
  if (this.props.setup !== prevProps.setup) {
16867
- Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", Errors.Internal);
16814
+ Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", perseusCore.Errors.Internal);
16868
16815
  }
16869
16816
  if (!deepEq$2(this.props.options, prevProps.options) || !deepEq$2(this.props.box, prevProps.box) || !deepEq$2(this.props.range, prevProps.range)) {
16870
16817
  this._setupGraphie();
@@ -17043,7 +16990,7 @@ class Graphie extends React__namespace.Component {
17043
16990
  // This generally is a bad idea, so warn about it if this
17044
16991
  // is being caused by implicit keys
17045
16992
  if (keyProp == null) {
17046
- Log.error("Replacing a <Graphie> child with a " + "child of a different type. Please add keys " + "to your <Graphie> children", Errors.Internal);
16993
+ Log.error("Replacing a <Graphie> child with a " + "child of a different type. Please add keys " + "to your <Graphie> children", perseusCore.Errors.Internal);
17047
16994
  }
17048
16995
  prevMovable.remove();
17049
16996
  child.add(graphie);
@@ -17320,7 +17267,7 @@ function getLocalizedDataUrl(url) {
17320
17267
  function getUrlHash(url) {
17321
17268
  const match = url.match(hashRegex);
17322
17269
  if (match == null) {
17323
- throw new PerseusError("not a valid URL", Errors.InvalidInput);
17270
+ throw new perseusCore.PerseusError("not a valid URL", perseusCore.Errors.InvalidInput);
17324
17271
  }
17325
17272
  return match && match[1];
17326
17273
  }
@@ -17457,7 +17404,7 @@ class SvgImage extends React__namespace.Component {
17457
17404
  // If there is isn't any localized data, fall back to
17458
17405
  // the original, unlocalized data
17459
17406
  retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
17460
- Log.error("Data load failed for svg-image", Errors.Service, {
17407
+ Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
17461
17408
  cause: error,
17462
17409
  loggedMetadata: {
17463
17410
  dataUrl: Util.getDataUrl(this.props.src),
@@ -17468,7 +17415,7 @@ class SvgImage extends React__namespace.Component {
17468
17415
  });
17469
17416
  } else {
17470
17417
  retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
17471
- Log.error("Data load failed for svg-image", Errors.Service, {
17418
+ Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
17472
17419
  cause: error,
17473
17420
  loggedMetadata: {
17474
17421
  dataUrl: Util.getDataUrl(this.props.src),
@@ -17720,7 +17667,7 @@ class SvgImage extends React__namespace.Component {
17720
17667
  } else if (this.state.imageDimensions) {
17721
17668
  box = [this.state.imageDimensions[0] * this.props.scale, this.state.imageDimensions[1] * this.props.scale];
17722
17669
  } else {
17723
- throw new PerseusError("svg-image has no dimensions", Errors.InvalidInput, {
17670
+ throw new perseusCore.PerseusError("svg-image has no dimensions", perseusCore.Errors.InvalidInput, {
17724
17671
  metadata: {
17725
17672
  src: this.props.src
17726
17673
  }
@@ -18148,7 +18095,7 @@ class ErrorBoundary extends React__namespace.Component {
18148
18095
  // differentiate between the different errors that are thrown, they
18149
18096
  // all end up being grouped as a single Sentry event, which is very
18150
18097
  // unhelpful.
18151
- "Unhandled Perseus error: " + error.message, Errors.Internal, {
18098
+ "Unhandled Perseus error: " + error.message, perseusCore.Errors.Internal, {
18152
18099
  cause: error,
18153
18100
  loggedMetadata: {
18154
18101
  componentStack: !!info && "componentStack" in info ? info.componentStack : "componentStack not provided",
@@ -19201,7 +19148,7 @@ const replaceWidget = (name, replacementName) => {
19201
19148
  // that doesn't exist at all.
19202
19149
  if (!substituteWidget) {
19203
19150
  const errorMsg = `Failed to replace ${name} with ${replacementName}`;
19204
- throw new PerseusError(errorMsg, Errors.Internal);
19151
+ throw new perseusCore.PerseusError(errorMsg, perseusCore.Errors.Internal);
19205
19152
  }
19206
19153
  registerWidget(name, substituteWidget);
19207
19154
  };
@@ -19215,7 +19162,7 @@ const replaceDeprecatedWidgets = () => {
19215
19162
  const registerEditors = editorsToRegister => {
19216
19163
  editorsToRegister.forEach(editor => {
19217
19164
  if (!editor.widgetName) {
19218
- throw new PerseusError(`Editor ${editor.displayName} doesn't have a widgetName property`, Errors.Internal);
19165
+ throw new perseusCore.PerseusError(`Editor ${editor.displayName} doesn't have a widgetName property`, perseusCore.Errors.Internal);
19219
19166
  }
19220
19167
  editors[editor.widgetName] = editor;
19221
19168
  });
@@ -19233,7 +19180,7 @@ const replaceEditor = (name, replacementName) => {
19233
19180
  const substituteEditor = editors[replacementName];
19234
19181
  if (!substituteEditor && Log) {
19235
19182
  const errorMsg = `Failed to replace editor ${name} with ${replacementName}`;
19236
- Log.error(errorMsg, Errors.Internal);
19183
+ Log.error(errorMsg, perseusCore.Errors.Internal);
19237
19184
  return;
19238
19185
  }
19239
19186
  editors[name] = substituteEditor;
@@ -19307,7 +19254,7 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
19307
19254
  // suspect we have legacy data (potentially unpublished) and we should
19308
19255
  // figure that out before depending solely on types.
19309
19256
  if (!___default["default"].isString(type)) {
19310
- throw new PerseusError("widget type must be a string, but was: " + type, Errors.Internal);
19257
+ throw new perseusCore.PerseusError("widget type must be a string, but was: " + type, perseusCore.Errors.Internal);
19311
19258
  }
19312
19259
  const widgetExports = widgets[type];
19313
19260
  if (widgetExports == null) {
@@ -19358,7 +19305,7 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
19358
19305
  // This is a Log.error because it is unlikely to be hit in
19359
19306
  // local testing, and a Log.error is slightly less scary in
19360
19307
  // prod than a `throw new Error`
19361
- Log.error("No upgrade found for widget. Cannot render.", Errors.Internal, {
19308
+ Log.error("No upgrade found for widget. Cannot render.", perseusCore.Errors.Internal, {
19362
19309
  loggedMetadata: {
19363
19310
  type,
19364
19311
  fromMajorVersion: nextVersion - 1,
@@ -19431,7 +19378,7 @@ const getRendererPropsForWidgetInfo = (widgetInfo, strings, problemNum) => {
19431
19378
  };
19432
19379
  const traverseChildWidgets$3 = (widgetInfo, traverseRenderer) => {
19433
19380
  if (!traverseRenderer) {
19434
- throw new PerseusError("traverseRenderer must be provided, but was not", Errors.Internal);
19381
+ throw new perseusCore.PerseusError("traverseRenderer must be provided, but was not", perseusCore.Errors.Internal);
19435
19382
  }
19436
19383
  if (!widgetInfo || !widgetInfo.type || !widgets[widgetInfo.type]) {
19437
19384
  return widgetInfo;
@@ -19500,12 +19447,12 @@ const validAlignments = ["block", "inline-block", "inline", "float-left", "float
19500
19447
  const validateAlignments = () => {
19501
19448
  ___default["default"].each(widgets, function (widgetInfo) {
19502
19449
  if (widgetInfo.defaultAlignment && !___default["default"].contains(validAlignments, widgetInfo.defaultAlignment)) {
19503
- throw new PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid defaultAlignment value: " + widgetInfo.defaultAlignment, Errors.InvalidInput);
19450
+ throw new perseusCore.PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid defaultAlignment value: " + widgetInfo.defaultAlignment, perseusCore.Errors.InvalidInput);
19504
19451
  }
19505
19452
  if (widgetInfo.supportedAlignments) {
19506
19453
  const unknownAlignments = ___default["default"].difference(widgetInfo.supportedAlignments, validAlignments);
19507
19454
  if (unknownAlignments.length) {
19508
- throw new PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid value for supportedAlignments: " + unknownAlignments.join(" "), Errors.InvalidInput);
19455
+ throw new perseusCore.PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid value for supportedAlignments: " + unknownAlignments.join(" "), perseusCore.Errors.InvalidInput);
19509
19456
  }
19510
19457
  }
19511
19458
  });
@@ -20131,7 +20078,7 @@ class Renderer extends React__namespace.Component {
20131
20078
 
20132
20079
  // If the two lists of IDs match (ignoring order)
20133
20080
  if (serializedWidgetIds.length !== widgetPropIds.length || ___default["default"].intersection(serializedWidgetIds, widgetPropIds).length !== serializedWidgetIds.length) {
20134
- Log.error("Refusing to restore bad serialized state:", Errors.Internal, {
20081
+ Log.error("Refusing to restore bad serialized state:", perseusCore.Errors.Internal, {
20135
20082
  loggedMetadata: {
20136
20083
  serializedState: JSON.stringify(serializedState),
20137
20084
  currentProps: JSON.stringify(this.state.widgetProps)
@@ -20289,7 +20236,7 @@ class Renderer extends React__namespace.Component {
20289
20236
  return function (id) {
20290
20237
  let focusPath = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
20291
20238
  if (!___default["default"].isArray(focusPath)) {
20292
- throw new PerseusError("widget props.onFocus focusPath must be an Array, " + "but was" + JSON.stringify(focusPath), Errors.Internal);
20239
+ throw new perseusCore.PerseusError("widget props.onFocus focusPath must be an Array, " + "but was" + JSON.stringify(focusPath), perseusCore.Errors.Internal);
20293
20240
  }
20294
20241
  _this2._setCurrentFocus([id].concat(focusPath));
20295
20242
  };
@@ -20773,7 +20720,7 @@ class Renderer extends React__namespace.Component {
20773
20720
  // object return value.
20774
20721
  // The result of focus was a {path, id} object itself
20775
20722
  path = [id].concat(focusResult.path || []);
20776
- Log.error("Renderer received a focus result of type 'object' " + "instead of the expected type 'boolean'", Errors.Internal, {
20723
+ Log.error("Renderer received a focus result of type 'object' " + "instead of the expected type 'boolean'", perseusCore.Errors.Internal, {
20777
20724
  loggedMetadata: {
20778
20725
  focusResult: JSON.stringify(focusResult),
20779
20726
  currentProps: JSON.stringify(this.state.widgetProps)
@@ -28657,6 +28604,129 @@ const useTransformDimensionsToPixels = function () {
28657
28604
  return dimensionsToPixels(dimens, graphState);
28658
28605
  };
28659
28606
 
28607
+ // Restricts `value` to be between `min` and `max`.
28608
+ function clamp(value, min, max) {
28609
+ if (value < min) {
28610
+ return min;
28611
+ }
28612
+ if (value > max) {
28613
+ return max;
28614
+ }
28615
+ return value;
28616
+ }
28617
+
28618
+ // Rounds each component of `point` to the nearest multiple of the
28619
+ // corresponding component of `snapStep`. E.g. if the snap step is [2, 3],
28620
+ // the x-coord of the point will be rounded to the nearest multiple of 2, and
28621
+ // the y-coord of the point will be rounded to the nearest multiple of 3.
28622
+ function snap(snapStep, point) {
28623
+ const [requestedX, requestedY] = point;
28624
+ const [snapX, snapY] = snapStep;
28625
+ return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
28626
+ }
28627
+
28628
+ // Use X and Y to index into arrays that represent coordinate pairs.
28629
+ // e.g. point[X]
28630
+ const X = 0;
28631
+ const Y = 1;
28632
+
28633
+ // Use MIN and MAX to index into arrays that represent intervals.
28634
+ // e.g. range[MAX]
28635
+ const MIN = 0;
28636
+ const MAX = 1;
28637
+ function size(_ref) {
28638
+ let [min, max] = _ref;
28639
+ return max - min;
28640
+ }
28641
+
28642
+ // Removes an equal amount from each end of the interval.
28643
+ //
28644
+ // If the given amount cannot be removed (because the interval is too small),
28645
+ // half the interval's size is removed from each end instead, so the interval
28646
+ // ends up with size = 0, and both MAX and MIN equal to its former midpoint.
28647
+ //
28648
+ // If `amount` is negative, the interval is expanded instead.
28649
+ function trim(amount, interval) {
28650
+ if (amount * 2 > size(interval)) {
28651
+ const middle = average(...interval);
28652
+ return [middle, middle];
28653
+ }
28654
+ return [interval[MIN] + amount, interval[MAX] - amount];
28655
+ }
28656
+ function average(a, b) {
28657
+ return (a + b) / 2;
28658
+ }
28659
+
28660
+ // Restricts the `point` to be within `box`, by clamping each coordinate to the
28661
+ // corresponding interval.
28662
+ function clampToBox(box, point) {
28663
+ return [clamp(point[X], ...box[X]), clamp(point[Y], ...box[Y])];
28664
+ }
28665
+
28666
+ // Reduces the size of `box`, trimming each corner by the given positive
28667
+ // `amount`. If a component of `amount` is negative, the box is expanded in
28668
+ // that dimension instead.
28669
+ function inset(amount, box) {
28670
+ return [trim(amount[X], box[X]), trim(amount[Y], box[Y])];
28671
+ }
28672
+
28673
+ // [L]inear Int[erp]olation: gets the weighted average of two values `a` and
28674
+ // `b`, with the given `fraction` specifying how much weight `a` and `b` get:
28675
+ // - if `fraction` is 0, `lerp` returns `a`.
28676
+ // - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
28677
+ // - if `fraction` is 1, `lerp` returns `b`.
28678
+ function lerp(a, b, fraction) {
28679
+ return (b - a) * clamp(fraction, 0, 1) + a;
28680
+ }
28681
+
28682
+ // Determines whether two line segments have exactly one intersection point.
28683
+ // The segments are treated as "open" - that is, not inclusive of their
28684
+ // endpoints.
28685
+ // Algorithm from https://stackoverflow.com/a/24392281/7347484
28686
+ const segmentsIntersect = (_ref, _ref2) => {
28687
+ let [[a, b], [c, d]] = _ref;
28688
+ let [[p, q], [r, s]] = _ref2;
28689
+ const determinant = (c - a) * (s - q) - (r - p) * (d - b);
28690
+ if (determinant === 0) {
28691
+ return false;
28692
+ } else {
28693
+ const lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / determinant;
28694
+ const gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / determinant;
28695
+ return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1;
28696
+ }
28697
+ };
28698
+
28699
+ // This file contains helper functions for working with angles.
28700
+
28701
+ function convertDegreesToRadians(degrees) {
28702
+ return degrees / 180 * Math.PI;
28703
+ }
28704
+
28705
+ // Returns a value between -180 and 180, inclusive. The angle is measured
28706
+ // between the positive x-axis and the given vector.
28707
+ function calculateAngleInDegrees(_ref) {
28708
+ let [x, y] = _ref;
28709
+ return Math.atan2(y, x) * 180 / Math.PI;
28710
+ }
28711
+ function polar(r, th) {
28712
+ if (typeof r === "number") {
28713
+ r = [r, r];
28714
+ }
28715
+ th = th * Math.PI / 180;
28716
+ return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
28717
+ }
28718
+ const findAngle = (point1, point2, vertex) => {
28719
+ if (vertex === undefined) {
28720
+ const x = point1[0] - point2[0];
28721
+ const y = point1[1] - point2[1];
28722
+ if (!x && !y) {
28723
+ return 0;
28724
+ }
28725
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
28726
+ }
28727
+ return findAngle(point1, vertex) - findAngle(point2, vertex);
28728
+ };
28729
+
28660
28730
  function AxisLabels() {
28661
28731
  const {
28662
28732
  range,
@@ -28664,8 +28734,8 @@ function AxisLabels() {
28664
28734
  width,
28665
28735
  height
28666
28736
  } = useGraphConfig();
28667
- const yAxisLabelLocation = [0, range[1][1]];
28668
- const xAxisLabelLocation = [range[0][1], 0];
28737
+ const yAxisLabelLocation = [0, range[Y][MAX]];
28738
+ const xAxisLabelLocation = [range[X][MAX], 0];
28669
28739
  const [xAxisLabelText, yAxisLabelText] = labels;
28670
28740
  const graphInfo = {
28671
28741
  range,
@@ -28836,6 +28906,32 @@ const LockedEllipse = props => {
28836
28906
  });
28837
28907
  };
28838
28908
 
28909
+ const LockedFunction = props => {
28910
+ const {
28911
+ color,
28912
+ strokeStyle,
28913
+ directionalAxis,
28914
+ domain
28915
+ } = props;
28916
+ const plotProps = {
28917
+ color: lockedFigureColors[color],
28918
+ style: strokeStyle,
28919
+ domain
28920
+ };
28921
+ const equation = props.equationParsed || KAS__namespace.parse(props.equation).expr;
28922
+ return /*#__PURE__*/React__namespace.createElement("g", {
28923
+ className: "locked-function"
28924
+ }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
28925
+ y: x => equation.eval({
28926
+ x
28927
+ })
28928
+ }, plotProps)), directionalAxis === "y" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfY, _extends({
28929
+ x: y => equation.eval({
28930
+ y
28931
+ })
28932
+ }, plotProps)));
28933
+ };
28934
+
28839
28935
  function pathBuilder() {
28840
28936
  return new PathBuilder();
28841
28937
  }
@@ -28934,7 +29030,7 @@ function Arrowhead(props) {
28934
29030
  const [point] = useTransformVectorsToPixels(props.tip);
28935
29031
  return /*#__PURE__*/React__namespace.createElement("g", {
28936
29032
  className: "interactive-graph-arrowhead",
28937
- transform: `translate(${point[0]} ${point[1]}) rotate(${props.angle})`
29033
+ transform: `translate(${point[X]} ${point[Y]}) rotate(${props.angle})`
28938
29034
  }, /*#__PURE__*/React__namespace.createElement("g", {
28939
29035
  transform: "translate(-1.5)"
28940
29036
  }, /*#__PURE__*/React__namespace.createElement("path", {
@@ -28949,52 +29045,6 @@ function Arrowhead(props) {
28949
29045
  })));
28950
29046
  }
28951
29047
 
28952
- function calculateAngleInDegrees(_ref) {
28953
- let [x, y] = _ref;
28954
- return Math.atan2(y, x) * 180 / Math.PI;
28955
- }
28956
-
28957
- /**
28958
- * Given a ray and a rectangular box, find the point where the ray intersects
28959
- * the edge of the box. Assumes the `initialPoint` is inside the box.
28960
- * @param initialPoint - The starting point of the ray.
28961
- * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
28962
- * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
28963
- */
28964
- const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28965
- const [[xMin, xMax], [yMin, yMax]] = box;
28966
- const [aX, aY] = initialPoint;
28967
- const [bX, bY] = throughPoint;
28968
- const yDiff = bY - aY;
28969
- const xDiff = bX - aX;
28970
- const slope = yDiff / xDiff;
28971
- const inverseSlope = 1 / slope;
28972
- const xExtreme = xDiff < 0 ? xMin : xMax;
28973
- const yExtreme = yDiff < 0 ? yMin : yMax;
28974
- const yAtXExtreme = aY + (xExtreme - aX) * slope;
28975
- const xAtYExtreme = aX + (yExtreme - aY) * inverseSlope;
28976
- switch (true) {
28977
- // does the ray exit the graph bounding box via the left or right edge?
28978
- case isBetween(yAtXExtreme, yMin, yMax):
28979
- return [xExtreme, yAtXExtreme];
28980
-
28981
- // does the ray exit the graph bounding box via the top or bottom edge?
28982
- case isBetween(xAtYExtreme, xMin, xMax):
28983
- return [xAtYExtreme, yExtreme];
28984
- default:
28985
- return [xExtreme, yExtreme];
28986
- }
28987
- };
28988
- const getLines = points => {
28989
- return points.map((point, i) => {
28990
- const next = points[(i + 1) % points.length];
28991
- return [point, next];
28992
- });
28993
- };
28994
- function isBetween(x, low, high) {
28995
- return x >= low && x <= high;
28996
- }
28997
-
28998
29048
  function SVGLine(props) {
28999
29049
  const {
29000
29050
  start,
@@ -29004,10 +29054,10 @@ function SVGLine(props) {
29004
29054
  testId
29005
29055
  } = props;
29006
29056
  return /*#__PURE__*/React__namespace.createElement("line", {
29007
- x1: start[0],
29008
- y1: start[1],
29009
- x2: end[0],
29010
- y2: end[1],
29057
+ x1: start[X],
29058
+ y1: start[Y],
29059
+ x2: end[X],
29060
+ y2: end[Y],
29011
29061
  style: style,
29012
29062
  className: className,
29013
29063
  "data-testid": testId
@@ -29040,6 +29090,41 @@ function Vector(props) {
29040
29090
  }));
29041
29091
  }
29042
29092
 
29093
+ /**
29094
+ * Given a ray and a rectangular box, find the point where the ray intersects
29095
+ * the edge of the box. Assumes the `initialPoint` is inside the box.
29096
+ * @param initialPoint - The starting point of the ray.
29097
+ * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
29098
+ * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
29099
+ */
29100
+ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29101
+ const [[xMin, xMax], [yMin, yMax]] = box;
29102
+ const [aX, aY] = initialPoint;
29103
+ const [bX, bY] = throughPoint;
29104
+ const yDiff = bY - aY;
29105
+ const xDiff = bX - aX;
29106
+ const slope = yDiff / xDiff;
29107
+ const inverseSlope = 1 / slope;
29108
+ const xExtreme = xDiff < 0 ? xMin : xMax;
29109
+ const yExtreme = yDiff < 0 ? yMin : yMax;
29110
+ const yAtXExtreme = aY + (xExtreme - aX) * slope;
29111
+ const xAtYExtreme = aX + (yExtreme - aY) * inverseSlope;
29112
+ switch (true) {
29113
+ // does the ray exit the graph bounding box via the left or right edge?
29114
+ case isBetween(yAtXExtreme, yMin, yMax):
29115
+ return [xExtreme, yAtXExtreme];
29116
+
29117
+ // does the ray exit the graph bounding box via the top or bottom edge?
29118
+ case isBetween(xAtYExtreme, xMin, xMax):
29119
+ return [xAtYExtreme, yExtreme];
29120
+ default:
29121
+ return [xExtreme, yExtreme];
29122
+ }
29123
+ };
29124
+ function isBetween(x, low, high) {
29125
+ return x >= low && x <= high;
29126
+ }
29127
+
29043
29128
  const LockedLine = props => {
29044
29129
  const {
29045
29130
  color,
@@ -29092,8 +29177,8 @@ const LockedLine = props => {
29092
29177
  return /*#__PURE__*/React__namespace.createElement("g", {
29093
29178
  className: kind === "ray" ? "locked-ray" : "locked-line"
29094
29179
  }, line, showPoint1 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29095
- x: point1.coord[0],
29096
- y: point1.coord[1],
29180
+ x: point1.coord[X],
29181
+ y: point1.coord[Y],
29097
29182
  svgCircleProps: {
29098
29183
  style: {
29099
29184
  fill: point1.filled ? lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
@@ -29102,8 +29187,8 @@ const LockedLine = props => {
29102
29187
  }
29103
29188
  }
29104
29189
  }), showPoint2 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29105
- x: point2.coord[0],
29106
- y: point2.coord[1],
29190
+ x: point2.coord[X],
29191
+ y: point2.coord[Y],
29107
29192
  svgCircleProps: {
29108
29193
  style: {
29109
29194
  fill: point2.filled ? lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
@@ -29151,8 +29236,8 @@ const LockedPolygon = props => {
29151
29236
  color: lockedFigureColors[color]
29152
29237
  }), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29153
29238
  key: `locked-polygon-point-${index}`,
29154
- x: point[0],
29155
- y: point[1],
29239
+ x: point[X],
29240
+ y: point[Y],
29156
29241
  color: lockedFigureColors[color]
29157
29242
  })));
29158
29243
  };
@@ -29199,6 +29284,10 @@ const GraphLockedLayer = props => {
29199
29284
  return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
29200
29285
  key: `polygon-${index}`
29201
29286
  }, figure));
29287
+ case "function":
29288
+ return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
29289
+ key: `function-${index}`
29290
+ }, figure));
29202
29291
  default:
29203
29292
  /**
29204
29293
  * Devlopment-time future-proofing: This should
@@ -29291,24 +29380,18 @@ const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coor
29291
29380
  coords.map((coord, i) => {
29292
29381
  const axisRange = range[i];
29293
29382
  if (noSnap) {
29294
- return axisRange[0] + (axisRange[1] - axisRange[0]) * coord;
29383
+ return axisRange[MIN] + size(axisRange) * coord;
29295
29384
  }
29296
29385
  const axisStep = step[i];
29297
- const nSteps = Math.floor((axisRange[1] - axisRange[0]) / axisStep);
29386
+ const nSteps = Math.floor(size(axisRange) / axisStep);
29298
29387
  const tick = Math.round(coord * nSteps);
29299
- return axisRange[0] + axisStep * tick;
29388
+ return axisRange[MIN] + axisStep * tick;
29300
29389
  }));
29301
29390
 
29302
29391
  // same as normalizeCoords in interactive-graph.tsx
29303
29392
  const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
29304
- const extent = ranges[i][1] - ranges[i][0];
29305
- return (coord + ranges[i][1]) / extent;
29393
+ return (coord + ranges[i][1]) / size(ranges[i]);
29306
29394
  }));
29307
- function snap(snapStep, point) {
29308
- const [requestedX, requestedY] = point;
29309
- const [snapX, snapY] = snapStep;
29310
- return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29311
- }
29312
29395
 
29313
29396
  // Returns the closest point to the given `point` that is within the graph
29314
29397
  // bounds given in `state`.
@@ -29318,19 +29401,8 @@ function bound$1(_ref) {
29318
29401
  range,
29319
29402
  point
29320
29403
  } = _ref;
29321
- const [requestedX, requestedY] = point;
29322
- const [snapX, snapY] = snapStep;
29323
- const [[minX, maxX], [minY, maxY]] = range;
29324
- return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
29325
- }
29326
- function clamp(value, min, max) {
29327
- if (value < min) {
29328
- return min;
29329
- }
29330
- if (value > max) {
29331
- return max;
29332
- }
29333
- return value;
29404
+ const boundingBox = inset(snapStep, range);
29405
+ return clampToBox(boundingBox, point);
29334
29406
  }
29335
29407
 
29336
29408
  var isProduction = process.env.NODE_ENV === 'production';
@@ -29400,8 +29472,8 @@ function useDraggable(args) {
29400
29472
  metaKey,
29401
29473
  shiftKey
29402
29474
  } = state;
29403
- const direction = [yDownDirection[0], -yDownDirection[1]];
29404
- const span = Math.abs(direction[0]) ? xSpan : ySpan;
29475
+ const direction = [yDownDirection[X], -yDownDirection[Y]];
29476
+ const span = Math.abs(direction[X]) ? xSpan : ySpan;
29405
29477
  let divisions = 50;
29406
29478
  if (altKey || metaKey) {
29407
29479
  divisions = 200;
@@ -29512,8 +29584,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
29512
29584
  })?.[0] ?? "blue";
29513
29585
  const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""} ${focusClass(focusBehavior)}`;
29514
29586
  const [[x, y]] = useTransformVectorsToPixels(point);
29515
- const [xMin, xMax] = range[0];
29516
- const [yMin, yMax] = range[1];
29587
+ const [[xMin, xMax], [yMin, yMax]] = range;
29517
29588
  const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29518
29589
  const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29519
29590
  const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
@@ -29570,7 +29641,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
29570
29641
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29571
29642
  autoUpdate: true,
29572
29643
  backgroundColor: wbColorName,
29573
- content: `(${point[0]}, ${point[1]})`,
29644
+ content: `(${point[X]}, ${point[Y]})`,
29574
29645
  contentStyle: {
29575
29646
  color: "white"
29576
29647
  }
@@ -29755,16 +29826,7 @@ function trimRange(range, graphDimensionsInPixels) {
29755
29826
  const graphUnitsPerPixelY = size(yRange) / pixelsTall;
29756
29827
  const graphUnitsToTrimX = pixelsToTrim * graphUnitsPerPixelX;
29757
29828
  const graphUnitsToTrimY = pixelsToTrim * graphUnitsPerPixelY;
29758
- return [trim(xRange, graphUnitsToTrimX), trim(yRange, graphUnitsToTrimY)];
29759
- }
29760
- function trim(interval, amount) {
29761
- if (size(interval) < amount * 2) {
29762
- return [0, 0];
29763
- }
29764
- return [interval[0] + amount, interval[1] - amount];
29765
- }
29766
- function size(interval) {
29767
- return interval[1] - interval[0];
29829
+ return inset([graphUnitsToTrimX, graphUnitsToTrimY], range);
29768
29830
  }
29769
29831
 
29770
29832
  const SegmentGraph = props => {
@@ -29899,7 +29961,7 @@ const SvgDefs = () => /*#__PURE__*/React__default["default"].createElement("defs
29899
29961
  in: "SourceGraphic"
29900
29962
  })));
29901
29963
 
29902
- const Angle = _ref => {
29964
+ const PolygonAngle = _ref => {
29903
29965
  let {
29904
29966
  centerPoint,
29905
29967
  endPoints,
@@ -29927,7 +29989,7 @@ const Angle = _ref => {
29927
29989
  // Used to calculate the inside of the angle and to create square
29928
29990
  const [x3, y3] = mafs.vec.add(centerPoint, mafs.vec.add(mafs.vec.sub([x1, y1], centerPoint), mafs.vec.sub([x2, y2], centerPoint)));
29929
29991
  if (!showAngles) {
29930
- return isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
29992
+ return isRightPolygonAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
29931
29993
  start: [x1, y1],
29932
29994
  vertex: [x2, y2],
29933
29995
  end: [x3, y3]
@@ -29964,35 +30026,138 @@ const Angle = _ref => {
29964
30026
  start: [x1, y1],
29965
30027
  vertex: [x2, y2],
29966
30028
  end: [x3, y3]
29967
- }) : /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
29968
- d: arc,
29969
- strokeWidth: 0.02,
29970
- fill: "none"
29971
- })), /*#__PURE__*/React__namespace.createElement(TextLabel, {
30029
+ }) : /*#__PURE__*/React__namespace.createElement(Arc, {
30030
+ arc: arc
30031
+ }), /*#__PURE__*/React__namespace.createElement(TextLabel, {
29972
30032
  x: x3,
29973
30033
  y: y3,
29974
30034
  attach: y3 - centerY > 0 ? "s" : "n",
29975
30035
  attachDistance: Math.abs(y3 - centerY) < 0.2 || mafs.vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
29976
30036
  }, angleLabel, "\xB0"));
29977
30037
  };
30038
+ const Angle = _ref2 => {
30039
+ let {
30040
+ vertex,
30041
+ coords,
30042
+ showAngles,
30043
+ allowReflexAngles,
30044
+ range
30045
+ } = _ref2;
30046
+ // Check if the points are clockwise or not, depending on whether we allow reflex angles
30047
+ const areClockwise = clockwise$1([...coords, vertex]);
30048
+ const shouldReverseCoords = areClockwise && !allowReflexAngles;
30049
+
30050
+ // Reverse the coordinates accordingly to ensure the angle is calculated correctly
30051
+ const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
30052
+
30053
+ // Calculate the angles between the two points
30054
+ const startAngle = findAngle(clockwiseCoords[0], vertex);
30055
+ const endAngle = findAngle(clockwiseCoords[1], vertex);
30056
+ const angle = (startAngle + 360 - endAngle) % 360;
30057
+
30058
+ // Check if the angle is reflexive
30059
+ const isReflexive = angle > 180;
30060
+
30061
+ // Break out the necessary points for the arc calculation
30062
+ const [centerX, centerY] = vertex;
30063
+ const [point1, point2] = clockwiseCoords;
30064
+ const [startX, startY] = point1;
30065
+ const [endX, endY] = point2;
30066
+ const a = mafs.vec.dist(vertex, point1);
30067
+ const b = mafs.vec.dist(vertex, point2);
30068
+
30069
+ // Set the radius of the arc
30070
+ const radius = 2;
30071
+
30072
+ // Calculate the end points of the arc
30073
+ const y1 = centerY + (startY - centerY) / a * radius;
30074
+ const x2 = centerX + (endX - centerX) / b * radius;
30075
+ const x1 = centerX + (startX - centerX) / a * radius;
30076
+ const y2 = centerY + (endY - centerY) / b * radius;
30077
+
30078
+ // Used to calculate the inside of the angle and to create square
30079
+ const [x3, y3] = mafs.vec.add(vertex, mafs.vec.add(mafs.vec.sub([x1, y1], vertex), mafs.vec.sub([x2, y2], vertex)));
30080
+
30081
+ // Determine whether the arc should be drawn outside the angle
30082
+ const isOutside = shouldDrawArcOutside([x3, y3], vertex, range, [[vertex, point1], [vertex, point2]]);
30083
+
30084
+ // Determine the flags for the arc
30085
+ // If the angle is outside OR reflexive, we want to draw a large arc
30086
+ // If the angle is outside AND reflexive, we want to sweep the arc
30087
+ const largeArcFlag = isOutside || isReflexive ? 1 : 0;
30088
+ const sweepFlag = isOutside && isReflexive ? 1 : 0;
30089
+
30090
+ // Create the SVG path for the arc
30091
+ const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
30092
+
30093
+ // We only ever want to show whole angles
30094
+ const angleLabel = parseFloat(angle.toFixed(0)); // Only want to show whole angles
30095
+
30096
+ // Calculate the text position based on the angle and whether we allow reflex angles
30097
+ // Let's try the angle bisector method to find the midpoint of the arc
30098
+ const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
30099
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
30100
+ id: "background",
30101
+ x: "-5%",
30102
+ width: "110%",
30103
+ y: "0%",
30104
+ height: "100%"
30105
+ }, /*#__PURE__*/React__namespace.createElement("feFlood", {
30106
+ floodColor: "#FFF",
30107
+ floodOpacity: "0.5"
30108
+ }), /*#__PURE__*/React__namespace.createElement("feComposite", {
30109
+ operator: "over",
30110
+ in: "SourceGraphic"
30111
+ }))), isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
30112
+ start: [x1, y1],
30113
+ vertex: [x2, y2],
30114
+ end: [x3, y3],
30115
+ className: "arc-right-angle"
30116
+ }) : /*#__PURE__*/React__namespace.createElement(Arc, {
30117
+ arc: arc,
30118
+ className: "angle-arc"
30119
+ }), showAngles && /*#__PURE__*/React__namespace.createElement(TextLabel, {
30120
+ x: textX,
30121
+ y: textY,
30122
+ color: wonderBlocksTokens.color.blue
30123
+ }, angleLabel, "\xB0"));
30124
+ };
29978
30125
 
29979
30126
  /**
29980
30127
  * This is broken out into its own component so that it can be used for an early return
29981
30128
  * (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
29982
30129
  */
29983
- const RightAngleSquare = _ref2 => {
30130
+ const RightAngleSquare = _ref3 => {
29984
30131
  let {
29985
30132
  start: [x1, y1],
29986
30133
  vertex: [x2, y2],
29987
- end: [x3, y3]
29988
- } = _ref2;
30134
+ end: [x3, y3],
30135
+ className
30136
+ } = _ref3;
29989
30137
  return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
29990
30138
  d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
29991
30139
  strokeWidth: 0.02,
29992
- fill: "none"
30140
+ fill: "none",
30141
+ className: className
30142
+ }));
30143
+ };
30144
+
30145
+ // We're conditionally adding the class name here so that we can style the arc differently
30146
+ // based on whether it's an angle or a polygon angle
30147
+ const Arc = _ref4 => {
30148
+ let {
30149
+ arc,
30150
+ className
30151
+ } = _ref4;
30152
+ return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
30153
+ d: arc,
30154
+ strokeWidth: 0.02,
30155
+ fill: "none",
30156
+ className: className
29993
30157
  }));
29994
30158
  };
29995
- const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
30159
+ const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
30160
+ const isRightAngle = angle => Math.round(angle) === 90;
29996
30161
 
29997
30162
  /**
29998
30163
  * Determines if an angle is an inside (false) or outside (true) angle.
@@ -30006,27 +30171,81 @@ const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
30006
30171
  // Create a ray from the midpoint (inside angle) to the edge of the range
30007
30172
  const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
30008
30173
  let lineIntersectionCount = 0;
30009
- polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
30174
+ polygonLines.forEach(line => segmentsIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
30010
30175
 
30011
30176
  // If the number of intersections is even, the angle is inside the polygon
30012
30177
  return !isEven(lineIntersectionCount);
30013
30178
  };
30179
+ const isEven = n => n % 2 === 0;
30014
30180
 
30015
- // https://stackoverflow.com/a/24392281/7347484
30016
- // The "intersects" function in geometry doesn't seem to work for this use case
30017
- const linesIntersect = (_ref3, _ref4) => {
30018
- let [[a, b], [c, d]] = _ref3;
30019
- let [[p, q], [r, s]] = _ref4;
30020
- const determinant = (c - a) * (s - q) - (r - p) * (d - b);
30021
- if (determinant === 0) {
30022
- return false;
30181
+ // This function calculates the bisector point of an angle formed by two points
30182
+ // and a vertex. It is used to position the angle label so that it is always
30183
+ // outside the arc at the exact midpoint of the angle.
30184
+ // Reflex angles make use of polar coordinates to determine the correct direction
30185
+ function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, arcRadius) {
30186
+ const [originX, originY] = vertex;
30187
+ const [x1, y1] = point1;
30188
+ const [x2, y2] = point2;
30189
+
30190
+ // Convert the Cartesian Coordinates to vectors
30191
+ const vectorA = [x1 - originX, y1 - originY];
30192
+ const vectorB = [x2 - originX, y2 - originY];
30193
+
30194
+ // Calculate the average angle of the vectors
30195
+ // so that we can point the bisector in the correct direction using polar coordinates
30196
+ const angleA = Math.atan2(vectorA[1], vectorA[0]);
30197
+ const angleB = Math.atan2(vectorB[1], vectorB[0]);
30198
+
30199
+ // Calculate the average of these two angles
30200
+ let averageAngle = (angleA + angleB) / 2;
30201
+ const angleRadians = Math.abs(angleA - angleB);
30202
+
30203
+ // Adjust the average angle to point in the correct direction
30204
+ // and to account for reflex angles and a bug with 180 degrees
30205
+ // that occurs due to our angleOffsetDegrees option in the graph
30206
+ if (allowReflex) {
30207
+ // If the resulting angles are less than 180 degrees,
30208
+ // or if the user has flipped the points on the interactive element
30209
+ // ensure that we have the correct direction for the reflex angle
30210
+ if (angleRadians <= Math.PI && isReflex || angleB > angleA) {
30211
+ averageAngle += Math.PI; // Add 180 degrees
30212
+ }
30023
30213
  } else {
30024
- const lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / determinant;
30025
- const gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / determinant;
30026
- return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1;
30214
+ // If the resulting angles are greater than 180 degrees, adjust
30215
+ // to get the correct angle, as we already know that the angle is not reflexive
30216
+ // This fixes a bug that occurs at 180 degrees due to our angleOffsetDegrees option in the graph
30217
+ if (angleRadians > Math.PI) {
30218
+ averageAngle -= Math.PI; // Subract 180 degrees
30219
+ }
30027
30220
  }
30028
- };
30029
- const isEven = n => n % 2 === 0;
30221
+
30222
+ // Convert the average angle back to Cartesian coordinates
30223
+ const sum = [Math.cos(averageAngle), Math.sin(averageAngle)];
30224
+
30225
+ // Calculate the magnitude of the sum to normalize it
30226
+ const sumMagnitude = Math.sqrt(sum[0] ** 2 + sum[1] ** 2);
30227
+ const bisectorDirection = [sum[0] / sumMagnitude, sum[1] / sumMagnitude];
30228
+
30229
+ // Calculate the initial distance of the bisector direction from the origin
30230
+ const initialDistance = Math.sqrt(bisectorDirection[0] ** 2 + bisectorDirection[1] ** 2);
30231
+
30232
+ // Determine the minimum radius to ensure that the text is always outside the arc
30233
+ const requiredDistance = arcRadius * 1.75;
30234
+ let radius = requiredDistance / initialDistance;
30235
+
30236
+ // Apply radius only if the initial distance is less than required
30237
+ if (initialDistance >= requiredDistance) {
30238
+ radius = 1; // Use the unit vector as is
30239
+ }
30240
+
30241
+ // Scale the bisector direction by the radius
30242
+ const bisectorPoint = [bisectorDirection[0] * radius, bisectorDirection[1] * radius];
30243
+
30244
+ // Add the vertex to the bisector point to get the final position
30245
+ // to ensure that the angle label moves with the interactive element
30246
+ const scaledBisector = mafs.vec.add(bisectorPoint, vertex);
30247
+ return scaledBisector;
30248
+ }
30030
30249
 
30031
30250
  const StyledMovablePoint = props => {
30032
30251
  const {
@@ -30108,7 +30327,7 @@ const PolygonGraph = props => {
30108
30327
  if (!pt1 || !pt2) {
30109
30328
  return null;
30110
30329
  }
30111
- return /*#__PURE__*/React__namespace.createElement(Angle, {
30330
+ return /*#__PURE__*/React__namespace.createElement(PolygonAngle, {
30112
30331
  key: "angle-" + i,
30113
30332
  centerPoint: point,
30114
30333
  endPoints: [pt1, pt2],
@@ -30150,6 +30369,12 @@ const PolygonGraph = props => {
30150
30369
  onMove: destination => dispatch(movePoint(i, destination))
30151
30370
  })));
30152
30371
  };
30372
+ function getLines(points) {
30373
+ return points.map((point, i) => {
30374
+ const next = points[(i + 1) % points.length];
30375
+ return [point, next];
30376
+ });
30377
+ }
30153
30378
 
30154
30379
  function getGradableGraph(state, initialGraph) {
30155
30380
  if (!state.hasBeenInteractedWith) {
@@ -30212,6 +30437,13 @@ function getGradableGraph(state, initialGraph) {
30212
30437
  coords: state.coords
30213
30438
  };
30214
30439
  }
30440
+ if (state.type === "angle" && initialGraph.type === "angle") {
30441
+ return {
30442
+ ...initialGraph,
30443
+ coords: state.coords,
30444
+ allowReflexAngles: state.allowReflexAngles
30445
+ };
30446
+ }
30215
30447
  throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
30216
30448
  }
30217
30449
 
@@ -30274,16 +30506,16 @@ function MovableCircle(props) {
30274
30506
  className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
30275
30507
  }, /*#__PURE__*/React__namespace.createElement("ellipse", {
30276
30508
  className: "focus-ring",
30277
- cx: centerPx[0],
30278
- cy: centerPx[1],
30279
- rx: radiiPx[0] + 3,
30280
- ry: radiiPx[1] + 3
30509
+ cx: centerPx[X],
30510
+ cy: centerPx[Y],
30511
+ rx: radiiPx[X] + 3,
30512
+ ry: radiiPx[Y] + 3
30281
30513
  }), /*#__PURE__*/React__namespace.createElement("ellipse", {
30282
30514
  className: "circle",
30283
- cx: centerPx[0],
30284
- cy: centerPx[1],
30285
- rx: radiiPx[0],
30286
- ry: radiiPx[1]
30515
+ cx: centerPx[X],
30516
+ cy: centerPx[Y],
30517
+ rx: radiiPx[X],
30518
+ ry: radiiPx[Y]
30287
30519
  }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
30288
30520
  center: center
30289
30521
  }));
@@ -30299,10 +30531,10 @@ function DragHandle(props) {
30299
30531
  const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
30300
30532
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
30301
30533
  className: "movable-circle-handle",
30302
- x: topLeft[0],
30303
- y: topLeft[1],
30304
- width: dragHandleDimensions[0],
30305
- height: dragHandleDimensions[1],
30534
+ x: topLeft[X],
30535
+ y: topLeft[Y],
30536
+ width: dragHandleDimensions[X],
30537
+ height: dragHandleDimensions[Y],
30306
30538
  rx: cornerRadius,
30307
30539
  ry: cornerRadius
30308
30540
  }), dragHandleDotPositions.map(offsetPx => {
@@ -30437,15 +30669,15 @@ const getSinusoidCoefficients = coords => {
30437
30669
  const p2 = coords[1];
30438
30670
 
30439
30671
  // If the x-coordinates are the same, we are unable to calculate the coefficients
30440
- if (p2[0] === p1[0]) {
30672
+ if (p2[X] === p1[X]) {
30441
30673
  return;
30442
30674
  }
30443
30675
 
30444
30676
  // Resulting coefficients are canonical for this sine curve
30445
- const amplitude = p2[1] - p1[1];
30446
- const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
30447
- const phase = p1[0] * angularFrequency;
30448
- const verticalOffset = p1[1];
30677
+ const amplitude = p2[Y] - p1[Y];
30678
+ const angularFrequency = Math.PI / (2 * (p2[X] - p1[X]));
30679
+ const phase = p1[X] * angularFrequency;
30680
+ const verticalOffset = p1[Y];
30449
30681
  return {
30450
30682
  amplitude,
30451
30683
  angularFrequency,
@@ -30454,6 +30686,75 @@ const getSinusoidCoefficients = coords => {
30454
30686
  };
30455
30687
  };
30456
30688
 
30689
+ function AngleGraph(props) {
30690
+ const {
30691
+ dispatch,
30692
+ graphState
30693
+ } = props;
30694
+ const {
30695
+ graphDimensionsInPixels
30696
+ } = useGraphConfig();
30697
+ const {
30698
+ coords,
30699
+ showAngles,
30700
+ range,
30701
+ allowReflexAngles,
30702
+ angleOffsetDeg,
30703
+ snapDegrees
30704
+ } = graphState;
30705
+
30706
+ // Break the coords into the two end points and the center point
30707
+ const endPoints = [coords[0], coords[2]];
30708
+ const centerPoint = coords[1];
30709
+
30710
+ // Convert the vectors to pixels for rendering the svg lines
30711
+ const angleLines = [[centerPoint, endPoints[0]], [centerPoint, endPoints[1]]];
30712
+ const linePixelCoords = [useTransformVectorsToPixels(centerPoint, endPoints[0]), useTransformVectorsToPixels(centerPoint, endPoints[1])];
30713
+
30714
+ // Create the SVG lines for the angle
30715
+ const svgLines = linePixelCoords.map((_ref, i) => {
30716
+ let [startPtPx, endPtPx] = _ref;
30717
+ const trimmedRange = trimRange(range, graphDimensionsInPixels);
30718
+ const endExtend = getIntersectionOfRayWithBox(angleLines[i][0], angleLines[i][1], trimmedRange);
30719
+ return /*#__PURE__*/React__namespace.createElement("g", {
30720
+ key: `line-${i}`
30721
+ }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
30722
+ start: startPtPx,
30723
+ end: endPtPx,
30724
+ style: {
30725
+ stroke: "var(--movable-line-stroke-color)",
30726
+ strokeWidth: "var(--movable-line-stroke-weight)"
30727
+ }
30728
+ }), /*#__PURE__*/React__namespace.createElement(Vector, {
30729
+ tail: angleLines[i][1],
30730
+ tip: endExtend,
30731
+ color: "var(--movable-line-stroke-color)"
30732
+ }));
30733
+ });
30734
+
30735
+ // Create the angle indicator parameters
30736
+ const angleParams = {
30737
+ vertex: centerPoint,
30738
+ coords: endPoints,
30739
+ allowReflexAngles: allowReflexAngles || false,
30740
+ // Whether to allow reflex angles or not
30741
+ angleOffsetDeg: angleOffsetDeg || 0,
30742
+ // The angle offset from the x-axis
30743
+ snapDegrees: snapDegrees || 1,
30744
+ // The multiple of degrees to snap to
30745
+ range: range,
30746
+ showAngles: showAngles || false // Whether to show the angle or not
30747
+ };
30748
+
30749
+ // Render the lines, angle, and then movable points
30750
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30751
+ key: "point-" + i,
30752
+ snapTo: "angles",
30753
+ point: point,
30754
+ onMove: destination => dispatch(movePoint(i, destination))
30755
+ })));
30756
+ }
30757
+
30457
30758
  // Universal padding for the axis ticks to ensure that the labels
30458
30759
  // have enough space to render without overlapping the tick lines
30459
30760
  const tickPadding = 5;
@@ -30463,10 +30764,8 @@ const AxisTickLabels = () => {
30463
30764
  tickStep,
30464
30765
  range
30465
30766
  } = graphConfig;
30466
- const [xMin, xMax] = range[0];
30467
- const [yMin, yMax] = range[1];
30468
- const yTickStep = tickStep[1];
30469
- const xTickStep = tickStep[0];
30767
+ const [[xMin, xMax], [yMin, yMax]] = range;
30768
+ const [xTickStep, yTickStep] = tickStep;
30470
30769
  const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
30471
30770
  const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
30472
30771
  return /*#__PURE__*/React__namespace.createElement("div", {
@@ -30748,13 +31047,13 @@ const YGridTick = _ref => {
30748
31047
 
30749
31048
  // If the graph is zoomed in, we want to make sure the ticks are still visible
30750
31049
  // even if they are outside the graph's range.
30751
- if (graphInfo.range[0][0] > 0) {
31050
+ if (graphInfo.range[X][MIN] > 0) {
30752
31051
  // If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
30753
- xPointOnAxis = graphInfo.range[0][0];
31052
+ xPointOnAxis = graphInfo.range[X][MIN];
30754
31053
  }
30755
- if (graphInfo.range[0][1] < 0) {
31054
+ if (graphInfo.range[X][MAX] < 0) {
30756
31055
  // If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
30757
- xPointOnAxis = graphInfo.range[0][1];
31056
+ xPointOnAxis = graphInfo.range[X][MAX];
30758
31057
  }
30759
31058
  const pointOnAxis = [xPointOnAxis, y];
30760
31059
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
@@ -30789,13 +31088,13 @@ const XGridTick = _ref2 => {
30789
31088
  let yPointOnAxis = 0;
30790
31089
  // If the graph is zoomed in, we want to make sure the ticks are still visible
30791
31090
  // even if they are outside the graph's range.
30792
- if (graphInfo.range[1][0] > 0) {
31091
+ if (graphInfo.range[Y][MIN] > 0) {
30793
31092
  // If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
30794
- yPointOnAxis = graphInfo.range[1][0];
31093
+ yPointOnAxis = graphInfo.range[Y][MIN];
30795
31094
  }
30796
- if (graphInfo.range[1][1] < 0) {
31095
+ if (graphInfo.range[Y][MAX] < 0) {
30797
31096
  // If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
30798
- yPointOnAxis = graphInfo.range[1][1];
31097
+ yPointOnAxis = graphInfo.range[Y][MAX];
30799
31098
  }
30800
31099
  const pointOnAxis = [x, yPointOnAxis];
30801
31100
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
@@ -30850,10 +31149,8 @@ const AxisTicks = () => {
30850
31149
  width,
30851
31150
  height
30852
31151
  };
30853
- const [xMin, xMax] = range[0];
30854
- const [yMin, yMax] = range[1];
30855
- const yTickStep = tickStep[1];
30856
- const xTickStep = tickStep[0];
31152
+ const [[xMin, xMax], [yMin, yMax]] = range;
31153
+ const [xTickStep, yTickStep] = tickStep;
30857
31154
  const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
30858
31155
  const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
30859
31156
  return /*#__PURE__*/React__namespace.createElement("g", {
@@ -30892,8 +31189,8 @@ const axisOptions = (props, axisIndex) => {
30892
31189
  };
30893
31190
  const Grid = props => {
30894
31191
  return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
30895
- xAxis: axisOptions(props, 0),
30896
- yAxis: axisOptions(props, 1)
31192
+ xAxis: axisOptions(props, X),
31193
+ yAxis: axisOptions(props, Y)
30897
31194
  }),
30898
31195
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
30899
31196
  props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
@@ -31390,7 +31687,7 @@ const PropCheckBox = createReactClass__default["default"]({
31390
31687
  return !___default["default"].has(this.DEFAULT_PROPS, localPropName);
31391
31688
  }, this);
31392
31689
  if (!propName) {
31393
- throw new PerseusError("Attempted to create a PropCheckBox with no prop!", Errors.InvalidInput);
31690
+ throw new perseusCore.PerseusError("Attempted to create a PropCheckBox with no prop!", perseusCore.Errors.InvalidInput);
31394
31691
  }
31395
31692
  return propName;
31396
31693
  },
@@ -31501,7 +31798,7 @@ const LegacyGrid = _ref => {
31501
31798
  height
31502
31799
  } = backgroundImage ?? {};
31503
31800
  if (url && typeof url === "string") {
31504
- const scale = box[0] / interactiveSizes$1.defaultBoxSize;
31801
+ const scale = box[X] / interactiveSizes$1.defaultBoxSize;
31505
31802
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31506
31803
  style: {
31507
31804
  position: "absolute",
@@ -31569,9 +31866,9 @@ function Protractor() {
31569
31866
  const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
31570
31867
  return /*#__PURE__*/React__namespace.createElement("g", {
31571
31868
  ref: draggableRef,
31572
- transform: `translate(${topLeftPx[0]}, ${topLeftPx[1]}), rotate(${angle})`,
31869
+ transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
31573
31870
  style: {
31574
- transformOrigin: `${-centerToTopLeft[0]}px ${-centerToTopLeft[1]}px`
31871
+ transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
31575
31872
  }
31576
31873
  }, /*#__PURE__*/React__namespace.createElement("image", {
31577
31874
  href: protractorImage
@@ -31583,7 +31880,7 @@ function Protractor() {
31583
31880
  function RotationArrow() {
31584
31881
  const radius = 175;
31585
31882
  const angleDeg = 10;
31586
- const angleRad = degreesToRadians(angleDeg);
31883
+ const angleRad = convertDegreesToRadians(angleDeg);
31587
31884
  const endX = radius * (1 - Math.cos(angleRad));
31588
31885
  const endY = radius * -Math.sin(angleRad);
31589
31886
  const rotationArrow = pathBuilder().move(0, 0).circularArc(radius, endX, endY, {
@@ -31662,17 +31959,135 @@ function useDraggablePx(args) {
31662
31959
  });
31663
31960
  }
31664
31961
 
31665
- // [L]inear Int[erp]olation: gets the weighted average of two values `a` and
31666
- // `b`, with the given `fraction` specifying how much weight `a` and `b` get:
31667
- // - if `fraction` is 0, `lerp` returns `a`.
31668
- // - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
31669
- // - if `fraction` is 1, `lerp` returns `b`.
31670
- function lerp(a, b, fraction) {
31671
- return (b - a) * fraction + a;
31672
- }
31673
- function degreesToRadians(degrees) {
31674
- return degrees / 180 * Math.PI;
31675
- }
31962
+ const MafsGraph = props => {
31963
+ const {
31964
+ state,
31965
+ dispatch,
31966
+ labels
31967
+ } = props;
31968
+ const [width, height] = props.box;
31969
+ const tickStep = props.step;
31970
+ return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
31971
+ value: {
31972
+ range: state.range,
31973
+ snapStep: state.snapStep,
31974
+ markings: props.markings,
31975
+ tickStep: tickStep,
31976
+ gridStep: props.gridStep,
31977
+ showTooltips: !!props.showTooltips,
31978
+ graphDimensionsInPixels: props.box,
31979
+ width,
31980
+ height,
31981
+ labels
31982
+ }
31983
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31984
+ className: "mafs-graph",
31985
+ style: {
31986
+ width,
31987
+ height,
31988
+ position: "relative",
31989
+ padding: "25px 25px 0 0",
31990
+ boxSizing: "content-box",
31991
+ marginLeft: "20px",
31992
+ marginBottom: "20px"
31993
+ }
31994
+ }, /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
31995
+ box: props.box,
31996
+ backgroundImage: props.backgroundImage
31997
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31998
+ style: {
31999
+ position: "absolute",
32000
+ bottom: 0,
32001
+ left: 0
32002
+ }
32003
+ }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null), /*#__PURE__*/React__namespace.createElement(AxisTickLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
32004
+ preserveAspectRatio: false,
32005
+ viewBox: {
32006
+ x: state.range[X],
32007
+ y: state.range[Y],
32008
+ padding: 0
32009
+ },
32010
+ pan: false,
32011
+ zoom: false,
32012
+ width: width,
32013
+ height: height
32014
+ }, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement(Grid, {
32015
+ tickStep: props.step,
32016
+ gridStep: props.gridStep,
32017
+ range: state.range,
32018
+ containerSizeClass: props.containerSizeClass,
32019
+ markings: props.markings
32020
+ }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32021
+ lockedFigures: props.lockedFigures,
32022
+ range: state.range
32023
+ }), "/* Protractor */", props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32024
+ state,
32025
+ dispatch
32026
+ })))));
32027
+ };
32028
+ const renderGraph = props => {
32029
+ const {
32030
+ state,
32031
+ dispatch
32032
+ } = props;
32033
+ const {
32034
+ type
32035
+ } = state;
32036
+ switch (type) {
32037
+ case "angle":
32038
+ return /*#__PURE__*/React__namespace.createElement(AngleGraph, {
32039
+ graphState: state,
32040
+ dispatch: dispatch
32041
+ });
32042
+ case "segment":
32043
+ return /*#__PURE__*/React__namespace.createElement(SegmentGraph, {
32044
+ graphState: state,
32045
+ dispatch: dispatch
32046
+ });
32047
+ case "linear-system":
32048
+ return /*#__PURE__*/React__namespace.createElement(LinearSystemGraph, {
32049
+ graphState: state,
32050
+ dispatch: dispatch
32051
+ });
32052
+ case "linear":
32053
+ return /*#__PURE__*/React__namespace.createElement(LinearGraph, {
32054
+ graphState: state,
32055
+ dispatch: dispatch
32056
+ });
32057
+ case "ray":
32058
+ return /*#__PURE__*/React__namespace.createElement(RayGraph, {
32059
+ graphState: state,
32060
+ dispatch: dispatch
32061
+ });
32062
+ case "polygon":
32063
+ return /*#__PURE__*/React__namespace.createElement(PolygonGraph, {
32064
+ graphState: state,
32065
+ dispatch: dispatch
32066
+ });
32067
+ case "point":
32068
+ return /*#__PURE__*/React__namespace.createElement(PointGraph, {
32069
+ graphState: state,
32070
+ dispatch: dispatch
32071
+ });
32072
+ case "circle":
32073
+ return /*#__PURE__*/React__namespace.createElement(CircleGraph, {
32074
+ graphState: state,
32075
+ dispatch: dispatch
32076
+ });
32077
+ case "quadratic":
32078
+ return /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
32079
+ graphState: state,
32080
+ dispatch: dispatch
32081
+ });
32082
+ case "sinusoid":
32083
+ return /*#__PURE__*/React__namespace.createElement(SinusoidGraph, {
32084
+ graphState: state,
32085
+ dispatch: dispatch
32086
+ });
32087
+ default:
32088
+ return new wonderStuffCore.UnreachableCaseError(type);
32089
+ }
32090
+ };
31676
32091
 
31677
32092
  function initializeGraphState(params) {
31678
32093
  const {
@@ -31745,7 +32160,19 @@ function initializeGraphState(params) {
31745
32160
  coords: getSinusoidCoords(graph, range, step)
31746
32161
  };
31747
32162
  case "angle":
31748
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
32163
+ return {
32164
+ ...shared,
32165
+ type: graph.type,
32166
+ showAngles: Boolean(graph.showAngles),
32167
+ coords: getAngleCoords({
32168
+ graph,
32169
+ range,
32170
+ step
32171
+ }),
32172
+ angleOffsetDeg: Number(graph.angleOffsetDeg),
32173
+ allowReflexAngles: Boolean(graph.allowReflexAngles),
32174
+ snapDegrees: Number(graph.snapDegrees)
32175
+ };
31749
32176
  default:
31750
32177
  throw new wonderStuffCore.UnreachableCaseError(graph);
31751
32178
  }
@@ -31877,6 +32304,43 @@ function getCircleCoords(graph) {
31877
32304
  radiusPoint: [2, 0]
31878
32305
  };
31879
32306
  }
32307
+ const getAngleCoords = params => {
32308
+ const {
32309
+ graph,
32310
+ range,
32311
+ step
32312
+ } = params;
32313
+ if (graph.coords) {
32314
+ return graph.coords;
32315
+ }
32316
+ const {
32317
+ snapDegrees,
32318
+ angleOffsetDeg
32319
+ } = graph;
32320
+ const snap = snapDegrees || 1;
32321
+ let angle = snap;
32322
+ while (angle < 20) {
32323
+ angle += snap;
32324
+ }
32325
+ angle = angle * Math.PI / 180;
32326
+ const offset = (angleOffsetDeg || 0) * Math.PI / 180;
32327
+ let defaultCoords = [[0.85, 0.5], [0.5, 0.5]];
32328
+ defaultCoords = normalizePoints(range, step, defaultCoords, true);
32329
+
32330
+ // @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.
32331
+ const radius = magnitude(vector(...defaultCoords));
32332
+
32333
+ // We're adding a placeholder for the third point to appease ts and so that we
32334
+ // can calculate it after we've adjusted the first point by the angleOffsetDeg
32335
+ const coords = [...defaultCoords, [0, 0]];
32336
+
32337
+ // Adjust the lower point by angleOffsetDeg degrees
32338
+ coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
32339
+ // Position the upper point angle radians from the
32340
+ // lower point
32341
+ coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
32342
+ return coords;
32343
+ };
31880
32344
 
31881
32345
  function interactiveGraphReducer(state, action) {
31882
32346
  switch (action.type) {
@@ -31940,6 +32404,7 @@ function doMoveControlPoint(state, action) {
31940
32404
  coords: newCoords
31941
32405
  };
31942
32406
  }
32407
+ case "angle":
31943
32408
  case "circle":
31944
32409
  throw new Error("FIXME implement circle reducer");
31945
32410
  case "point":
@@ -32044,12 +32509,32 @@ function doMoveAll(state, action) {
32044
32509
  }
32045
32510
  function doMovePoint(state, action) {
32046
32511
  switch (state.type) {
32512
+ case "angle":
32513
+ // If the index is 1, we are moving the vertex of the angle,
32514
+ // which will move the other two points as well
32515
+ if (action.index === 1) {
32516
+ const updatedCoords = boundAndSnapAngleVertex(state, action);
32517
+ return {
32518
+ ...state,
32519
+ hasBeenInteractedWith: true,
32520
+ coords: updatedCoords
32521
+ };
32522
+ }
32523
+ return {
32524
+ ...state,
32525
+ hasBeenInteractedWith: true,
32526
+ coords: setAtIndex({
32527
+ array: state.coords,
32528
+ index: action.index,
32529
+ newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
32530
+ })
32531
+ };
32047
32532
  case "polygon":
32048
32533
  let newValue;
32049
32534
  if (state.snapTo === "sides") {
32050
32535
  newValue = boundAndSnapToSides(action.destination, state, action.index);
32051
32536
  } else if (state.snapTo === "angles") {
32052
- newValue = boundAndSnapToAngle(action.destination, state, action.index);
32537
+ newValue = boundAndSnapToPolygonAngle(action.destination, state, action.index);
32053
32538
  } else {
32054
32539
  newValue = boundAndSnapToGrid(action.destination, state);
32055
32540
  }
@@ -32090,7 +32575,7 @@ function doMovePoint(state, action) {
32090
32575
  // vertical line. If they are, then we don't want to move the point
32091
32576
  const newCoords = [...state.coords];
32092
32577
  newCoords[action.index] = boundDestination;
32093
- if (newCoords[0][0] === newCoords[1][0]) {
32578
+ if (newCoords[0][X] === newCoords[1][X]) {
32094
32579
  return state;
32095
32580
  }
32096
32581
  return {
@@ -32150,13 +32635,13 @@ function doMoveCenter(state, action) {
32150
32635
  // if it otherwise would be off the chart
32151
32636
  // ex: if the handle is on the right and we move the center
32152
32637
  // to the rightmost position, move the handle to the left
32153
- const [xMin, xMax] = state.range[0];
32638
+ const [xMin, xMax] = state.range[X];
32154
32639
  const [radX] = newRadiusPoint;
32155
32640
  if (radX < xMin || radX > xMax) {
32156
- const xJumpDist = (radX - constrainedCenter[0]) * 2;
32641
+ const xJumpDist = (radX - constrainedCenter[X]) * 2;
32157
32642
  const possibleNewX = radX - xJumpDist;
32158
32643
  if (possibleNewX >= xMin && possibleNewX <= xMax) {
32159
- newRadiusPoint[0] = possibleNewX;
32644
+ newRadiusPoint[X] = possibleNewX;
32160
32645
  }
32161
32646
  }
32162
32647
  return {
@@ -32174,11 +32659,11 @@ function doMoveRadiusPoint(state, action) {
32174
32659
  switch (state.type) {
32175
32660
  case "circle":
32176
32661
  {
32177
- const [xMin, xMax] = state.range[0];
32662
+ const [xMin, xMax] = state.range[X];
32178
32663
  const nextRadiusPoint = [
32179
32664
  // Constrain to graph range
32180
32665
  // The +0 is to convert -0 to +0
32181
- Math.min(Math.max(xMin, action.destination[0] + 0), xMax), state.center[1]];
32666
+ clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
32182
32667
  if (___default["default"].isEqual(nextRadiusPoint, state.center)) {
32183
32668
  return state;
32184
32669
  }
@@ -32216,10 +32701,10 @@ function doChangeRange(state, action) {
32216
32701
  }
32217
32702
  const getDeltaVertex = (maxMoves, minMoves, delta) => {
32218
32703
  const [deltaX, deltaY] = delta;
32219
- const maxXMove = Math.min(...maxMoves.map(move => move[0]));
32220
- const maxYMove = Math.min(...maxMoves.map(move => move[1]));
32221
- const minXMove = Math.max(...minMoves.map(move => move[0]));
32222
- const minYMove = Math.max(...minMoves.map(move => move[1]));
32704
+ const maxXMove = Math.min(...maxMoves.map(move => move[X]));
32705
+ const maxYMove = Math.min(...maxMoves.map(move => move[Y]));
32706
+ const minXMove = Math.max(...minMoves.map(move => move[X]));
32707
+ const minYMove = Math.max(...minMoves.map(move => move[Y]));
32223
32708
  const dx = clamp(deltaX, minXMove, maxXMove);
32224
32709
  const dy = clamp(deltaY, minYMove, maxYMove);
32225
32710
  return [dx, dy];
@@ -32253,11 +32738,130 @@ function boundAndSnapToGrid(point, _ref) {
32253
32738
  point
32254
32739
  }));
32255
32740
  }
32256
- function boundAndSnapToAngle(destinationPoint, _ref2, index) {
32741
+ function boundAndSnapAngleVertex(_ref2, _ref3) {
32257
32742
  let {
32258
32743
  range,
32259
- coords
32744
+ coords,
32745
+ snapStep
32260
32746
  } = _ref2;
32747
+ let {
32748
+ destination
32749
+ } = _ref3;
32750
+ // Needed to prevent updating the original coords before the checks for
32751
+ // degenerate triangles and overlapping sides
32752
+ const coordsCopy = [...coords];
32753
+
32754
+ // Get the current and upcoming positions of the vertex
32755
+ const startingVertex = coordsCopy[1];
32756
+ const newVertex = bound$1({
32757
+ snapStep,
32758
+ range,
32759
+ point: snap(snapStep, destination)
32760
+ });
32761
+
32762
+ // Get the vector from the starting vertex to the new vertex
32763
+ const delta = mafs.vec.add(newVertex, reverseVector(startingVertex));
32764
+
32765
+ // Apply the delta to each of the other two points so that the angle is maintained
32766
+ let valid = true;
32767
+ const newPoints = {};
32768
+ for (const i of [0, 2]) {
32769
+ const oldPoint = coordsCopy[i];
32770
+ let newPoint = mafs.vec.add(oldPoint, delta);
32771
+ let angle = findAngle(newVertex, newPoint);
32772
+ angle *= Math.PI / 180;
32773
+ newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
32774
+ newPoints[i] = newPoint;
32775
+
32776
+ // Check if the new point is too close to the vertex
32777
+ if (tooClose(newVertex, newPoint)) {
32778
+ valid = false;
32779
+ }
32780
+ }
32781
+
32782
+ // Update the vertex after snapping to the snapStep
32783
+ newPoints[1] = newVertex;
32784
+ // Only move points if all new positions are valid
32785
+ if (valid) {
32786
+ Object.entries(newPoints).forEach(_ref4 => {
32787
+ let [i, newPoint] = _ref4;
32788
+ coordsCopy[i] = newPoint;
32789
+ });
32790
+ }
32791
+ return coordsCopy;
32792
+ }
32793
+
32794
+ // This function is used to ensure that the vertex of
32795
+ // an angle is not too close to the other points
32796
+ function tooClose(point1, point2, range) {
32797
+ const safeDistance = 2;
32798
+ const distance = mafs.vec.dist(point1, point2);
32799
+ return distance < safeDistance;
32800
+ }
32801
+ function constrainToBoundsOnAngle(point, angle, range, snapStep) {
32802
+ // We're subtracting the snapStep from the lower bound and adding it to the upper bound
32803
+ // to ensure that the point is within the bounds of the graph even after snapping to the nearest degree
32804
+ const lower = [range[0][0] + snapStep[0], range[1][0] + snapStep[0]];
32805
+ const upper = [range[0][1] - snapStep[1], range[1][1] - snapStep[1]];
32806
+ let result = point;
32807
+ if (result[0] < lower[0]) {
32808
+ result = [lower[0], result[1] + (lower[0] - result[0]) * Math.tan(angle)];
32809
+ } else if (result[0] > upper[0]) {
32810
+ result = [upper[0], result[1] - (result[0] - upper[0]) * Math.tan(angle)];
32811
+ }
32812
+ if (result[1] < lower[1]) {
32813
+ result = [result[0] + (lower[1] - result[1]) / Math.tan(angle), lower[1]];
32814
+ } else if (result[1] > upper[1]) {
32815
+ result = [result[0] - (result[1] - upper[1]) / Math.tan(angle), upper[1]];
32816
+ }
32817
+ return result;
32818
+ }
32819
+ function boundAndSnapAngleEndPoints(destinationPoint, _ref5, index) {
32820
+ let {
32821
+ range,
32822
+ coords,
32823
+ snapDegrees,
32824
+ angleOffsetDeg,
32825
+ snapStep
32826
+ } = _ref5;
32827
+ const snap = snapDegrees || 1;
32828
+ const offsetDegrees = angleOffsetDeg || 0;
32829
+
32830
+ // Needed to prevent updating the original coords before the checks for
32831
+ // degenerate triangles and overlapping sides
32832
+ const coordsCopy = [...coords];
32833
+
32834
+ // We want to subtract or add the snapStep to the lower and upper bounds
32835
+ // respectively to ensure that the point is within the bounds of the graph
32836
+ // even after snapping to the nearest degree
32837
+ const angleRange = [[range[0][0] + snapStep[0], range[0][1] - snapStep[0]], [range[1][0] + snapStep[1], range[1][1] - snapStep[1]]];
32838
+
32839
+ // Takes the destination point and makes sure it is within the bounds of the graph
32840
+ // SnapStep is [0, 0] because we don't want to snap these points to the grid at all
32841
+ const boundPoint = bound$1({
32842
+ snapStep: [0, 0],
32843
+ range: angleRange,
32844
+ point: destinationPoint
32845
+ });
32846
+ coordsCopy[index] = boundPoint;
32847
+
32848
+ // Get the vertex of the angle
32849
+ const vertex = coords[1];
32850
+
32851
+ // Gets the angle between the coords and the vertex
32852
+ let angle = findAngle(coordsCopy[index], vertex);
32853
+
32854
+ // Snap the angle to the nearest multiple of snapDegrees (if provided)
32855
+ angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
32856
+ const distance = mafs.vec.dist(coordsCopy[index], vertex);
32857
+ const snappedValue = mafs.vec.add(vertex, polar(distance, angle));
32858
+ return snappedValue;
32859
+ }
32860
+ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
32861
+ let {
32862
+ range,
32863
+ coords
32864
+ } = _ref6;
32261
32865
  const startingPoint = coords[index];
32262
32866
 
32263
32867
  // Needed to prevent updating the original coords before the checks for
@@ -32285,7 +32889,7 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
32285
32889
  angles[rel(j)] = Math.round(angles[rel(j)]);
32286
32890
  });
32287
32891
  const getAngle = function (a, vertex, b) {
32288
- const angle = GraphUtils.findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
32892
+ const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
32289
32893
  return (angle + 360) % 360;
32290
32894
  };
32291
32895
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
@@ -32313,7 +32917,7 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
32313
32917
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
32314
32918
 
32315
32919
  // Angle at the second vertex of the polygon
32316
- const outerAngle = GraphUtils.findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
32920
+ const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
32317
32921
 
32318
32922
  // Uses the length of the side of the polygon (radial coordinate)
32319
32923
  // and the angle between the first and second sides of the
@@ -32321,11 +32925,11 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
32321
32925
  const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
32322
32926
  return kmath.vector.add(coordsCopy[rel(-1)], offset);
32323
32927
  }
32324
- function boundAndSnapToSides(destinationPoint, _ref3, index) {
32928
+ function boundAndSnapToSides(destinationPoint, _ref7, index) {
32325
32929
  let {
32326
32930
  range,
32327
32931
  coords
32328
- } = _ref3;
32932
+ } = _ref7;
32329
32933
  const startingPoint = coords[index];
32330
32934
 
32331
32935
  // Needed to prevent updating the original coords before the checks for
@@ -32364,7 +32968,7 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
32364
32968
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
32365
32969
 
32366
32970
  // Angle at the second vertex of the polygon
32367
- const outerAngle = GraphUtils.findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
32971
+ const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
32368
32972
 
32369
32973
  // Returns true if the points form a counter-clockwise turn;
32370
32974
  // a.k.a if the point is on the left or right of the polygon.
@@ -32382,12 +32986,12 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
32382
32986
  }
32383
32987
 
32384
32988
  // Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
32385
- function maxMove(_ref4) {
32989
+ function maxMove(_ref8) {
32386
32990
  let {
32387
32991
  snapStep,
32388
32992
  range,
32389
32993
  point
32390
- } = _ref4;
32994
+ } = _ref8;
32391
32995
  const topRight = bound$1({
32392
32996
  snapStep,
32393
32997
  range,
@@ -32397,12 +33001,12 @@ function maxMove(_ref4) {
32397
33001
  }
32398
33002
 
32399
33003
  // Returns the vector from the given point to the bottom-left corner of the graph when snapped to the grid
32400
- function minMove(_ref5) {
33004
+ function minMove(_ref9) {
32401
33005
  let {
32402
33006
  snapStep,
32403
33007
  range,
32404
33008
  point
32405
- } = _ref5;
33009
+ } = _ref9;
32406
33010
  const bottomLeft = bound$1({
32407
33011
  snapStep,
32408
33012
  range,
@@ -32435,65 +33039,6 @@ function setAtIndex(args) {
32435
33039
  return copy;
32436
33040
  }
32437
33041
 
32438
- const renderGraph = props => {
32439
- const {
32440
- state,
32441
- dispatch
32442
- } = props;
32443
- const {
32444
- type
32445
- } = state;
32446
- switch (type) {
32447
- case "segment":
32448
- return /*#__PURE__*/React__namespace.createElement(SegmentGraph, {
32449
- graphState: state,
32450
- dispatch: dispatch
32451
- });
32452
- case "linear-system":
32453
- return /*#__PURE__*/React__namespace.createElement(LinearSystemGraph, {
32454
- graphState: state,
32455
- dispatch: dispatch
32456
- });
32457
- case "linear":
32458
- return /*#__PURE__*/React__namespace.createElement(LinearGraph, {
32459
- graphState: state,
32460
- dispatch: dispatch
32461
- });
32462
- case "ray":
32463
- return /*#__PURE__*/React__namespace.createElement(RayGraph, {
32464
- graphState: state,
32465
- dispatch: dispatch
32466
- });
32467
- case "polygon":
32468
- return /*#__PURE__*/React__namespace.createElement(PolygonGraph, {
32469
- graphState: state,
32470
- dispatch: dispatch
32471
- });
32472
- case "point":
32473
- return /*#__PURE__*/React__namespace.createElement(PointGraph, {
32474
- graphState: state,
32475
- dispatch: dispatch
32476
- });
32477
- case "circle":
32478
- return /*#__PURE__*/React__namespace.createElement(CircleGraph, {
32479
- graphState: state,
32480
- dispatch: dispatch
32481
- });
32482
- case "quadratic":
32483
- return /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
32484
- graphState: state,
32485
- dispatch: dispatch
32486
- });
32487
- case "sinusoid":
32488
- return /*#__PURE__*/React__namespace.createElement(SinusoidGraph, {
32489
- graphState: state,
32490
- dispatch: dispatch
32491
- });
32492
- default:
32493
- return new wonderStuffCore.UnreachableCaseError(type);
32494
- }
32495
- };
32496
-
32497
33042
  // Rather than be tightly bound to how data was structured in
32498
33043
  // the legacy interactive graph, this lets us store state
32499
33044
  // however we want and we just transform it before handing it off
@@ -32563,72 +33108,6 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
32563
33108
  dispatch: dispatch
32564
33109
  }));
32565
33110
  });
32566
- const MafsGraph = props => {
32567
- const {
32568
- state,
32569
- dispatch,
32570
- labels
32571
- } = props;
32572
- const [width, height] = props.box;
32573
- const tickStep = props.step;
32574
- return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
32575
- value: {
32576
- range: state.range,
32577
- snapStep: state.snapStep,
32578
- markings: props.markings,
32579
- tickStep: tickStep,
32580
- gridStep: props.gridStep,
32581
- showTooltips: !!props.showTooltips,
32582
- graphDimensionsInPixels: props.box,
32583
- width,
32584
- height,
32585
- labels
32586
- }
32587
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32588
- className: "mafs-graph",
32589
- style: {
32590
- width,
32591
- height,
32592
- position: "relative",
32593
- padding: "25px 25px 0 0",
32594
- boxSizing: "content-box",
32595
- marginLeft: "20px",
32596
- marginBottom: "20px"
32597
- }
32598
- }, /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
32599
- box: props.box,
32600
- backgroundImage: props.backgroundImage
32601
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32602
- style: {
32603
- position: "absolute",
32604
- bottom: 0,
32605
- left: 0
32606
- }
32607
- }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null), /*#__PURE__*/React__namespace.createElement(AxisTickLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
32608
- preserveAspectRatio: false,
32609
- viewBox: {
32610
- x: state.range[0],
32611
- y: state.range[1],
32612
- padding: 0
32613
- },
32614
- pan: false,
32615
- zoom: false,
32616
- width: width,
32617
- height: height
32618
- }, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement(Grid, {
32619
- tickStep: props.step,
32620
- gridStep: props.gridStep,
32621
- range: state.range,
32622
- containerSizeClass: props.containerSizeClass,
32623
- markings: props.markings
32624
- }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32625
- lockedFigures: props.lockedFigures,
32626
- range: state.range
32627
- }), "/* Protractor */", props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32628
- state,
32629
- dispatch
32630
- })))));
32631
- };
32632
33111
 
32633
33112
  const {
32634
33113
  DeprecationMixin
@@ -32677,7 +33156,7 @@ const _isClickToAddPoints = props => {
32677
33156
  return props.graph.type === "point" && props.graph.numPoints === UNLIMITED || props.graph.type === "polygon" && props.graph.numSides === UNLIMITED;
32678
33157
  };
32679
33158
  const makeInvalidTypeError = (functionName, graphType) => {
32680
- return new PerseusError(`${functionName} called but current graph type is not a '${graphType}'`, Errors.NotAllowed, {
33159
+ return new perseusCore.PerseusError(`${functionName} called but current graph type is not a '${graphType}'`, perseusCore.Errors.NotAllowed, {
32681
33160
  metadata: {
32682
33161
  graphType
32683
33162
  }
@@ -32776,7 +33255,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
32776
33255
  // This function should only be called when this.isClickToAddPoints()
32777
33256
  // is true
32778
33257
  if (!this.isClickToAddPoints()) {
32779
- throw new PerseusError("handleAddPointsClick should not be registered" + "when isClickToAddPoints() is false", Errors.NotAllowed);
33258
+ throw new perseusCore.PerseusError("handleAddPointsClick should not be registered" + "when isClickToAddPoints() is false", perseusCore.Errors.NotAllowed);
32780
33259
  }
32781
33260
  if (!this.isCoordInTrash(coord)) {
32782
33261
  let point;
@@ -33492,7 +33971,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
33492
33971
  // Solve for side by using the law of sines
33493
33972
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
33494
33973
  const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
33495
- const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
33974
+ const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
33496
33975
  return this.graphie?.addPoints(coords[rel(-1)], offset);
33497
33976
  }
33498
33977
  // @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
@@ -33516,7 +33995,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
33516
33995
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
33517
33996
  const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
33518
33997
  const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
33519
- const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
33998
+ const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
33520
33999
  return this.graphie?.addPoints(coords[rel(-1)], offset);
33521
34000
  }
33522
34001
  // Snap to grid (already done)
@@ -33914,7 +34393,7 @@ class InteractiveGraph extends React__namespace.Component {
33914
34393
  if (this.legacyGraphRef.current) {
33915
34394
  return this.legacyGraphRef.current.getUserInput();
33916
34395
  }
33917
- throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
34396
+ throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
33918
34397
  };
33919
34398
  simpleValidate(rubric) {
33920
34399
  return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
@@ -34123,11 +34602,9 @@ class InteractiveGraph extends React__namespace.Component {
34123
34602
  const radius = magnitude(vector(...coords));
34124
34603
 
34125
34604
  // Adjust the lower point by angleOffsetDeg degrees
34126
- // @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
34127
34605
  coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
34128
34606
  // Position the upper point angle radians from the
34129
34607
  // lower point
34130
- // @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
34131
34608
  coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
34132
34609
  return coords;
34133
34610
  }
@@ -39642,7 +40119,7 @@ class Card extends React__namespace.Component {
39642
40119
  // Event handlers should be unbound before component unmounting, but
39643
40120
  // just in case...
39644
40121
  if (this.mouseMoveUpBound) {
39645
- Log.error("Removing an element with bound event handlers.", Errors.Internal);
40122
+ Log.error("Removing an element with bound event handlers.", perseusCore.Errors.Internal);
39646
40123
  this.unbindMouseMoveUp();
39647
40124
  Util.resetTouchHandlers();
39648
40125
  }
@@ -40463,7 +40940,7 @@ function deserializeHighlight(serializedHighlight, wordRanges) {
40463
40940
  } = serializedHighlight.range;
40464
40941
  const firstWord = wordRanges[firstWordIndex];
40465
40942
  if (!firstWord) {
40466
- throw new PerseusError(`first word index ${firstWord} is out of bounds: ` + `must be 0–${wordRanges.length - 1} inclusive`, Errors.InvalidInput, {
40943
+ throw new perseusCore.PerseusError(`first word index ${firstWord} is out of bounds: ` + `must be 0–${wordRanges.length - 1} inclusive`, perseusCore.Errors.InvalidInput, {
40467
40944
  metadata: {
40468
40945
  firstWord,
40469
40946
  wordRanges: JSON.stringify(wordRanges)
@@ -40472,7 +40949,7 @@ function deserializeHighlight(serializedHighlight, wordRanges) {
40472
40949
  }
40473
40950
  const lastWord = wordRanges[lastWordIndex];
40474
40951
  if (!lastWord) {
40475
- throw new PerseusError(`last word index ${lastWord} is out of bounds: ` + `must be 0–${wordRanges.length - 1} inclusive`, Errors.InvalidInput, {
40952
+ throw new perseusCore.PerseusError(`last word index ${lastWord} is out of bounds: ` + `must be 0–${wordRanges.length - 1} inclusive`, perseusCore.Errors.InvalidInput, {
40476
40953
  metadata: {
40477
40954
  lastWord,
40478
40955
  wordRanges: JSON.stringify(wordRanges)
@@ -41225,7 +41702,7 @@ class WordIndexer extends React__namespace.PureComponent {
41225
41702
  _getWordRanges() {
41226
41703
  const container = this._container;
41227
41704
  if (!container) {
41228
- throw new PerseusError("invariant violation: `this._container` is not defined, but " + "this method is only called after mount/update", Errors.NotAllowed);
41705
+ throw new perseusCore.PerseusError("invariant violation: `this._container` is not defined, but " + "this method is only called after mount/update", perseusCore.Errors.NotAllowed);
41229
41706
  }
41230
41707
 
41231
41708
  // Create a TreeWalker that visits text nodes and element nodes.
@@ -41691,6 +42168,7 @@ const rules = {
41691
42168
  }, /*#__PURE__*/React__namespace.createElement("span", {
41692
42169
  style: SQUARE_LABEL_STYLE
41693
42170
  }, node.content)), /*#__PURE__*/React__namespace.createElement(AltText, {
42171
+ key: "alt-text",
41694
42172
  id: "questionMarker",
41695
42173
  number: node.content
41696
42174
  }), node.space ? "\u00A0" : null];
@@ -41715,6 +42193,7 @@ const rules = {
41715
42193
  }, /*#__PURE__*/React__namespace.createElement("span", {
41716
42194
  style: CIRCLE_LABEL_STYLE
41717
42195
  }, node.content)), /*#__PURE__*/React__namespace.createElement(AltText, {
42196
+ key: "alt-text",
41718
42197
  id: "circleMarker",
41719
42198
  number: node.content
41720
42199
  }), node.space ? "\u00A0" : null];
@@ -41738,6 +42217,7 @@ const rules = {
41738
42217
  className: "perseus-passage-bracket-label",
41739
42218
  "aria-hidden": "true"
41740
42219
  }, "[", node.content, "]"), /*#__PURE__*/React__namespace.createElement(AltText, {
42220
+ key: "alt-text",
41741
42221
  id: "sentenceMarker",
41742
42222
  number: node.content
41743
42223
  }), node.space ? "\u00A0" : null];
@@ -44747,7 +45227,7 @@ const init = function (options) {
44747
45227
 
44748
45228
  // This file is processed by a Rollup plugin (replace) to inject the production
44749
45229
  const libName = "@khanacademy/perseus";
44750
- const libVersion = "24.2.0";
45230
+ const libVersion = "25.0.0";
44751
45231
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44752
45232
 
44753
45233
  const apiVersion = {
@@ -46123,9 +46603,9 @@ tree, shape, path, mappers
46123
46603
  // @ts-expect-error - TS2315 - Type 'ArrayNode' is not generic.
46124
46604
  const array = tree;
46125
46605
  if (!Array.isArray(array)) {
46126
- throw new PerseusError(`Invalid object of type "${typeof array}" found at path ` +
46606
+ throw new perseusCore.PerseusError(`Invalid object of type "${typeof array}" found at path ` +
46127
46607
  // @ts-expect-error - TS2769 - No overload matches this call.
46128
- `${["<root>"].concat(path).join(".")}. Expected array.`, Errors.Internal);
46608
+ `${["<root>"].concat(path).join(".")}. Expected array.`, perseusCore.Errors.Internal);
46129
46609
  }
46130
46610
  const elementShape = shape.elementShape;
46131
46611
  // @ts-expect-error - TS2315 - Type 'ArrayNode' is not generic.
@@ -46135,30 +46615,30 @@ tree, shape, path, mappers
46135
46615
  if (shape.type === "object") {
46136
46616
  const object = tree;
46137
46617
  if (object && typeof object !== "object") {
46138
- throw new PerseusError(`Invalid object of type "${typeof object}" found at ` +
46618
+ throw new perseusCore.PerseusError(`Invalid object of type "${typeof object}" found at ` +
46139
46619
  // @ts-expect-error - TS2769 - No overload matches this call.
46140
- `path ${["<root>"].concat(path).join(".")}. Expected ` + `"object" type.`, Errors.InvalidInput);
46620
+ `path ${["<root>"].concat(path).join(".")}. Expected ` + `"object" type.`, perseusCore.Errors.InvalidInput);
46141
46621
  }
46142
46622
  const valueShapes = shape.shape;
46143
46623
  if (!valueShapes) {
46144
- throw new PerseusError(`Unexpected shape ${JSON.stringify(shape)} at path ` +
46624
+ throw new perseusCore.PerseusError(`Unexpected shape ${JSON.stringify(shape)} at path ` +
46145
46625
  // @ts-expect-error - TS2769 - No overload matches this call.
46146
- `${["<root>"].concat(path).join(".")}.`, Errors.InvalidInput);
46626
+ `${["<root>"].concat(path).join(".")}.`, perseusCore.Errors.InvalidInput);
46147
46627
  }
46148
46628
  const newObject = {};
46149
46629
  Object.keys(valueShapes).forEach(key => {
46150
46630
  if (!(key in object)) {
46151
- throw new PerseusError(`Key "${key}" is missing from shape at path ` +
46631
+ throw new perseusCore.PerseusError(`Key "${key}" is missing from shape at path ` +
46152
46632
  // @ts-expect-error - TS2769 - No overload matches this call.
46153
- `${["<root>"].concat(path).join(".")}.`, Errors.InvalidInput);
46633
+ `${["<root>"].concat(path).join(".")}.`, perseusCore.Errors.InvalidInput);
46154
46634
  }
46155
46635
  newObject[key] = mapTree(object[key], valueShapes[key], path.concat(key), mappers);
46156
46636
  });
46157
46637
  return newObject;
46158
46638
  }
46159
- throw new PerseusError(
46639
+ throw new perseusCore.PerseusError(
46160
46640
  // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
46161
- `unexpected shape type ${shape.type}`, Errors.InvalidInput);
46641
+ `unexpected shape type ${shape.type}`, perseusCore.Errors.InvalidInput);
46162
46642
  }
46163
46643
 
46164
46644
  /**
@@ -46210,9 +46690,9 @@ function buildEmptyItemTreeForShape(shape) {
46210
46690
  });
46211
46691
  return object;
46212
46692
  }
46213
- throw new PerseusError(
46693
+ throw new perseusCore.PerseusError(
46214
46694
  // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
46215
- `unexpected shape type ${shape.type}`, Errors.InvalidInput);
46695
+ `unexpected shape type ${shape.type}`, perseusCore.Errors.InvalidInput);
46216
46696
  }
46217
46697
 
46218
46698
  /**
@@ -46287,7 +46767,7 @@ function inferItemTreeShape(node) {
46287
46767
  });
46288
46768
  return shapes.shape(valueShapes);
46289
46769
  }
46290
- throw new PerseusError(`unexpected multi-item node ${JSON.stringify(node)}`, Errors.InvalidInput);
46770
+ throw new perseusCore.PerseusError(`unexpected multi-item node ${JSON.stringify(node)}`, perseusCore.Errors.InvalidInput);
46291
46771
  }
46292
46772
 
46293
46773
  /**
@@ -46365,7 +46845,7 @@ class MultiRenderer extends React__namespace.Component {
46365
46845
  renderError: null
46366
46846
  };
46367
46847
  } catch (e) {
46368
- Log.error("Error building tree state", Errors.Internal, {
46848
+ Log.error("Error building tree state", perseusCore.Errors.Internal, {
46369
46849
  cause: e
46370
46850
  });
46371
46851
  return {
@@ -46729,9 +47209,9 @@ function buildTreePropTypeForShape(shape) {
46729
47209
  });
46730
47210
  return PropTypes__default["default"].shape(propTypeShape);
46731
47211
  }
46732
- throw new PerseusError(
47212
+ throw new perseusCore.PerseusError(
46733
47213
  // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
46734
- `unexpected shape type ${shape.type}`, Errors.InvalidInput);
47214
+ `unexpected shape type ${shape.type}`, perseusCore.Errors.InvalidInput);
46735
47215
  }
46736
47216
 
46737
47217
  /**
@@ -46854,7 +47334,7 @@ const isRendererContentRenderableBy = function (rendererOptions, rendererContent
46854
47334
  };
46855
47335
  const isItemRenderableByVersion = function (itemData, rendererContentVersion) {
46856
47336
  if (itemData == null || rendererContentVersion == null) {
46857
- throw new PerseusError("missing parameter to Perseus.isRenderable.item", Errors.InvalidInput);
47337
+ throw new perseusCore.PerseusError("missing parameter to Perseus.isRenderable.item", perseusCore.Errors.InvalidInput);
46858
47338
  }
46859
47339
  if (itemData._multi) {
46860
47340
  const shape = inferItemShape$1(itemData);
@@ -47775,7 +48255,6 @@ exports.DashPicker = DashPicker;
47775
48255
  exports.Dependencies = dependencies;
47776
48256
  exports.EditorJsonify = EditorJsonify;
47777
48257
  exports.ElementContainer = ElementContainer;
47778
- exports.Errors = Errors;
47779
48258
  exports.Expression = Expression;
47780
48259
  exports.GrapherUtil = util;
47781
48260
  exports.GrapherWidget = Grapher$1;
@@ -47792,7 +48271,6 @@ exports.Log = Log;
47792
48271
  exports.MatrixWidget = Matrix$1;
47793
48272
  exports.MultiItems = MultiItems;
47794
48273
  exports.NumericInput = NumericInput$1;
47795
- exports.PerseusError = PerseusError;
47796
48274
  exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
47797
48275
  exports.PerseusI18nContext = PerseusI18nContext;
47798
48276
  exports.PerseusI18nContextProvider = PerseusI18nContextProvider;