@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.
- package/dist/components/svg-image.d.ts +0 -4
- package/dist/es/index.js +283 -177
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +47 -2
- package/dist/es/strings.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +296 -182
- package/dist/index.js.map +1 -1
- package/dist/strings.d.ts +23 -1
- package/dist/strings.js +59 -2
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/util/graphie-utils.d.ts +35 -0
- package/dist/widgets/categorizer/validate-categorizer.d.ts +2 -4
- package/dist/widgets/dropdown/validate-dropdown.d.ts +2 -4
- package/dist/widgets/expression/validate-expression.d.ts +2 -4
- package/dist/widgets/interactive-graphs/utils.d.ts +2 -0
- package/dist/widgets/matrix/validate-matrix.d.ts +2 -4
- package/dist/widgets/orderer/validate-orderer.d.ts +2 -4
- package/dist/widgets/plotter/validate-plotter.d.ts +2 -4
- package/dist/widgets/radio/validate-radio.d.ts +2 -4
- package/dist/widgets/sorter/validate-sorter.d.ts +2 -4
- package/dist/widgets/table/validate-table.d.ts +2 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -368,6 +368,8 @@ const mockStrings = {
|
|
|
368
368
|
addPoint: "Add Point",
|
|
369
369
|
removePoint: "Remove Point",
|
|
370
370
|
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
371
|
+
selectAnAnswer: "Select an answer",
|
|
372
|
+
// The following strings are used for interactive graph SR descriptions.
|
|
371
373
|
closePolygon: "Close shape",
|
|
372
374
|
openPolygon: "Re-open shape",
|
|
373
375
|
srPointAtCoordinates: _ref27 => {
|
|
@@ -385,7 +387,41 @@ const mockStrings = {
|
|
|
385
387
|
return `Interactive elements: ${elements}`;
|
|
386
388
|
},
|
|
387
389
|
srNoInteractiveElements: "No interactive elements",
|
|
388
|
-
|
|
390
|
+
circleGraphAriaLabel: "A circle on a coordinate plane.",
|
|
391
|
+
circleShapeAriaLabel: _ref29 => {
|
|
392
|
+
let {
|
|
393
|
+
centerX,
|
|
394
|
+
centerY
|
|
395
|
+
} = _ref29;
|
|
396
|
+
return `Circle. The center point is at ${centerX} comma ${centerY}.`;
|
|
397
|
+
},
|
|
398
|
+
circleRadiusPointAriaLabel: _ref30 => {
|
|
399
|
+
let {
|
|
400
|
+
radiusPointX,
|
|
401
|
+
radiusPointY
|
|
402
|
+
} = _ref30;
|
|
403
|
+
return `Radius point at ${radiusPointX} comma ${radiusPointY}.`;
|
|
404
|
+
},
|
|
405
|
+
circleRadiusDescription: _ref31 => {
|
|
406
|
+
let {
|
|
407
|
+
radius
|
|
408
|
+
} = _ref31;
|
|
409
|
+
return `Circle radius is ${radius}.`;
|
|
410
|
+
},
|
|
411
|
+
circleOuterPointsDescription: _ref32 => {
|
|
412
|
+
let {
|
|
413
|
+
point1X,
|
|
414
|
+
point1Y,
|
|
415
|
+
point2X,
|
|
416
|
+
point2Y,
|
|
417
|
+
point3X,
|
|
418
|
+
point3Y,
|
|
419
|
+
point4X,
|
|
420
|
+
point4Y
|
|
421
|
+
} = _ref32;
|
|
422
|
+
return `Points on the circle at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, ${point3X} comma ${point3Y}, ${point4X} comma ${point4Y}.`;
|
|
423
|
+
}
|
|
424
|
+
// The above strings are used for interactive graph SR descriptions.
|
|
389
425
|
};
|
|
390
426
|
|
|
391
427
|
/**
|
|
@@ -3494,6 +3530,163 @@ const Util = {
|
|
|
3494
3530
|
deepClone
|
|
3495
3531
|
};
|
|
3496
3532
|
|
|
3533
|
+
// For offline exercises in the mobile app, we download the graphie data
|
|
3534
|
+
// (svgs and localized data files) and serve them from the local file
|
|
3535
|
+
// system (with file://). We replace urls that start with `web+graphie`
|
|
3536
|
+
// in the perseus json with this `file+graphie` prefix to indicate that
|
|
3537
|
+
// they should have the `file://` protocol instead of `https://`.
|
|
3538
|
+
const svgLocalLabelsRegex = /^file\+graphie:/;
|
|
3539
|
+
const hashRegex = /\/([^/]+)$/;
|
|
3540
|
+
|
|
3541
|
+
/**
|
|
3542
|
+
* A Graphie axis range.
|
|
3543
|
+
*/
|
|
3544
|
+
|
|
3545
|
+
/**
|
|
3546
|
+
* A Graphie label.
|
|
3547
|
+
*/
|
|
3548
|
+
|
|
3549
|
+
/**
|
|
3550
|
+
* The Graphie data (labels, etc.)
|
|
3551
|
+
*/
|
|
3552
|
+
|
|
3553
|
+
function getLocale() {
|
|
3554
|
+
const {
|
|
3555
|
+
JIPT,
|
|
3556
|
+
kaLocale
|
|
3557
|
+
} = getDependencies();
|
|
3558
|
+
return JIPT.useJIPT ? "en-pt" : kaLocale;
|
|
3559
|
+
}
|
|
3560
|
+
function shouldUseLocalizedData() {
|
|
3561
|
+
return getLocale() !== "en";
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
// A regex to split at the last / of a URL, separating the base part from the
|
|
3565
|
+
// hash. This is used to create the localized label data URLs.
|
|
3566
|
+
const splitHashRegex = /\/(?=[^/]+$)/;
|
|
3567
|
+
function getLocalizedDataUrl(url) {
|
|
3568
|
+
// For local (cached) graphie images, they are already localized.
|
|
3569
|
+
if (svgLocalLabelsRegex.test(url)) {
|
|
3570
|
+
return Util.getDataUrl(url);
|
|
3571
|
+
}
|
|
3572
|
+
const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
|
|
3573
|
+
return `${base}/${getLocale()}/${hash}-data.json`;
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
// Get the hash from the url, which is just the filename
|
|
3577
|
+
function getUrlHash(url) {
|
|
3578
|
+
const match = url.match(hashRegex);
|
|
3579
|
+
if (match == null) {
|
|
3580
|
+
throw new perseusCore.PerseusError("not a valid URL", perseusCore.Errors.InvalidInput);
|
|
3581
|
+
}
|
|
3582
|
+
return match && match[1];
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
/**
|
|
3586
|
+
* Parse JSONP for a web+graphie://... hash to extract Graphie data, or throw
|
|
3587
|
+
* an error if invalid JSONP.
|
|
3588
|
+
*/
|
|
3589
|
+
function parseDataFromJSONP(graphieHash, graphieJSONP, errorCallback) {
|
|
3590
|
+
// The JSONP is expected to be in the form of `svgDataHASH(...)` or `svgOtherDataHASH(...)`
|
|
3591
|
+
const match = graphieJSONP.match(new RegExp(`^(?:svgData|svgOtherData)${graphieHash}\\((.+)\\);$`));
|
|
3592
|
+
|
|
3593
|
+
// It is also possible that the JSONP is simply a JSON object,
|
|
3594
|
+
// in which case we can parse it directly.
|
|
3595
|
+
const jsonToParse = match ? match[1] : graphieJSONP;
|
|
3596
|
+
|
|
3597
|
+
// Try to parse the JSONP, and if it fails, call the error callback
|
|
3598
|
+
try {
|
|
3599
|
+
return JSON.parse(jsonToParse);
|
|
3600
|
+
} catch (error) {
|
|
3601
|
+
errorCallback(error);
|
|
3602
|
+
return null;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
// The global cache of label data. Its format is:
|
|
3606
|
+
// {
|
|
3607
|
+
// hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
|
|
3608
|
+
// loaded: a boolean of whether the data has been loaded or not
|
|
3609
|
+
// dataCallbacks: a list of callbacks to call with the data when the data
|
|
3610
|
+
// is loaded
|
|
3611
|
+
// data: the other data for this hash
|
|
3612
|
+
// },
|
|
3613
|
+
// ...
|
|
3614
|
+
// }
|
|
3615
|
+
const labelDataCache = {};
|
|
3616
|
+
function loadGraphie(url, onDataLoaded) {
|
|
3617
|
+
const hash = getUrlHash(url);
|
|
3618
|
+
|
|
3619
|
+
// We can't make multiple jsonp calls to the same file because their
|
|
3620
|
+
// callbacks will collide with each other. Instead, we cache the data
|
|
3621
|
+
// and only make the jsonp calls once.
|
|
3622
|
+
const entry = labelDataCache[hash];
|
|
3623
|
+
if (entry != null) {
|
|
3624
|
+
if (entry.loaded) {
|
|
3625
|
+
const {
|
|
3626
|
+
data,
|
|
3627
|
+
localized
|
|
3628
|
+
} = entry;
|
|
3629
|
+
onDataLoaded(data, localized);
|
|
3630
|
+
} else {
|
|
3631
|
+
entry.dataCallbacks.push(onDataLoaded);
|
|
3632
|
+
}
|
|
3633
|
+
} else {
|
|
3634
|
+
const cacheData = {
|
|
3635
|
+
loaded: false,
|
|
3636
|
+
dataCallbacks: [onDataLoaded],
|
|
3637
|
+
localized: shouldUseLocalizedData()
|
|
3638
|
+
};
|
|
3639
|
+
labelDataCache[hash] = cacheData;
|
|
3640
|
+
const retrieveData = async (url, errorCallback) => {
|
|
3641
|
+
const response = await fetch(url);
|
|
3642
|
+
if (!response?.ok) {
|
|
3643
|
+
errorCallback();
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
const jsonp = await response.text();
|
|
3647
|
+
const data = parseDataFromJSONP(hash, jsonp, error => {
|
|
3648
|
+
Log.error("Failed to parse JSONP for svg-image", perseusCore.Errors.Service, {
|
|
3649
|
+
cause: error,
|
|
3650
|
+
loggedMetadata: {
|
|
3651
|
+
dataUrl: Util.getDataUrl(url),
|
|
3652
|
+
jsonp
|
|
3653
|
+
}
|
|
3654
|
+
});
|
|
3655
|
+
});
|
|
3656
|
+
|
|
3657
|
+
// If the data is null, then we failed to parse the JSONP
|
|
3658
|
+
if (!data) {
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
const newCacheEntry = labelDataCache[hash] = {
|
|
3662
|
+
...labelDataCache[hash],
|
|
3663
|
+
loaded: true,
|
|
3664
|
+
data
|
|
3665
|
+
};
|
|
3666
|
+
newCacheEntry.dataCallbacks.forEach(callback => {
|
|
3667
|
+
callback(newCacheEntry.data, cacheData.localized);
|
|
3668
|
+
});
|
|
3669
|
+
};
|
|
3670
|
+
const dataLoadErrorHandler = (x, status, error) => {
|
|
3671
|
+
Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
|
|
3672
|
+
cause: error,
|
|
3673
|
+
loggedMetadata: {
|
|
3674
|
+
dataUrl: Util.getDataUrl(url),
|
|
3675
|
+
status
|
|
3676
|
+
}
|
|
3677
|
+
});
|
|
3678
|
+
};
|
|
3679
|
+
if (shouldUseLocalizedData()) {
|
|
3680
|
+
cacheData.localized = false;
|
|
3681
|
+
// If there is isn't any localized data, use the error callback to try to
|
|
3682
|
+
// load the non-localized data. If that fails too, then we will throw an error.
|
|
3683
|
+
retrieveData(getLocalizedDataUrl(url), () => retrieveData(Util.getDataUrl(url), dataLoadErrorHandler));
|
|
3684
|
+
} else {
|
|
3685
|
+
retrieveData(Util.getDataUrl(url), dataLoadErrorHandler);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3497
3690
|
// Derived from the MIT-licensed:
|
|
3498
3691
|
|
|
3499
3692
|
/*jshint browser:true, node:true */
|
|
@@ -8925,6 +9118,9 @@ class Graphie$1 {
|
|
|
8925
9118
|
$__default["default"](el).css("position", "relative");
|
|
8926
9119
|
this.raphael = Raphael$1(el);
|
|
8927
9120
|
|
|
9121
|
+
// Hide the Raphael canvas from screen readers
|
|
9122
|
+
$__default["default"](el).attr("aria-hidden", "true");
|
|
9123
|
+
|
|
8928
9124
|
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
8929
9125
|
$__default["default"](el).children("div").css("position", "absolute");
|
|
8930
9126
|
}
|
|
@@ -17002,106 +17198,11 @@ class ImageLoader extends React__namespace.Component {
|
|
|
17002
17198
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
17003
17199
|
// Minimum image width to make an image appear as zoomable.
|
|
17004
17200
|
const ZOOMABLE_THRESHOLD = 700;
|
|
17005
|
-
|
|
17006
|
-
// The global cache of label data. Its format is:
|
|
17007
|
-
// {
|
|
17008
|
-
// hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
|
|
17009
|
-
// loaded: a boolean of whether the data has been loaded or not
|
|
17010
|
-
// dataCallbacks: a list of callbacks to call with the data when the data
|
|
17011
|
-
// is loaded
|
|
17012
|
-
// data: the other data for this hash
|
|
17013
|
-
// },
|
|
17014
|
-
// ...
|
|
17015
|
-
// }
|
|
17016
|
-
const labelDataCache = {};
|
|
17017
|
-
|
|
17018
|
-
// Write our own JSONP handler because all the other ones don't do things we
|
|
17019
|
-
// need.
|
|
17020
|
-
const doJSONP = function (url, options) {
|
|
17021
|
-
options = {
|
|
17022
|
-
callbackName: "callback",
|
|
17023
|
-
success: $__default["default"].noop,
|
|
17024
|
-
error: $__default["default"].noop,
|
|
17025
|
-
...options
|
|
17026
|
-
};
|
|
17027
|
-
|
|
17028
|
-
// Create the script
|
|
17029
|
-
const script = document.createElement("script");
|
|
17030
|
-
script.setAttribute("async", "");
|
|
17031
|
-
script.setAttribute("src", url);
|
|
17032
|
-
|
|
17033
|
-
// A cleanup function to run when we're done.
|
|
17034
|
-
function cleanup() {
|
|
17035
|
-
document.head?.removeChild(script);
|
|
17036
|
-
delete window[options.callbackName];
|
|
17037
|
-
}
|
|
17038
|
-
|
|
17039
|
-
// Add the global callback.
|
|
17040
|
-
// @ts-expect-error - TS2740 - Type '() => void' is missing the following properties from type 'Window': clientInformation, closed, customElements, devicePixelRatio, and 206 more.
|
|
17041
|
-
window[options.callbackName] = function () {
|
|
17042
|
-
cleanup();
|
|
17043
|
-
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
17044
|
-
args[_key] = arguments[_key];
|
|
17045
|
-
}
|
|
17046
|
-
options.success.apply(null, args);
|
|
17047
|
-
};
|
|
17048
|
-
|
|
17049
|
-
// Add the error handler.
|
|
17050
|
-
script.addEventListener("error", function () {
|
|
17051
|
-
cleanup();
|
|
17052
|
-
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
17053
|
-
args[_key2] = arguments[_key2];
|
|
17054
|
-
}
|
|
17055
|
-
options.error.apply(null, args);
|
|
17056
|
-
});
|
|
17057
|
-
|
|
17058
|
-
// Insert the script to start the download.
|
|
17059
|
-
document.head?.appendChild(script);
|
|
17060
|
-
};
|
|
17061
|
-
|
|
17062
|
-
// For offline exercises in the mobile app, we download the graphie data
|
|
17063
|
-
// (svgs and localized data files) and serve them from the local file
|
|
17064
|
-
// system (with file://). We replace urls that start with `web+graphie`
|
|
17065
|
-
// in the perseus json with this `file+graphie` prefix to indicate that
|
|
17066
|
-
// they should have the `file://` protocol instead of `https://`.
|
|
17067
|
-
const svgLocalLabelsRegex = /^file\+graphie:/;
|
|
17068
|
-
const hashRegex = /\/([^/]+)$/;
|
|
17069
17201
|
function isImageProbablyPhotograph(imageUrl) {
|
|
17070
17202
|
// TODO(david): Do an inventory to refine this heuristic. For example, what
|
|
17071
17203
|
// % of .png images are illustrations?
|
|
17072
17204
|
return /\.(jpg|jpeg)$/i.test(imageUrl);
|
|
17073
17205
|
}
|
|
17074
|
-
function getLocale() {
|
|
17075
|
-
const {
|
|
17076
|
-
JIPT,
|
|
17077
|
-
kaLocale
|
|
17078
|
-
} = getDependencies();
|
|
17079
|
-
return JIPT.useJIPT ? "en-pt" : kaLocale;
|
|
17080
|
-
}
|
|
17081
|
-
function shouldUseLocalizedData() {
|
|
17082
|
-
return getLocale() !== "en";
|
|
17083
|
-
}
|
|
17084
|
-
|
|
17085
|
-
// A regex to split at the last / of a URL, separating the base part from the
|
|
17086
|
-
// hash. This is used to create the localized label data URLs.
|
|
17087
|
-
const splitHashRegex = /\/(?=[^/]+$)/;
|
|
17088
|
-
function getLocalizedDataUrl(url) {
|
|
17089
|
-
// For local (cached) graphie images, they are already localized.
|
|
17090
|
-
if (svgLocalLabelsRegex.test(url)) {
|
|
17091
|
-
return Util.getDataUrl(url);
|
|
17092
|
-
}
|
|
17093
|
-
const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
|
|
17094
|
-
return `${base}/${getLocale()}/${hash}-data.json`;
|
|
17095
|
-
}
|
|
17096
|
-
|
|
17097
|
-
// Get the hash from the url, which is just the filename
|
|
17098
|
-
function getUrlHash(url) {
|
|
17099
|
-
const match = url.match(hashRegex);
|
|
17100
|
-
if (match == null) {
|
|
17101
|
-
throw new perseusCore.PerseusError("not a valid URL", perseusCore.Errors.InvalidInput);
|
|
17102
|
-
}
|
|
17103
|
-
return match && match[1];
|
|
17104
|
-
}
|
|
17105
17206
|
function defaultPreloader(dimensions) {
|
|
17106
17207
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
17107
17208
|
style: {
|
|
@@ -17188,90 +17289,22 @@ class SvgImage extends React__namespace.Component {
|
|
|
17188
17289
|
return Util.isLabeledSVG(this.props.src) ? state.imageLoaded && state.dataLoaded : state.imageLoaded;
|
|
17189
17290
|
}
|
|
17190
17291
|
loadResources() {
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
if (labelDataCache[hash]) {
|
|
17197
|
-
if (labelDataCache[hash].loaded) {
|
|
17198
|
-
const {
|
|
17199
|
-
data,
|
|
17200
|
-
localized
|
|
17201
|
-
} = labelDataCache[hash];
|
|
17202
|
-
this.onDataLoaded(data, localized);
|
|
17203
|
-
} else {
|
|
17204
|
-
labelDataCache[hash].dataCallbacks.push(this.onDataLoaded);
|
|
17205
|
-
}
|
|
17206
|
-
} else {
|
|
17207
|
-
const cacheData = {
|
|
17208
|
-
loaded: false,
|
|
17209
|
-
dataCallbacks: [this.onDataLoaded],
|
|
17210
|
-
data: null,
|
|
17211
|
-
localized: shouldUseLocalizedData()
|
|
17212
|
-
};
|
|
17213
|
-
labelDataCache[hash] = cacheData;
|
|
17214
|
-
const retrieveData = (url, errorCallback) => {
|
|
17215
|
-
doJSONP(url, {
|
|
17216
|
-
callbackName: "svgData" + hash,
|
|
17217
|
-
success: data => {
|
|
17218
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'data' because it is a read-only property.
|
|
17219
|
-
cacheData.data = data;
|
|
17220
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'loaded' because it is a read-only property.
|
|
17221
|
-
cacheData.loaded = true;
|
|
17222
|
-
___default["default"].each(cacheData.dataCallbacks, callback => {
|
|
17223
|
-
// @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '{ labels: readonly any[]; range: readonly any[]; }'.
|
|
17224
|
-
callback(cacheData.data, cacheData.localized);
|
|
17225
|
-
});
|
|
17226
|
-
},
|
|
17227
|
-
error: errorCallback
|
|
17292
|
+
loadGraphie(this.props.src, (data, localized) => {
|
|
17293
|
+
if (this._isMounted && data.labels && data.range) {
|
|
17294
|
+
const labelsRendered = {};
|
|
17295
|
+
data.labels.forEach(label => {
|
|
17296
|
+
labelsRendered[label.content] = false;
|
|
17228
17297
|
});
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
|
|
17235
|
-
// If there is isn't any localized data, fall back to
|
|
17236
|
-
// the original, unlocalized data
|
|
17237
|
-
retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
|
|
17238
|
-
Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
|
|
17239
|
-
cause: error,
|
|
17240
|
-
loggedMetadata: {
|
|
17241
|
-
dataUrl: Util.getDataUrl(this.props.src),
|
|
17242
|
-
status
|
|
17243
|
-
}
|
|
17244
|
-
});
|
|
17245
|
-
});
|
|
17246
|
-
});
|
|
17247
|
-
} else {
|
|
17248
|
-
retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
|
|
17249
|
-
Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
|
|
17250
|
-
cause: error,
|
|
17251
|
-
loggedMetadata: {
|
|
17252
|
-
dataUrl: Util.getDataUrl(this.props.src),
|
|
17253
|
-
status
|
|
17254
|
-
}
|
|
17255
|
-
});
|
|
17298
|
+
this.setState({
|
|
17299
|
+
dataLoaded: true,
|
|
17300
|
+
labelDataIsLocalized: localized,
|
|
17301
|
+
labelsRendered,
|
|
17302
|
+
labels: data.labels,
|
|
17303
|
+
range: data.range
|
|
17256
17304
|
});
|
|
17257
17305
|
}
|
|
17258
|
-
}
|
|
17306
|
+
});
|
|
17259
17307
|
}
|
|
17260
|
-
onDataLoaded = (data, localized) => {
|
|
17261
|
-
if (this._isMounted && data.labels && data.range) {
|
|
17262
|
-
const labelsRendered = data.labels.reduce((dict, label) => ({
|
|
17263
|
-
...dict,
|
|
17264
|
-
[label.content]: false
|
|
17265
|
-
}), {});
|
|
17266
|
-
this.setState({
|
|
17267
|
-
dataLoaded: true,
|
|
17268
|
-
labelDataIsLocalized: localized,
|
|
17269
|
-
labelsRendered,
|
|
17270
|
-
labels: data.labels,
|
|
17271
|
-
range: data.range
|
|
17272
|
-
});
|
|
17273
|
-
}
|
|
17274
|
-
};
|
|
17275
17308
|
sizeProvided() {
|
|
17276
17309
|
return this.props.width != null && this.props.height != null;
|
|
17277
17310
|
}
|
|
@@ -34464,21 +34497,96 @@ function CircleGraph(props) {
|
|
|
34464
34497
|
center,
|
|
34465
34498
|
radiusPoint
|
|
34466
34499
|
} = graphState;
|
|
34467
|
-
|
|
34500
|
+
const {
|
|
34501
|
+
strings
|
|
34502
|
+
} = usePerseusI18n();
|
|
34503
|
+
const [radiusPointAriaLive, setRadiusPointAriaLive] = React__namespace.useState("off");
|
|
34504
|
+
const radius = getRadius(graphState);
|
|
34505
|
+
const id = React__namespace.useId();
|
|
34506
|
+
const circleId = id + "-circle";
|
|
34507
|
+
const radiusId = id + "-radius";
|
|
34508
|
+
const outerPointsId = id + "-outer-points";
|
|
34509
|
+
|
|
34510
|
+
// Aria label strings
|
|
34511
|
+
const circleGraphAriaLabel = strings.circleGraphAriaLabel;
|
|
34512
|
+
const circleShapeAriaLabel = strings.circleShapeAriaLabel({
|
|
34513
|
+
centerX: center[0],
|
|
34514
|
+
centerY: center[1]
|
|
34515
|
+
});
|
|
34516
|
+
const circleRadiusPointAriaLabel = strings.circleRadiusPointAriaLabel({
|
|
34517
|
+
radiusPointX: radiusPoint[0],
|
|
34518
|
+
radiusPointY: radiusPoint[1]
|
|
34519
|
+
});
|
|
34520
|
+
const circleRadiusDescription = strings.circleRadiusDescription({
|
|
34521
|
+
radius
|
|
34522
|
+
});
|
|
34523
|
+
const circleOuterPointsDescription = strings.circleOuterPointsDescription({
|
|
34524
|
+
point1X: center[0] + radius,
|
|
34525
|
+
point1Y: center[1],
|
|
34526
|
+
point2X: center[0],
|
|
34527
|
+
point2Y: center[1] + radius,
|
|
34528
|
+
point3X: center[0] - radius,
|
|
34529
|
+
point3Y: center[1],
|
|
34530
|
+
point4X: center[0],
|
|
34531
|
+
point4Y: center[1] - radius
|
|
34532
|
+
});
|
|
34533
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
34534
|
+
// Outer circle minimal description
|
|
34535
|
+
"aria-label": circleGraphAriaLabel,
|
|
34536
|
+
"aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
|
|
34537
|
+
}, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
|
|
34538
|
+
id: circleId
|
|
34539
|
+
// Focusable circle aria label reads with every update
|
|
34540
|
+
// because of the aria-live property in the circle <g>.
|
|
34541
|
+
,
|
|
34542
|
+
ariaLabel: circleShapeAriaLabel
|
|
34543
|
+
// Aria-describedby describes additional info on focus.
|
|
34544
|
+
,
|
|
34545
|
+
ariaDescribedBy: `${radiusId} ${outerPointsId}`,
|
|
34468
34546
|
center: center,
|
|
34469
|
-
radius:
|
|
34470
|
-
onMove: c =>
|
|
34471
|
-
|
|
34547
|
+
radius: radius,
|
|
34548
|
+
onMove: c => {
|
|
34549
|
+
setRadiusPointAriaLive("off");
|
|
34550
|
+
dispatch(actions.circle.moveCenter(c));
|
|
34551
|
+
}
|
|
34552
|
+
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1
|
|
34553
|
+
// Radius point aria label reads with every update.
|
|
34554
|
+
, {
|
|
34555
|
+
ariaLabel: `${circleRadiusPointAriaLabel} ${circleRadiusDescription}`
|
|
34556
|
+
// Aria-describedby describes additional info on focus.
|
|
34557
|
+
,
|
|
34558
|
+
ariaDescribedBy: `${outerPointsId}`
|
|
34559
|
+
// The radius point's aria-live property is set to "off" when
|
|
34560
|
+
// the circle is moved, so that it doesn't override the circle's
|
|
34561
|
+
// aria-live (since the point is moved along with the circle).
|
|
34562
|
+
// When the radius point is moved, the aria-live is set to
|
|
34563
|
+
// "polite" so that the radius is read out.
|
|
34564
|
+
,
|
|
34565
|
+
ariaLive: radiusPointAriaLive,
|
|
34472
34566
|
point: radiusPoint,
|
|
34473
34567
|
sequenceNumber: 1,
|
|
34474
34568
|
cursor: "ew-resize",
|
|
34475
34569
|
onMove: newRadiusPoint => {
|
|
34570
|
+
setRadiusPointAriaLive("polite");
|
|
34476
34571
|
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
34477
34572
|
}
|
|
34478
|
-
})
|
|
34573
|
+
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
34574
|
+
id: radiusId,
|
|
34575
|
+
style: {
|
|
34576
|
+
display: "hidden"
|
|
34577
|
+
}
|
|
34578
|
+
}, circleRadiusDescription), /*#__PURE__*/React__namespace.createElement("g", {
|
|
34579
|
+
id: outerPointsId,
|
|
34580
|
+
style: {
|
|
34581
|
+
display: "hidden"
|
|
34582
|
+
}
|
|
34583
|
+
}, circleOuterPointsDescription));
|
|
34479
34584
|
}
|
|
34480
34585
|
function MovableCircle(props) {
|
|
34481
34586
|
const {
|
|
34587
|
+
id,
|
|
34588
|
+
ariaLabel,
|
|
34589
|
+
ariaDescribedBy,
|
|
34482
34590
|
center,
|
|
34483
34591
|
radius,
|
|
34484
34592
|
onMove
|
|
@@ -34499,7 +34607,11 @@ function MovableCircle(props) {
|
|
|
34499
34607
|
const [centerPx] = useTransformVectorsToPixels(center);
|
|
34500
34608
|
const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
|
|
34501
34609
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
34610
|
+
"aria-label": ariaLabel,
|
|
34611
|
+
"aria-describedby": ariaDescribedBy,
|
|
34612
|
+
"aria-live": "polite",
|
|
34502
34613
|
ref: draggableRef,
|
|
34614
|
+
role: "button",
|
|
34503
34615
|
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
34504
34616
|
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
34505
34617
|
}, /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
@@ -34509,6 +34621,7 @@ function MovableCircle(props) {
|
|
|
34509
34621
|
rx: radiiPx[X] + 3,
|
|
34510
34622
|
ry: radiiPx[Y] + 3
|
|
34511
34623
|
}), /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
34624
|
+
id: id,
|
|
34512
34625
|
className: "circle",
|
|
34513
34626
|
cx: centerPx[X],
|
|
34514
34627
|
cy: centerPx[Y],
|
|
@@ -45530,7 +45643,7 @@ const init = function () {
|
|
|
45530
45643
|
|
|
45531
45644
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45532
45645
|
const libName = "@khanacademy/perseus";
|
|
45533
|
-
const libVersion = "48.
|
|
45646
|
+
const libVersion = "48.2.0";
|
|
45534
45647
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45535
45648
|
|
|
45536
45649
|
const apiVersion = {
|
|
@@ -49196,6 +49309,7 @@ exports.lockedFigureFillStyles = lockedFigureFillStyles;
|
|
|
49196
49309
|
exports.makeSafeUrl = makeSafeUrl;
|
|
49197
49310
|
exports.mathOnlyParser = mathOnlyParser;
|
|
49198
49311
|
exports.mediaQueries = mediaQueries;
|
|
49312
|
+
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
49199
49313
|
exports.parsePerseusItem = parsePerseusItem;
|
|
49200
49314
|
exports.plotterPlotTypes = plotterPlotTypes;
|
|
49201
49315
|
exports.preprocessTex = preprocessTex;
|