@khanacademy/perseus 48.1.0 → 49.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.
package/dist/es/index.js CHANGED
@@ -44,13 +44,21 @@ import cornersOutIcon from '@phosphor-icons/core/regular/corners-out.svg';
44
44
  import Link from '@khanacademy/wonder-blocks-link';
45
45
 
46
46
  function _extends() {
47
- return _extends = Object.assign ? Object.assign.bind() : function (n) {
48
- for (var e = 1; e < arguments.length; e++) {
49
- var t = arguments[e];
50
- for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
47
+ _extends = Object.assign || function (target) {
48
+ for (var i = 1; i < arguments.length; i++) {
49
+ var source = arguments[i];
50
+
51
+ for (var key in source) {
52
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
53
+ target[key] = source[key];
54
+ }
55
+ }
51
56
  }
52
- return n;
53
- }, _extends.apply(null, arguments);
57
+
58
+ return target;
59
+ };
60
+
61
+ return _extends.apply(this, arguments);
54
62
  }
55
63
 
56
64
  /**
@@ -239,6 +247,8 @@ const mockStrings = {
239
247
  addPoint: "Add Point",
240
248
  removePoint: "Remove Point",
241
249
  graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
250
+ selectAnAnswer: "Select an answer",
251
+ // The following strings are used for interactive graph SR descriptions.
242
252
  closePolygon: "Close shape",
243
253
  openPolygon: "Re-open shape",
244
254
  srPointAtCoordinates: ({
@@ -250,7 +260,29 @@ const mockStrings = {
250
260
  elements
251
261
  }) => `Interactive elements: ${elements}`,
252
262
  srNoInteractiveElements: "No interactive elements",
253
- selectAnAnswer: "Select an answer"
263
+ circleGraphAriaLabel: "A circle on a coordinate plane.",
264
+ circleShapeAriaLabel: ({
265
+ centerX,
266
+ centerY
267
+ }) => `Circle. The center point is at ${centerX} comma ${centerY}.`,
268
+ circleRadiusPointAriaLabel: ({
269
+ radiusPointX,
270
+ radiusPointY
271
+ }) => `Radius point at ${radiusPointX} comma ${radiusPointY}.`,
272
+ circleRadiusDescription: ({
273
+ radius
274
+ }) => `Circle radius is ${radius}.`,
275
+ circleOuterPointsDescription: ({
276
+ point1X,
277
+ point1Y,
278
+ point2X,
279
+ point2Y,
280
+ point3X,
281
+ point3Y,
282
+ point4X,
283
+ point4Y
284
+ }) => `Points on the circle at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, ${point3X} comma ${point3Y}, ${point4X} comma ${point4Y}.`
285
+ // The above strings are used for interactive graph SR descriptions.
254
286
  };
255
287
 
256
288
  /**
@@ -3520,9 +3552,9 @@ function loadGraphie(url, onDataLoaded) {
3520
3552
  };
3521
3553
  if (shouldUseLocalizedData()) {
3522
3554
  cacheData.localized = false;
3523
- // If there is isn't any localized data, fall back to
3524
- // the original, unlocalized data
3525
- retrieveData(getLocalizedDataUrl(url), dataLoadErrorHandler);
3555
+ // If there is isn't any localized data, use the error callback to try to
3556
+ // load the non-localized data. If that fails too, then we will throw an error.
3557
+ retrieveData(getLocalizedDataUrl(url), () => retrieveData(Util.getDataUrl(url), dataLoadErrorHandler));
3526
3558
  } else {
3527
3559
  retrieveData(Util.getDataUrl(url), dataLoadErrorHandler);
3528
3560
  }
@@ -4421,14 +4453,17 @@ const InteractiveUtil = {
4421
4453
  }
4422
4454
  };
4423
4455
 
4424
- function _classPrivateFieldBase(e, t) {
4425
- if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance");
4426
- return e;
4456
+ function _classPrivateFieldBase(receiver, privateKey) {
4457
+ if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
4458
+ throw new TypeError("attempted to use private field on non-instance");
4459
+ }
4460
+
4461
+ return receiver;
4427
4462
  }
4428
4463
 
4429
4464
  var id = 0;
4430
- function _classPrivateFieldKey(e) {
4431
- return "__private_" + id++ + "_" + e;
4465
+ function _classPrivateFieldKey(name) {
4466
+ return "__private_" + id++ + "_" + name;
4432
4467
  }
4433
4468
 
4434
4469
  /*!
@@ -21057,14 +21092,19 @@ TextInput.defaultProps = {
21057
21092
  disabled: false
21058
21093
  };
21059
21094
 
21060
- function _objectWithoutPropertiesLoose(r, e) {
21061
- if (null == r) return {};
21062
- var t = {};
21063
- for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
21064
- if (e.includes(n)) continue;
21065
- t[n] = r[n];
21095
+ function _objectWithoutPropertiesLoose(source, excluded) {
21096
+ if (source == null) return {};
21097
+ var target = {};
21098
+ var sourceKeys = Object.keys(source);
21099
+ var key, i;
21100
+
21101
+ for (i = 0; i < sourceKeys.length; i++) {
21102
+ key = sourceKeys[i];
21103
+ if (excluded.indexOf(key) >= 0) continue;
21104
+ target[key] = source[key];
21066
21105
  }
21067
- return t;
21106
+
21107
+ return target;
21068
21108
  }
21069
21109
 
21070
21110
  const _excluded$7 = ["position", "visibility", "left", "top"];
@@ -27345,36 +27385,41 @@ class Dropdown extends React.Component {
27345
27385
  value: String(i + 1),
27346
27386
  label: choice
27347
27387
  }))];
27348
- return /*#__PURE__*/React.createElement(UniqueIDProvider, {
27349
- scope: "dropdown-widget",
27350
- mockOnFirstRender: true
27351
- }, ids => /*#__PURE__*/React.createElement(View
27352
- // NOTE(jared): These are required to prevent weird behavior
27353
- // When there's a dropdown in a zoomable table.
27354
- , {
27355
- onClick: e => {
27356
- e.stopPropagation();
27357
- },
27358
- onTouchStart: e => {
27359
- e.stopPropagation();
27360
- }
27361
- }, this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelLarge, {
27362
- tag: "label",
27363
- htmlFor: ids.get("dropdown")
27364
- }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27365
- id: ids.get("dropdown"),
27366
- placeholder: "",
27367
- onChange: value => this._handleChange(parseInt(value)),
27368
- selectedValue: String(this.props.selected),
27369
- disabled: this.props.apiOptions.readOnly,
27370
- "aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
27371
- // This is currently necessary for SRs to read the labels properly.
27372
- // However, WB is working on a change to add the "combobox" role to
27373
- // all dropdowns.
27374
- // See https://khanacademy.atlassian.net/browse/WB-1671
27375
- ,
27376
- role: "combobox"
27377
- }, children)));
27388
+ return (
27389
+ /*#__PURE__*/
27390
+ // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
27391
+ // eslint-disable-next-line no-restricted-syntax
27392
+ React.createElement(UniqueIDProvider, {
27393
+ scope: "dropdown-widget",
27394
+ mockOnFirstRender: true
27395
+ }, ids => /*#__PURE__*/React.createElement(View
27396
+ // NOTE(jared): These are required to prevent weird behavior
27397
+ // When there's a dropdown in a zoomable table.
27398
+ , {
27399
+ onClick: e => {
27400
+ e.stopPropagation();
27401
+ },
27402
+ onTouchStart: e => {
27403
+ e.stopPropagation();
27404
+ }
27405
+ }, this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelLarge, {
27406
+ tag: "label",
27407
+ htmlFor: ids.get("dropdown")
27408
+ }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27409
+ id: ids.get("dropdown"),
27410
+ placeholder: "",
27411
+ onChange: value => this._handleChange(parseInt(value)),
27412
+ selectedValue: String(this.props.selected),
27413
+ disabled: this.props.apiOptions.readOnly,
27414
+ "aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
27415
+ // This is currently necessary for SRs to read the labels properly.
27416
+ // However, WB is working on a change to add the "combobox" role to
27417
+ // all dropdowns.
27418
+ // See https://khanacademy.atlassian.net/browse/WB-1671
27419
+ ,
27420
+ role: "combobox"
27421
+ }, children)))
27422
+ );
27378
27423
  }
