@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
|
@@ -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
|
-
|
|
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
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
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
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
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
|
-
|
|
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:
|
|
34149
|
-
onMove: c =>
|
|
34150
|
-
|
|
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.
|
|
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
|