@khanacademy/perseus 22.4.0 → 22.4.2

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.
package/dist/es/index.css CHANGED
@@ -4419,7 +4419,9 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4419
4419
  );
4420
4420
  }
4421
4421
 
4422
- .MafsView .movable-point:focus-visible .movable-point-focus-outline {
4422
+ .MafsView
4423
+ .movable-point:is(:focus-visible, .movable-point--focus)
4424
+ .movable-point-focus-outline {
4423
4425
  visibility: visible;
4424
4426
  }
4425
4427
 
package/dist/es/index.js CHANGED
@@ -2,11 +2,11 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, useUniqueIdWithMock, UniqueIDProvider, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
7
  import classNames from 'classnames';
8
8
  import * as React from 'react';
9
- import React__default, { useContext, useState, useEffect, useRef, createContext, useImperativeHandle } from 'react';
9
+ import React__default, { useContext, useState, useEffect, useRef, createContext, forwardRef, useImperativeHandle } from 'react';
10
10
  import _, { debounce as debounce$1 } from 'underscore';
11
11
  import Clickable from '@khanacademy/wonder-blocks-clickable';
12
12
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
@@ -341,7 +341,7 @@ class InnerMathInput extends React.Component {
341
341
  locale
342
342
  } = this.context;
343
343
  // Initialize MathQuill.MathField instance
344
- this.__mathField = createMathField(this.__mathFieldWrapperRef, this.props.mathInputStrings, baseConfig => _extends({}, baseConfig, {
344
+ this.__mathField = createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings, baseConfig => _extends({}, baseConfig, {
345
345
  handlers: {
346
346
  edit: debounce(mathField => {
347
347
  // This handler is guaranteed to be called on change, but
@@ -23407,8 +23407,6 @@ const Choice = function Choice(props) {
23407
23407
  const showDimmed = !reviewMode && apiOptions.readOnly || crossedOut;
23408
23408
  const letter = getChoiceLetter(pos, strings);
23409
23409
  const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness, strings);
23410
- const idFactory = useUniqueIdWithMock(`choice`);
23411
- const choiceId = idFactory.get(letter);
23412
23410
  return /*#__PURE__*/React.createElement("div", {
23413
23411
  style: {
23414
23412
  // @ts-expect-error - TS2322 - Type '{ dispay: string; flexDirection: "column"; color: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
@@ -23427,9 +23425,8 @@ const Choice = function Choice(props) {
23427
23425
  }
23428
23426
  }, /*#__PURE__*/React.createElement("div", {
23429
23427
  className: "perseus-sr-only"
23430
- }, /*#__PURE__*/React.createElement("input", {
23428
+ }, /*#__PURE__*/React.createElement("label", null, a11yText, " \xA0 ", content, /*#__PURE__*/React.createElement("input", {
23431
23429
  type: multipleSelect ? "checkbox" : "radio",
23432
- id: choiceId,
23433
23430
  checked: checked,
23434
23431
  onClick: () => {
23435
23432
  // If we're checking a crossed-out option, let's
@@ -23443,9 +23440,7 @@ const Choice = function Choice(props) {
23443
23440
  disabled: disabled || apiOptions.readOnly,
23444
23441
  tabIndex: -1,
23445
23442
  className: ClassNames.RADIO.OPTION_CONTENT
23446
- }), /*#__PURE__*/React.createElement("label", {
23447
- htmlFor: choiceId
23448
- }, a11yText, " \xA0 ", content)), /*#__PURE__*/React.createElement(Clickable, {
23443
+ }))), /*#__PURE__*/React.createElement(Clickable, {
23449
23444
  onClick: () => {
23450
23445
  // If we're checking a crossed-out option, let's
23451
23446
  // also uncross it.
@@ -29046,6 +29041,192 @@ function snap(snapStep, point) {
29046
29041
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29047
29042
  }
29048
29043
 
29044
+ // The hitbox size of 48px by 48px is preserved from the legacy interactive
29045
+ // graph.
29046
+ const hitboxSizePx = 48;
29047
+
29048
+ // MovablePointView is a purely presentational component (i.e. it is a pure
29049
+ // function with no state or effects) that renders the SVG for a movable point
29050
+ // on an interactive graph.
29051
+ //
29052
+ // It has two modes for managing tabbing / focus: "controlled" (where the caller
29053
+ // manages the display of the focus ring, and the point itself cannot be
29054
+ // focused) and "uncontrolled" (where the point is focusable, and the browser
29055
+ // manages the focus state and styling). For context on why we did this, see
29056
+ // the description of https://github.com/Khan/perseus/pull/1240
29057
+ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
29058
+ var _Object$entries$find$, _Object$entries$find;
29059
+ const {
29060
+ range,
29061
+ markings,
29062
+ showTooltips
29063
+ } = useGraphConfig();
29064
+ const {
29065
+ point,
29066
+ color: color$1 = color.blue,
29067
+ dragging,
29068
+ focusBehavior
29069
+ } = props;
29070
+
29071
+ // WB Tooltip requires a color name for the background color.
29072
+ // Since the color in props is a hex value, a reverse lookup is needed.
29073
+ const wbColorName = (_Object$entries$find$ = (_Object$entries$find = Object.entries(color).find(([_, value]) => value === color$1)) == null ? void 0 : _Object$entries$find[0]) != null ? _Object$entries$find$ : "blue";
29074
+ const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""} ${focusClass(focusBehavior)}`;
29075
+ const [[x, y]] = useTransformVectorsToPixels(point);
29076
+ const [xMin, xMax] = range[0];
29077
+ const [yMin, yMax] = range[1];
29078
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29079
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29080
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29081
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29082
+ const showHairlines = dragging && markings !== "none";
29083
+ const hairlines = /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
29084
+ x1: verticalStartX,
29085
+ y1: y,
29086
+ x2: verticalEndX,
29087
+ y2: y,
29088
+ stroke: color$1
29089
+ }), /*#__PURE__*/React.createElement("line", {
29090
+ x1: x,
29091
+ y1: horizontalStartY,
29092
+ x2: x,
29093
+ y2: horizontalEndY,
29094
+ stroke: color$1
29095
+ }));
29096
+ const svgForPoint = /*#__PURE__*/React.createElement("g", {
29097
+ ref: hitboxRef,
29098
+ className: pointClasses,
29099
+ style: {
29100
+ "--movable-point-color": color$1
29101
+ },
29102
+ "data-testid": "movable-point",
29103
+ tabIndex: tabIndex(focusBehavior)
29104
+ }, /*#__PURE__*/React.createElement("circle", {
29105
+ className: "movable-point-hitbox",
29106
+ r: hitboxSizePx / 2,
29107
+ cx: x,
29108
+ cy: y
29109
+ }), /*#__PURE__*/React.createElement("circle", {
29110
+ className: "movable-point-halo",
29111
+ cx: x,
29112
+ cy: y
29113
+ }), /*#__PURE__*/React.createElement("circle", {
29114
+ className: "movable-point-ring",
29115
+ cx: x,
29116
+ cy: y
29117
+ }), /*#__PURE__*/React.createElement("circle", {
29118
+ className: "movable-point-focus-outline",
29119
+ cx: x,
29120
+ cy: y
29121
+ }), /*#__PURE__*/React.createElement("circle", {
29122
+ className: "movable-point-center",
29123
+ cx: x,
29124
+ cy: y,
29125
+ style: {
29126
+ fill: color$1
29127
+ },
29128
+ "data-testid": "movable-point__center"
29129
+ }));
29130
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
29131
+ autoUpdate: true,
29132
+ backgroundColor: wbColorName,
29133
+ content: `(${point[0]}, ${point[1]})`,
29134
+ contentStyle: {
29135
+ color: "white"
29136
+ }
29137
+ }, svgForPoint) : svgForPoint);
29138
+ });
29139
+ function focusClass(config) {
29140
+ if (config.type === "controlled" && config.showFocusRing) {
29141
+ return "movable-point--focus";
29142
+ }
29143
+ return "";
29144
+ }
29145
+ function tabIndex(config) {
29146
+ if (config.type === "uncontrolled") {
29147
+ return config.tabIndex;
29148
+ }
29149
+ return undefined;
29150
+ }
29151
+
29152
+ const Segment = props => {
29153
+ const {
29154
+ onMoveLine,
29155
+ color,
29156
+ points: [start, end],
29157
+ extend
29158
+ } = props;
29159
+
29160
+ // We use separate focusableHandle elements, instead of letting the movable
29161
+ // points themselves be focusable, to allow the tab order of the points to
29162
+ // be different from the rendering order. We had to solve for the following
29163
+ // constraints:
29164
+ // - SVG has no equivalent of z-index, so the order of elements in the
29165
+ // document determines the order in which they're painted. We want the
29166
+ // movable line segment to render behind its endpoints (it looks weird
29167
+ // otherwise) so we have to render the line first.
29168
+ // - There isn't a browser-native way to customize tab order, other than
29169
+ // setting tabindex > 0. But that bumps elements to the front of the
29170
+ // tab order for the entire page, which is not what we want.
29171
+ const {
29172
+ visiblePoint: visiblePoint1,
29173
+ focusableHandle: focusableHandle1
29174
+ } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29175
+ const {
29176
+ visiblePoint: visiblePoint2,
29177
+ focusableHandle: focusableHandle2
29178
+ } = useControlPoint(end, color, p => props.onMovePoint(1, p));
29179
+ return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle1, /*#__PURE__*/React.createElement(MovableLine, {
29180
+ start: start,
29181
+ end: end,
29182
+ stroke: color,
29183
+ extend: extend,
29184
+ onMove: onMoveLine
29185
+ }), focusableHandle2, visiblePoint1, visiblePoint2);
29186
+ };
29187
+ function useControlPoint(point, color, onMovePoint) {
29188
+ const {
29189
+ snapStep
29190
+ } = useGraphConfig();
29191
+ const [focused, setFocused] = useState(false);
29192
+ const keyboardHandleRef = useRef(null);
29193
+ useMovable({
29194
+ gestureTarget: keyboardHandleRef,
29195
+ point,
29196
+ onMove: onMovePoint,
29197
+ constrain: p => snap(snapStep, p)
29198
+ });
29199
+ const visiblePointRef = useRef(null);
29200
+ const {
29201
+ dragging
29202
+ } = useMovable({
29203
+ gestureTarget: visiblePointRef,
29204
+ point,
29205
+ onMove: onMovePoint,
29206
+ constrain: p => snap(snapStep, p)
29207
+ });
29208
+ const focusableHandle = /*#__PURE__*/React.createElement("g", {
29209
+ "data-testid": "movable-point__focusable-handle",
29210
+ tabIndex: 0,
29211
+ ref: keyboardHandleRef,
29212
+ onFocus: () => setFocused(true),
29213
+ onBlur: () => setFocused(false)
29214
+ });
29215
+ const visiblePoint = /*#__PURE__*/React.createElement(MovablePointView, {
29216
+ point: point,
29217
+ dragging: dragging,
29218
+ color: color,
29219
+ ref: visiblePointRef,
29220
+ focusBehavior: {
29221
+ type: "controlled",
29222
+ showFocusRing: focused
29223
+ }
29224
+ });
29225
+ return {
29226
+ focusableHandle,
29227
+ visiblePoint
29228
+ };
29229
+ }
29049
29230
  const defaultStroke = "var(--movable-line-stroke-color)";
29050
29231
  const MovableLine = props => {
29051
29232
  const {
@@ -29058,12 +29239,13 @@ const MovableLine = props => {
29058
29239
  const midpoint = vec.midpoint(start, end);
29059
29240
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29060
29241
  const {
29242
+ range,
29061
29243
  graphDimensionsInPixels
29062
29244
  } = useGraphConfig();
29063
29245
  let startExtend = undefined;
29064
29246
  let endExtend = undefined;
29065
29247
  if (extend) {
29066
- const trimmedRange = trimRange(extend.range, graphDimensionsInPixels);
29248
+ const trimmedRange = trimRange(range, graphDimensionsInPixels);
29067
29249
  startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29068
29250
  endExtend = extend.end ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29069
29251
  }
@@ -29142,101 +29324,6 @@ function size(interval) {
29142
29324
  return interval[1] - interval[0];
29143
29325
  }
29144
29326
 
29145
- // The hitbox size of 48px by 48px is preserved from the legacy interactive
29146
- // graph.
29147
- const hitboxSizePx = 48;
29148
- const StyledMovablePoint = props => {
29149
- var _Object$entries$find$, _Object$entries$find;
29150
- const {
29151
- range,
29152
- snapStep,
29153
- markings,
29154
- showTooltips
29155
- } = useGraphConfig();
29156
- const hitboxRef = useRef(null);
29157
- const {
29158
- point,
29159
- onMove,
29160
- color: color$1 = color.blue
29161
- } = props;
29162
-
29163
- // WB Tooltip requires a color name for the background color.
29164
- // Since the color in props is a hex value, a reverse lookup is needed.
29165
- const wbColorName = (_Object$entries$find$ = (_Object$entries$find = Object.entries(color).find(([_, value]) => value === color$1)) == null ? void 0 : _Object$entries$find[0]) != null ? _Object$entries$find$ : "blue";
29166
- const {
29167
- dragging
29168
- } = useMovable({
29169
- gestureTarget: hitboxRef,
29170
- point,
29171
- onMove,
29172
- constrain: p => snap(snapStep, p)
29173
- });
29174
- const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""}`;
29175
- const [[x, y]] = useTransformVectorsToPixels(point);
29176
- const [xMin, xMax] = range[0];
29177
- const [yMin, yMax] = range[1];
29178
- const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29179
- const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29180
- const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29181
- const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29182
- const showHairlines = dragging && markings !== "none";
29183
- const hairlines = /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
29184
- x1: verticalStartX,
29185
- y1: y,
29186
- x2: verticalEndX,
29187
- y2: y,
29188
- stroke: color$1
29189
- }), /*#__PURE__*/React.createElement("line", {
29190
- x1: x,
29191
- y1: horizontalStartY,
29192
- x2: x,
29193
- y2: horizontalEndY,
29194
- stroke: color$1
29195
- }));
29196
- const svgForPoint = /*#__PURE__*/React.createElement("g", {
29197
- ref: hitboxRef,
29198
- className: pointClasses,
29199
- tabIndex: 0,
29200
- style: {
29201
- "--movable-point-color": color$1
29202
- },
29203
- "data-testid": "movable-point"
29204
- }, /*#__PURE__*/React.createElement("circle", {
29205
- className: "movable-point-hitbox",
29206
- r: hitboxSizePx / 2,
29207
- cx: x,
29208
- cy: y
29209
- }), /*#__PURE__*/React.createElement("circle", {
29210
- className: "movable-point-halo",
29211
- cx: x,
29212
- cy: y
29213
- }), /*#__PURE__*/React.createElement("circle", {
29214
- className: "movable-point-ring",
29215
- cx: x,
29216
- cy: y
29217
- }), /*#__PURE__*/React.createElement("circle", {
29218
- className: "movable-point-focus-outline",
29219
- cx: x,
29220
- cy: y
29221
- }), /*#__PURE__*/React.createElement("circle", {
29222
- className: "movable-point-center",
29223
- cx: x,
29224
- cy: y,
29225
- style: {
29226
- fill: color$1
29227
- },
29228
- "data-testid": "movable-point__center"
29229
- }));
29230
- return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
29231
- autoUpdate: true,
29232
- backgroundColor: wbColorName,
29233
- content: `(${point[0]}, ${point[1]})`,
29234
- contentStyle: {
29235
- color: "white"
29236
- }
29237
- }, svgForPoint) : svgForPoint);
29238
- };
29239
-
29240
29327
  const SegmentGraph = props => {
29241
29328
  const {
29242
29329
  dispatch
@@ -29244,7 +29331,7 @@ const SegmentGraph = props => {
29244
29331
  const {
29245
29332
  coords: segments
29246
29333
  } = props.graphState;
29247
- return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(SegmentView, {
29334
+ return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(Segment, {
29248
29335
  key: i,
29249
29336
  points: segment,
29250
29337
  onMoveLine: delta => {
@@ -29255,27 +29342,6 @@ const SegmentGraph = props => {
29255
29342
  }
29256
29343
  })));
29257
29344
  };
29258
- const SegmentView = props => {
29259
- const {
29260
- onMoveLine: onMoveSegment,
29261
- points: [start, end]
29262
- } = props;
29263
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
29264
- start: start,
29265
- end: end,
29266
- onMove: onMoveSegment
29267
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29268
- point: start,
29269
- onMove: newPoint => {
29270
- props.onMovePoint(0, newPoint);
29271
- }
29272
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29273
- point: end,
29274
- onMove: newPoint => {
29275
- props.onMovePoint(1, newPoint);
29276
- }
29277
- }));
29278
- };
29279
29345
 
29280
29346
  const LinearGraph = props => {
29281
29347
  const {
@@ -29285,49 +29351,19 @@ const LinearGraph = props => {
29285
29351
  coords: lines
29286
29352
  } = props.graphState;
29287
29353
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29288
- return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(LineView, {
29354
+ return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(Segment, {
29289
29355
  key: i,
29290
29356
  points: line,
29291
29357
  onMoveLine: delta => {
29292
29358
  dispatch(moveLine(i, delta));
29293
29359
  },
29294
- onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29295
- stroke: colors[i]
29296
- })));
29297
- };
29298
- const LineView = props => {
29299
- const {
29300
- onMoveLine,
29301
- onMovePoint,
29302
- points: [start, end],
29303
- stroke
29304
- } = props;
29305
- const {
29306
- range
29307
- } = useGraphConfig();
29308
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
29309
- start: start,
29310
- end: end,
29311
- onMove: onMoveLine,
29312
29360
  extend: {
29313
29361
  start: true,
29314
- end: true,
29315
- range
29362
+ end: true
29316
29363
  },
29317
- stroke: stroke
29318
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29319
- point: start,
29320
- onMove: newPoint => {
29321
- onMovePoint(0, newPoint);
29322
- },
29323
- color: stroke
29324
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29325
- point: end,
29326
- onMove: newPoint => {
29327
- onMovePoint(1, newPoint);
29328
- },
29329
- color: stroke
29330
- }));
29364
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29365
+ color: colors[i]
29366
+ })));
29331
29367
  };
29332
29368
 
29333
29369
  const RayGraph = props => {
@@ -29335,30 +29371,21 @@ const RayGraph = props => {
29335
29371
  dispatch
29336
29372
  } = props;
29337
29373
  const {
29338
- coords: lines,
29339
- range
29374
+ coords: lines
29340
29375
  } = props.graphState;
29341
-
29342
- // a ray only has one line
29343
- const [start, end] = lines[0];
29344
29376
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29345
- const handleMovePoint = (newPoint, pointIndex) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29346
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
29347
- start: start,
29348
- end: end,
29349
- onMove: handleMoveLine,
29377
+ const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29378
+ return /*#__PURE__*/React.createElement(Segment
29379
+ // a ray only has one line
29380
+ , {
29381
+ points: lines[0],
29382
+ onMoveLine: handleMoveLine,
29383
+ onMovePoint: handleMovePoint,
29350
29384
  extend: {
29351
- end: true,
29352
29385
  start: false,
29353
- range
29386
+ end: true
29354
29387
  }
29355
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29356
- point: start,
29357
- onMove: newPoint => handleMovePoint(newPoint, 0)
29358
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29359
- point: end,
29360
- onMove: newPoint => handleMovePoint(newPoint, 1)
29361
- }));
29388
+ });
29362
29389
  };