27379
27424
  }
27380
27425
  Dropdown.contextType = PerseusI18nContext;
@@ -27477,32 +27522,37 @@ class Explanation extends React.Component {
27477
27522
  "white-space": "normal"
27478
27523
  };
27479
27524
  const contentStyling = [styles$k.content, this.state.expanded ? styles$k.contentExpanded : styles$k.contentCollapsed, allowTransition && (this.state.expanded ? styles$k.transitionExpanded : styles$k.transitionCollapsed)];
27480
- return /*#__PURE__*/React.createElement(UniqueIDProvider, {
27481
- mockOnFirstRender: true,
27482
- scope: "explanation-widget"
27483
- }, ids => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
27484
- "aria-expanded": this.state.expanded,
27485
- "aria-controls": ids.get("content"),
27486
- endIcon: caretIcon,
27487
- kind: "tertiary",
27488
- labelStyle: labelStyle,
27489
- onClick: this._onClick,
27490
- size: "small",
27491
- style: buttonStyleOverrides
27492
- }, promptText), /*#__PURE__*/React.createElement(View, {
27493
- id: ids.get("content"),
27494
- style: contentStyling,
27495
- "aria-hidden": !this.state.expanded,
27496
- testId: "content-container"
27497
- }, /*#__PURE__*/React.createElement(View, {
27498
- style: styles$k.contentWrapper
27499
- }, /*#__PURE__*/React.createElement(Renderer, {
27500
- apiOptions: this.props.apiOptions,
27501
- content: this.props.explanation,
27502
- widgets: this.props.widgets,
27503
- linterContext: this.props.linterContext,
27504
- strings: this.context.strings
27505
- })))));
27525
+ return (
27526
+ /*#__PURE__*/
27527
+ // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
27528
+ // eslint-disable-next-line no-restricted-syntax
27529
+ React.createElement(UniqueIDProvider, {
27530
+ mockOnFirstRender: true,
27531
+ scope: "explanation-widget"
27532
+ }, ids => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
27533
+ "aria-expanded": this.state.expanded,
27534
+ "aria-controls": ids.get("content"),
27535
+ endIcon: caretIcon,
27536
+ kind: "tertiary",
27537
+ labelStyle: labelStyle,
27538
+ onClick: this._onClick,
27539
+ size: "small",
27540
+ style: buttonStyleOverrides
27541
+ }, promptText), /*#__PURE__*/React.createElement(View, {
27542
+ id: ids.get("content"),
27543
+ style: contentStyling,
27544
+ "aria-hidden": !this.state.expanded,
27545
+ testId: "content-container"
27546
+ }, /*#__PURE__*/React.createElement(View, {
27547
+ style: styles$k.contentWrapper
27548
+ }, /*#__PURE__*/React.createElement(Renderer, {
27549
+ apiOptions: this.props.apiOptions,
27550
+ content: this.props.explanation,
27551
+ widgets: this.props.widgets,
27552
+ linterContext: this.props.linterContext,
27553
+ strings: this.context.strings
27554
+ })))))
27555
+ );
27506
27556
  }
