@khanacademy/perseus 48.0.0 → 48.2.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.
@@ -92,10 +92,6 @@ declare class SvgImage extends React.Component<Props, State> {
92
92
  componentWillUnmount(): void;
93
93
  isLoadedInState(state: State): boolean;
94
94
  loadResources(): void;
95
- onDataLoaded: (data: {
96
- labels: ReadonlyArray<any>;
97
- range: [Coord, Coord];
98
- }, localized: boolean) => void;
99
95
  sizeProvided(): boolean;
100
96
  onImageLoad: () => void;
101
97
  setupGraphie: (graphie?: any, options?: any) => void;
package/dist/es/index.js CHANGED
@@ -239,6 +239,8 @@ const mockStrings = {
239
239
  addPoint: "Add Point",
240
240
  removePoint: "Remove Point",
241
241
  graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
242
+ selectAnAnswer: "Select an answer",
243
+ // The following strings are used for interactive graph SR descriptions.
242
244
  closePolygon: "Close shape",
243
245
  openPolygon: "Re-open shape",
244
246
  srPointAtCoordinates: ({
@@ -250,7 +252,29 @@ const mockStrings = {
250
252
  elements
251
253
  }) => `Interactive elements: ${elements}`,
252
254
  srNoInteractiveElements: "No interactive elements",
253
- selectAnAnswer: "Select an answer"
255
+ circleGraphAriaLabel: "A circle on a coordinate plane.",
256
+ circleShapeAriaLabel: ({
257
+ centerX,
258
+ centerY
259
+ }) => `Circle. The center point is at ${centerX} comma ${centerY}.`,
260
+ circleRadiusPointAriaLabel: ({
261
+ radiusPointX,
262
+ radiusPointY
263
+ }) => `Radius point at ${radiusPointX} comma ${radiusPointY}.`,
264
+ circleRadiusDescription: ({
265
+ radius
266
+ }) => `Circle radius is ${radius}.`,
267
+ circleOuterPointsDescription: ({
268
+ point1X,
269
+ point1Y,
270
+ point2X,
271
+ point2Y,
272
+ point3X,
273
+ point3Y,
274
+ point4X,
275
+ point4Y
276
+ }) => `Points on the circle at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, ${point3X} comma ${point3Y}, ${point4X} comma ${point4Y}.`
277
+ // The above strings are used for interactive graph SR descriptions.
254
278
  };
255
279
 
256
280
  /**
@@ -3373,6 +3397,162 @@ const Util = {
3373
3397
  deepClone
3374
3398
  };
3375
3399
 
3400
+ // For offline exercises in the mobile app, we download the graphie data
3401
+ // (svgs and localized data files) and serve them from the local file
3402
+ // system (with file://). We replace urls that start with `web+graphie`
3403
+ // in the perseus json with this `file+graphie` prefix to indicate that
3404
+ // they should have the `file://` protocol instead of `https://`.
3405
+ const svgLocalLabelsRegex = /^file\+graphie:/;
3406
+ const hashRegex = /\/([^/]+)$/;
3407
+
3408
+ /**
3409
+ * A Graphie axis range.
3410
+ */
3411
+
3412
+ /**
3413
+ * A Graphie label.
3414
+ */
3415
+
3416
+ /**
3417
+ * The Graphie data (labels, etc.)
3418
+ */
3419
+
3420
+ function getLocale() {
3421
+ const {
3422
+ JIPT,
3423
+ kaLocale
3424
+ } = getDependencies();
3425
+ return JIPT.useJIPT ? "en-pt" : kaLocale;
3426
+ }
3427
+ function shouldUseLocalizedData() {
3428
+ return getLocale() !== "en";
3429
+ }
3430
+
3431
+ // A regex to split at the last / of a URL, separating the base part from the
3432
+ // hash. This is used to create the localized label data URLs.
3433
+ const splitHashRegex = /\/(?=[^/]+$)/;
3434
+ function getLocalizedDataUrl(url) {
3435
+ // For local (cached) graphie images, they are already localized.
3436
+ if (svgLocalLabelsRegex.test(url)) {
3437
+ return Util.getDataUrl(url);
3438
+ }
3439
+ const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
3440
+ return `${base}/${getLocale()}/${hash}-data.json`;
3441
+ }
3442
+
3443
+ // Get the hash from the url, which is just the filename
3444
+ function getUrlHash(url) {
3445
+ const match = url.match(hashRegex);
3446
+ if (match == null) {
3447
+ throw new PerseusError("not a valid URL", Errors.InvalidInput);
3448
+ }
3449
+ return match && match[1];
3450
+ }
3451
+
3452
+ /**
3453
+ * Parse JSONP for a web+graphie://... hash to extract Graphie data, or throw
3454
+ * an error if invalid JSONP.
3455
+ */
3456
+ function parseDataFromJSONP(graphieHash, graphieJSONP, errorCallback) {
3457
+ // The JSONP is expected to be in the form of `svgDataHASH(...)` or `svgOtherDataHASH(...)`
3458
+ const match = graphieJSONP.match(new RegExp(`^(?:svgData|svgOtherData)${graphieHash}\\((.+)\\);$`));
3459
+
3460
+ // It is also possible that the JSONP is simply a JSON object,
3461
+ // in which case we can parse it directly.
3462
+ const jsonToParse = match ? match[1] : graphieJSONP;
3463
+
3464
+ // Try to parse the JSONP, and if it fails, call the error callback
3465
+ try {
3466
+ return JSON.parse(jsonToParse);
3467
+ } catch (error) {
3468
+ errorCallback(error);
3469
+ return null;
3470
+ }
3471
+ }
3472
+ // The global cache of label data. Its format is:
3473
+ // {
3474
+ // hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
3475
+ // loaded: a boolean of whether the data has been loaded or not
3476
+ // dataCallbacks: a list of callbacks to call with the data when the data
3477
+ // is loaded
3478
+ // data: the other data for this hash
3479
+ // },
3480
+ // ...
3481
+ // }
3482
+ const labelDataCache = {};
3483
+ function loadGraphie(url, onDataLoaded) {
3484
+ const hash = getUrlHash(url);
3485
+
3486
+ // We can't make multiple jsonp calls to the same file because their
3487
+ // callbacks will collide with each other. Instead, we cache the data
3488
+ // and only make the jsonp calls once.
3489
+ const entry = labelDataCache[hash];
3490
+ if (entry != null) {
3491
+ if (entry.loaded) {
3492
+ const {
3493
+ data,
3494
+ localized
3495
+ } = entry;
3496
+ onDataLoaded(data, localized);
3497
+ } else {
3498
+ entry.dataCallbacks.push(onDataLoaded);
3499
+ }
3500
+ } else {
3501
+ const cacheData = {
3502
+ loaded: false,
3503
+ dataCallbacks: [onDataLoaded],
3504
+ localized: shouldUseLocalizedData()
3505
+ };
3506
+ labelDataCache[hash] = cacheData;
3507
+ const retrieveData = async (url, errorCallback) => {
3508
+ const response = await fetch(url);
3509
+ if (!(response != null && response.ok)) {
3510
+ errorCallback();
3511
+ return;
3512
+ }
3513
+ const jsonp = await response.text();
3514
+ const data = parseDataFromJSONP(hash, jsonp, error => {
3515
+ Log.error("Failed to parse JSONP for svg-image", Errors.Service, {
3516
+ cause: error,
3517
+ loggedMetadata: {
3518
+ dataUrl: Util.getDataUrl(url),
3519
+ jsonp
3520
+ }
3521
+ });
3522
+ });
3523
+
3524
+ // If the data is null, then we failed to parse the JSONP
3525
+ if (!data) {
3526
+ return;
3527
+ }
3528
+ const newCacheEntry = labelDataCache[hash] = _extends({}, labelDataCache[hash], {
3529
+ loaded: true,
3530
+ data
3531
+ });
3532
+ newCacheEntry.dataCallbacks.forEach(callback => {
3533
+ callback(newCacheEntry.data, cacheData.localized);
3534
+ });
3535
+ };
3536
+ const dataLoadErrorHandler = (x, status, error) => {
3537
+ Log.error("Data load failed for svg-image", Errors.Service, {
3538
+ cause: error,
3539
+ loggedMetadata: {
3540
+ dataUrl: Util.getDataUrl(url),
3541
+ status
3542
+ }
3543
+ });
3544
+ };
3545
+ if (shouldUseLocalizedData()) {
3546
+ cacheData.localized = false;
3547
+ // If there is isn't any localized data, use the error callback to try to
3548
+ // load the non-localized data. If that fails too, then we will throw an error.
3549
+ retrieveData(getLocalizedDataUrl(url), () => retrieveData(Util.getDataUrl(url), dataLoadErrorHandler));
3550
+ } else {
3551
+ retrieveData(Util.getDataUrl(url), dataLoadErrorHandler);
3552
+ }
3553
+ }
3554
+ }
3555
+
3376
3556
  // Derived from the MIT-licensed:
3377
3557
 
3378
3558
  /*jshint browser:true, node:true */
@@ -8959,6 +9139,9 @@ class Graphie$1 {
8959
9139
  $(el).css("position", "relative");
8960
9140
  this.raphael = Raphael$1(el);
8961
9141
 
9142
+ // Hide the Raphael canvas from screen readers
9143
+ $(el).attr("aria-hidden", "true");
9144
+
8962
9145
  // For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
8963
9146
  $(el).children("div").css("position", "absolute");
8964
9147
  }
@@ -16832,101 +17015,11 @@ class ImageLoader extends React.Component {
16832
17015
 
16833
17016
  // Minimum image width to make an image appear as zoomable.
16834
17017
  const ZOOMABLE_THRESHOLD = 700;
16835
-
16836
- // The global cache of label data. Its format is:
16837
- // {
16838
- // hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
16839
- // loaded: a boolean of whether the data has been loaded or not
16840
- // dataCallbacks: a list of callbacks to call with the data when the data
16841
- // is loaded
16842
- // data: the other data for this hash
16843
- // },
16844
- // ...
16845
- // }
16846
- const labelDataCache = {};
16847
-
16848
- // Write our own JSONP handler because all the other ones don't do things we
16849
- // need.
16850
- const doJSONP = function doJSONP(url, options) {
16851
- var _document$head2;
16852
- options = _extends({
16853
- callbackName: "callback",
16854
- success: $.noop,
16855
- error: $.noop
16856
- }, options);
16857
-
16858
- // Create the script
16859
- const script = document.createElement("script");
16860
- script.setAttribute("async", "");
16861
- script.setAttribute("src", url);
16862
-
16863
- // A cleanup function to run when we're done.
16864
- function cleanup() {
16865
- var _document$head;
16866
- (_document$head = document.head) == null || _document$head.removeChild(script);
16867
- delete window[options.callbackName];
16868
- }
16869
-
16870
- // Add the global callback.
16871
- // @ts-expect-error - TS2740 - Type '() => void' is missing the following properties from type 'Window': clientInformation, closed, customElements, devicePixelRatio, and 206 more.
16872
- window[options.callbackName] = function (...args) {
16873
- cleanup();
16874
- options.success.apply(null, args);
16875
- };
16876
-
16877
- // Add the error handler.
16878
- script.addEventListener("error", function (...args) {
16879
- cleanup();
16880
- options.error.apply(null, args);
16881
- });
16882
-
16883
- // Insert the script to start the download.
16884
- (_document$head2 = document.head) == null || _document$head2.appendChild(script);
16885
- };
16886
-
16887
- // For offline exercises in the mobile app, we download the graphie data
16888
- // (svgs and localized data files) and serve them from the local file
16889
- // system (with file://). We replace urls that start with `web+graphie`
16890
- // in the perseus json with this `file+graphie` prefix to indicate that
16891
- // they should have the `file://` protocol instead of `https://`.
16892
- const svgLocalLabelsRegex = /^file\+graphie:/;
16893
- const hashRegex = /\/([^/]+)$/;
16894
17018
  function isImageProbablyPhotograph(imageUrl) {
16895
17019
  // TODO(david): Do an inventory to refine this heuristic. For example, what
16896
17020
  // % of .png images are illustrations?
16897
17021
  return /\.(jpg|jpeg)$/i.test(imageUrl);
16898
17022
  }
16899
- function getLocale() {
16900
- const {
16901
- JIPT,
16902
- kaLocale
16903
- } = getDependencies();
16904
- return JIPT.useJIPT ? "en-pt" : kaLocale;
16905
- }
16906
- function shouldUseLocalizedData() {
16907
- return getLocale() !== "en";
16908
- }
16909
-
16910
- // A regex to split at the last / of a URL, separating the base part from the
16911
- // hash. This is used to create the localized label data URLs.
16912
- const splitHashRegex = /\/(?=[^/]+$)/;
16913
- function getLocalizedDataUrl(url) {
16914
- // For local (cached) graphie images, they are already localized.
16915
- if (svgLocalLabelsRegex.test(url)) {
16916
- return Util.getDataUrl(url);
16917
- }
16918
- const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
16919
- return `${base}/${getLocale()}/${hash}-data.json`;
16920
- }
16921
-
16922
- // Get the hash from the url, which is just the filename
16923
- function getUrlHash(url) {
16924
- const match = url.match(hashRegex);
16925
- if (match == null) {
16926
- throw new PerseusError("not a valid URL", Errors.InvalidInput);
16927
- }
16928
- return match && match[1];
16929
- }
16930
17023
  function defaultPreloader(dimensions) {
16931
17024
  return /*#__PURE__*/React.createElement("span", {
16932
17025
  style: {
@@ -16948,20 +17041,6 @@ class SvgImage extends React.Component {
16948
17041
  constructor(props) {
16949
17042
  super(props);
16950
17043
  this._isMounted = void 0;
16951
- this.onDataLoaded = (data, localized) => {
16952
- if (this._isMounted && data.labels && data.range) {
16953
- const labelsRendered = data.labels.reduce((dict, label) => _extends({}, dict, {
16954
- [label.content]: false
16955
- }), {});
16956
- this.setState({
16957
- dataLoaded: true,
16958
- labelDataIsLocalized: localized,
16959
- labelsRendered,
16960
- labels: data.labels,
16961
- range: data.range
16962
- });
16963
- }
16964
- };
16965
17044
  this.onImageLoad = () => {
16966
17045
  // Only need to do this if rendering a Graphie
16967
17046
  if (this.sizeProvided()) {
@@ -17137,74 +17216,21 @@ class SvgImage extends React.Component {
17137
17216
  return Util.isLabeledSVG(this.props.src) ? state.imageLoaded && state.dataLoaded : state.imageLoaded;
17138
17217
  }
17139
17218
  loadResources() {
17140
- const hash = getUrlHash(this.props.src);
17141
-
17142
- // We can't make multiple jsonp calls to the same file because their
17143
- // callbacks will collide with each other. Instead, we cache the data
17144
- // and only make the jsonp calls once.
17145
- if (labelDataCache[hash]) {
17146
- if (labelDataCache[hash].loaded) {
17147
- const {
17148
- data,
17149
- localized
17150
- } = labelDataCache[hash];
17151
- this.onDataLoaded(data, localized);
17152
- } else {
17153
- labelDataCache[hash].dataCallbacks.push(this.onDataLoaded);
17154
- }
17155
- } else {
17156
- const cacheData = {
17157
- loaded: false,
17158
- dataCallbacks: [this.onDataLoaded],
17159
- data: null,
17160
- localized: shouldUseLocalizedData()
17161
- };
17162
- labelDataCache[hash] = cacheData;
17163
- const retrieveData = (url, errorCallback) => {
17164
- doJSONP(url, {
17165
- callbackName: "svgData" + hash,
17166
- success: data => {
17167
- // @ts-expect-error - TS2540 - Cannot assign to 'data' because it is a read-only property.
17168
- cacheData.data = data;
17169
- // @ts-expect-error - TS2540 - Cannot assign to 'loaded' because it is a read-only property.
17170
- cacheData.loaded = true;
17171
- _.each(cacheData.dataCallbacks, callback => {
17172
- // @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '{ labels: readonly any[]; range: readonly any[]; }'.
17173
- callback(cacheData.data, cacheData.localized);
17174
- });
17175
- },
17176
- error: errorCallback
17177
- });
17178
- };
17179
- if (shouldUseLocalizedData()) {
17180
- retrieveData(getLocalizedDataUrl(this.props.src), (x, status, error) => {
17181
- // @ts-expect-error - TS2540 - Cannot assign to 'localized' because it is a read-only property.
17182
- cacheData.localized = false;
17183
-
17184
- // If there is isn't any localized data, fall back to
17185
- // the original, unlocalized data
17186
- retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
17187
- Log.error("Data load failed for svg-image", Errors.Service, {
17188
- cause: error,
17189
- loggedMetadata: {
17190
- dataUrl: Util.getDataUrl(this.props.src),
17191
- status
17192
- }
17193
- });
17194
- });
17219
+ loadGraphie(this.props.src, (data, localized) => {
17220
+ if (this._isMounted && data.labels && data.range) {
17221
+ const labelsRendered = {};
17222
+ data.labels.forEach(label => {
17223
+ labelsRendered[label.content] = false;
17195
17224
  });
17196
- } else {
17197
- retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
17198
- Log.error("Data load failed for svg-image", Errors.Service, {
17199
- cause: error,
17200
- loggedMetadata: {
17201
- dataUrl: Util.getDataUrl(this.props.src),
17202
- status
17203
- }
17204
- });
17225
+ this.setState({
17226
+ dataLoaded: true,
17227
+ labelDataIsLocalized: localized,
17228
+ labelsRendered,
17229
+ labels: data.labels,
17230
+ range: data.range
17205
17231
  });
17206
17232
  }
17207
- }
17233
+ });
17208
17234
  }
17209
17235
  sizeProvided() {
17210
17236
  return this.props.width != null && this.props.height != null;
@@ -34143,21 +34169,96 @@ function CircleGraph(props) {
34143
34169
  center,
34144
34170
  radiusPoint
34145
34171
  } = graphState;
34146
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableCircle, {
34172
+ const {
34173
+ strings
34174
+ } = usePerseusI18n();
34175
+ const [radiusPointAriaLive, setRadiusPointAriaLive] = React.useState("off");
34176
+ const radius = getRadius(graphState);
34177
+ const id = React.useId();
34178
+ const circleId = id + "-circle";
34179
+ const radiusId = id + "-radius";
34180
+ const outerPointsId = id + "-outer-points";
34181
+
34182
+ // Aria label strings
34183
+ const circleGraphAriaLabel = strings.circleGraphAriaLabel;
34184
+ const circleShapeAriaLabel = strings.circleShapeAriaLabel({
34185
+ centerX: center[0],
34186
+ centerY: center[1]
34187
+ });
34188
+ const circleRadiusPointAriaLabel = strings.circleRadiusPointAriaLabel({
34189
+ radiusPointX: radiusPoint[0],
34190
+ radiusPointY: radiusPoint[1]
34191
+ });
34192
+ const circleRadiusDescription = strings.circleRadiusDescription({
34193
+ radius
34194
+ });
34195
+ const circleOuterPointsDescription = strings.circleOuterPointsDescription({
34196
+ point1X: center[0] + radius,
34197
+ point1Y: center[1],
34198
+ point2X: center[0],
34199
+ point2Y: center[1] + radius,
34200
+ point3X: center[0] - radius,
34201
+ point3Y: center[1],
34202
+ point4X: center[0],
34203
+ point4Y: center[1] - radius
34204
+ });
34205
+ return /*#__PURE__*/React.createElement("g", {
34206
+ // Outer circle minimal description
34207
+ "aria-label": circleGraphAriaLabel,
34208
+ "aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
34209
+ }, /*#__PURE__*/React.createElement(MovableCircle, {
34210
+ id: circleId
34211
+ // Focusable circle aria label reads with every update
34212
+ // because of the aria-live property in the circle <g>.
34213
+ ,
34214
+ ariaLabel: circleShapeAriaLabel
34215
+ // Aria-describedby describes additional info on focus.
34216
+ ,
34217
+ ariaDescribedBy: `${radiusId} ${outerPointsId}`,
34147
34218
  center: center,
34148
- radius: getRadius(graphState),
34149
- onMove: c => dispatch(actions.circle.moveCenter(c))
34150
- }), /*#__PURE__*/React.createElement(MovablePoint$1, {
34219
+ radius: radius,
34220
+ onMove: c => {
34221
+ setRadiusPointAriaLive("off");
34222
+ dispatch(actions.circle.moveCenter(c));
34223
+ }
34224
+ }), /*#__PURE__*/React.createElement(MovablePoint$1
34225
+ // Radius point aria label reads with every update.
34226
+ , {
34227
+ ariaLabel: `${circleRadiusPointAriaLabel} ${circleRadiusDescription}`
34228
+ // Aria-describedby describes additional info on focus.
34229
+ ,
34230
+ ariaDescribedBy: `${outerPointsId}`
34231
+ // The radius point's aria-live property is set to "off" when
34232
+ // the circle is moved, so that it doesn't override the circle's
34233
+ // aria-live (since the point is moved along with the circle).
34234
+ // When the radius point is moved, the aria-live is set to
34235
+ // "polite" so that the radius is read out.
34236
+ ,
34237
+ ariaLive: radiusPointAriaLive,
34151
34238
  point: radiusPoint,
34152
34239
  sequenceNumber: 1,
34153
34240
  cursor: "ew-resize",
34154
34241
  onMove: newRadiusPoint => {
34242
+ setRadiusPointAriaLive("polite");
34155
34243
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
34156
34244
  }
34157
- }));
34245
+ }), /*#__PURE__*/React.createElement("g", {
34246
+ id: radiusId,
34247
+ style: {
34248
+ display: "hidden"
34249
+ }
34250
+ }, circleRadiusDescription), /*#__PURE__*/React.createElement("g", {
34251
+ id: outerPointsId,
34252
+ style: {
34253
+ display: "hidden"
34254
+ }
34255
+ }, circleOuterPointsDescription));
34158
34256
  }
34159
34257
  function MovableCircle(props) {
34160
34258
  const {
34259
+ id,
34260
+ ariaLabel,
34261
+ ariaDescribedBy,
34161
34262
  center,
34162
34263
  radius,
34163
34264
  onMove
@@ -34178,7 +34279,11 @@ function MovableCircle(props) {
34178
34279
  const [centerPx] = useTransformVectorsToPixels(center);
34179
34280
  const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
34180
34281
  return /*#__PURE__*/React.createElement("g", {
34282
+ "aria-label": ariaLabel,
34283
+ "aria-describedby": ariaDescribedBy,
34284
+ "aria-live": "polite",
34181
34285
  ref: draggableRef,
34286
+ role: "button",
34182
34287
  tabIndex: disableKeyboardInteraction ? -1 : 0,
34183
34288
  className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
34184
34289
  }, /*#__PURE__*/React.createElement("ellipse", {
@@ -34188,6 +34293,7 @@ function MovableCircle(props) {
34188
34293
  rx: radiiPx[X] + 3,
34189
34294
  ry: radiiPx[Y] + 3
34190
34295
  }), /*#__PURE__*/React.createElement("ellipse", {
34296
+ id: id,
34191
34297
  className: "circle",
34192
34298
  cx: centerPx[X],
34193
34299
  cy: centerPx[Y],
@@ -45170,7 +45276,7 @@ const init = function init() {
45170
45276
 
45171
45277
  // This file is processed by a Rollup plugin (replace) to inject the production
45172
45278
  const libName = "@khanacademy/perseus";
45173
- const libVersion = "48.0.0";
45279
+ const libVersion = "48.2.0";
45174
45280
  addLibraryVersionToPerseusDebug(libName, libVersion);
45175
45281
 
45176
45282
  const apiVersion = {
@@ -48722,5 +48828,5 @@ const ScoringUtil = {
48722
48828
  * Types
48723
48829
  */
48724
48830
 
48725
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48831
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48726
48832
  //# sourceMappingURL=index.js.map