29363
29390
 
29364
29391
  /**
@@ -29522,8 +29549,37 @@ const linesIntersect = ([[a, b], [c, d]], [[p, q], [r, s]]) => {
29522
29549
  };
29523
29550
  const isEven = n => n % 2 === 0;
29524
29551
 
29552
+ const StyledMovablePoint = props => {
29553
+ const {
29554
+ snapStep
29555
+ } = useGraphConfig();
29556
+ const elementRef = useRef(null);
29557
+ const {
29558
+ point,
29559
+ onMove,
29560
+ color: color$1 = color.blue
29561
+ } = props;
29562
+ const {
29563
+ dragging
29564
+ } = useMovable({
29565
+ gestureTarget: elementRef,
29566
+ point,
29567
+ onMove,
29568
+ constrain: p => snap(snapStep, p)
29569
+ });
29570
+ return /*#__PURE__*/React.createElement(MovablePointView, {
29571
+ ref: elementRef,
29572
+ point: point,
29573
+ color: color$1,
29574
+ dragging: dragging,
29575
+ focusBehavior: {
29576
+ type: "uncontrolled",
29577
+ tabIndex: 0
29578
+ }
29579
+ });
29580
+ };
29581
+
29525
29582
  const PolygonGraph = props => {
29526
- var _points$length;
29527
29583
  const [focused, setFocused] = React.useState(false);
29528
29584
  const [hovered, setHovered] = React.useState(false);
29529
29585
  const {
@@ -29536,17 +29592,19 @@ const PolygonGraph = props => {
29536
29592
  range,
29537
29593
  snapStep
29538
29594
  } = props.graphState;
29595
+
29596
+ // TODO(benchristel): can the default set of points be removed here? I don't
29597
+ // think coords can be null.
29539
29598
  const points = coords != null ? coords : [[0, 0]];
29540
- const pointsSum = points.reduce((acc, point) => vec.add(acc, point), [0, 0]);
29541
- const midpoint = pointsSum && vec.scale(pointsSum, 1 / ((_points$length = points.length) != null ? _points$length : 1));
29542
29599
  const ref = React.useRef(null);
29600
+ const dragReferencePoint = points[0];
29543
29601
  const {
29544
29602
  dragging
29545
29603
  } = useMovable({
29546
29604
  gestureTarget: ref,
29547
- point: midpoint,
29605
+ point: dragReferencePoint,
29548
29606
  onMove: newPoint => {
29549
- const delta = vec.sub(newPoint, midpoint);
29607
+ const delta = vec.sub(newPoint, dragReferencePoint);
29550
29608
  dispatch(moveAll(delta));
29551
29609
  },
29552
29610
  constrain: p => snap(snapStep, p)
@@ -43231,7 +43289,7 @@ const init = function init(options) {
43231
43289
 
43232
43290
  // This file is processed by a Rollup plugin (replace) to inject the production
43233
43291
  const libName = "@khanacademy/perseus";
43234
- const libVersion = "22.4.0";
43292
+ const libVersion = "22.4.2";
43235
43293
  addLibraryVersionToPerseusDebug(libName, libVersion);
43236
43294
 
43237
43295
  const apiVersion = {