27507
27557
  }
27508
27558
  Explanation.contextType = PerseusI18nContext;
@@ -34145,21 +34195,96 @@ function CircleGraph(props) {
34145
34195
  center,
34146
34196
  radiusPoint
34147
34197
  } = graphState;
34148
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableCircle, {
34198
+ const {
34199
+ strings
34200
+ } = usePerseusI18n();
34201
+ const [radiusPointAriaLive, setRadiusPointAriaLive] = React.useState("off");
34202
+ const radius = getRadius(graphState);
34203
+ const id = React.useId();
34204
+ const circleId = id + "-circle";
34205
+ const radiusId = id + "-radius";
34206
+ const outerPointsId = id + "-outer-points";
34207
+
34208
+ // Aria label strings
34209
+ const circleGraphAriaLabel = strings.circleGraphAriaLabel;
34210
+ const circleShapeAriaLabel = strings.circleShapeAriaLabel({
34211
+ centerX: center[0],
34212
+ centerY: center[1]
34213
+ });
34214
+ const circleRadiusPointAriaLabel = strings.circleRadiusPointAriaLabel({
34215
+ radiusPointX: radiusPoint[0],
34216
+ radiusPointY: radiusPoint[1]
34217
+ });
34218
+ const circleRadiusDescription = strings.circleRadiusDescription({
34219
+ radius
34220
+ });
34221
+ const circleOuterPointsDescription = strings.circleOuterPointsDescription({
34222
+ point1X: center[0] + radius,
34223
+ point1Y: center[1],
34224
+ point2X: center[0],
34225
+ point2Y: center[1] + radius,
34226
+ point3X: center[0] - radius,
34227
+ point3Y: center[1],
34228
+ point4X: center[0],
34229
+ point4Y: center[1] - radius
34230
+ });
34231
+ return /*#__PURE__*/React.createElement("g", {
34232
+ // Outer circle minimal description
34233
+ "aria-label": circleGraphAriaLabel,
34234
+ "aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
34235
+ }, /*#__PURE__*/React.createElement(MovableCircle, {
34236
+ id: circleId
34237
+ // Focusable circle aria label reads with every update
34238
+ // because of the aria-live property in the circle <g>.
34239
+ ,
34240
+ ariaLabel: circleShapeAriaLabel
34241
+ // Aria-describedby describes additional info on focus.
34242
+ ,
34243
+ ariaDescribedBy: `${radiusId} ${outerPointsId}`,
34149
34244
  center: center,
34150
- radius: getRadius(graphState),
34151
- onMove: c => dispatch(actions.circle.moveCenter(c))
34152
- }), /*#__PURE__*/React.createElement(MovablePoint$1, {
34245
+ radius: radius,
34246
+ onMove: c => {
34247
+ setRadiusPointAriaLive("off");
34248
+ dispatch(actions.circle.moveCenter(c));
34249
+ }
34250
+ }), /*#__PURE__*/React.createElement(MovablePoint$1
34251
+ // Radius point aria label reads with every update.
34252
+ , {
34253
+ ariaLabel: `${circleRadiusPointAriaLabel} ${circleRadiusDescription}`
34254
+ // Aria-describedby describes additional info on focus.
34255
+ ,
34256
+ ariaDescribedBy: `${outerPointsId}`
34257
+ // The radius point's aria-live property is set to "off" when
34258
+ // the circle is moved, so that it doesn't override the circle's
34259
+ // aria-live (since the point is moved along with the circle).
34260
+ // When the radius point is moved, the aria-live is set to
34261
+ // "polite" so that the radius is read out.
34262
+ ,
34263
+ ariaLive: radiusPointAriaLive,
34153
34264
  point: radiusPoint,
34154
34265
  sequenceNumber: 1,
34155
34266
  cursor: "ew-resize",
34156
34267
  onMove: newRadiusPoint => {
34268
+ setRadiusPointAriaLive("polite");
34157
34269
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
34158
34270
  }
34159
- }));
34271
+ }), /*#__PURE__*/React.createElement("g", {
34272
+ id: radiusId,
34273
+ style: {
34274
+ display: "hidden"
34275
+ }
34276
+ }, circleRadiusDescription), /*#__PURE__*/React.createElement("g", {
34277
+ id: outerPointsId,
34278
+ style: {
34279
+ display: "hidden"
34280
+ }
34281
+ }, circleOuterPointsDescription));
34160
34282
  }
34161
34283
  function MovableCircle(props) {
34162
34284
  const {
34285
+ id,
34286
+ ariaLabel,
34287
+ ariaDescribedBy,
34163
34288
  center,
34164
34289
  radius,
34165
34290
  onMove
@@ -34180,7 +34305,11 @@ function MovableCircle(props) {
34180
34305
  const [centerPx] = useTransformVectorsToPixels(center);
34181
34306
  const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
34182
34307
  return /*#__PURE__*/React.createElement("g", {
34308
+ "aria-label": ariaLabel,
34309
+ "aria-describedby": ariaDescribedBy,
34310
+ "aria-live": "polite",
34183
34311
  ref: draggableRef,
34312
+ role: "button",
34184
34313
  tabIndex: disableKeyboardInteraction ? -1 : 0,
34185
34314
  className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
34186
34315
  }, /*#__PURE__*/React.createElement("ellipse", {
@@ -34190,6 +34319,7 @@ function MovableCircle(props) {
34190
34319
  rx: radiiPx[X] + 3,
34191
34320
  ry: radiiPx[Y] + 3
34192
34321
  }), /*#__PURE__*/React.createElement("ellipse", {
34322
+ id: id,
34193
34323
  className: "circle",
34194
34324
  cx: centerPx[X],
34195
34325
  cy: centerPx[Y],
@@ -38784,6 +38914,8 @@ const AnswerChoices = props => {
38784
38914
  };
38785
38915
 
38786
38916
  const HideAnswersToggle = props => {
38917
+ // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
38918
+ // eslint-disable-next-line no-restricted-syntax
38787
38919
  const idFactory = useUniqueIdWithMock();
38788
38920
  const switchId = idFactory.get("hide-answers-toggle");
38789
38921
  const labelId = idFactory.get("hide-answers-label");
@@ -38829,6 +38961,9 @@ const AnswerPill = props => {
38829
38961
  focused,
38830
38962
  hovered
38831
38963
  } = props;
38964
+
38965
+ // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
38966
+ // eslint-disable-next-line no-restricted-syntax
38832
38967
  const idFactory = useUniqueIdWithMock();
38833
38968
  const {
38834
38969
  strings
@@ -45172,7 +45307,7 @@ const init = function init() {
45172
45307
 
45173
45308
  // This file is processed by a Rollup plugin (replace) to inject the production
45174
45309
  const libName = "@khanacademy/perseus";
45175
- const libVersion = "48.1.0";
45310
+ const libVersion = "49.0.0";
45176
45311
  addLibraryVersionToPerseusDebug(libName, libVersion);
45177
45312
 
45178
45313
  const apiVersion = {