@khanacademy/perseus 52.0.1 → 53.1.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/number-input.d.ts +2 -0
- package/dist/components/range-input.d.ts +1 -0
- package/dist/components/svg-image.d.ts +2 -2
- package/dist/es/index.css +0 -30
- package/dist/es/index.js +893 -1042
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +42 -6
- package/dist/es/strings.js.map +1 -1
- package/dist/index.css +0 -30
- package/dist/index.d.ts +1 -2
- package/dist/index.js +913 -2263
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +0 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/server-item-renderer.d.ts +0 -3
- package/dist/strings.d.ts +36 -0
- package/dist/strings.js +60 -6
- package/dist/strings.js.map +1 -1
- package/dist/styles/constants.d.ts +0 -2
- package/dist/types.d.ts +3 -32
- package/dist/util/math-utils.d.ts +1 -0
- package/dist/util/scoring.d.ts +0 -15
- package/dist/util/test-utils.d.ts +1 -1
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
- package/dist/widget-container.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +0 -3
- package/dist/widgets/cs-program/cs-program.d.ts +2 -3
- package/dist/widgets/definition/definition.d.ts +0 -2
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +0 -1
- package/dist/widgets/dropdown/dropdown.d.ts +2 -4
- package/dist/widgets/explanation/explanation.d.ts +0 -2
- package/dist/widgets/expression/expression.d.ts +1 -11
- package/dist/widgets/grapher/grapher.d.ts +2 -2
- package/dist/widgets/group/group.d.ts +0 -4
- package/dist/widgets/iframe/iframe.d.ts +2 -3
- package/dist/widgets/image/image.d.ts +0 -3
- package/dist/widgets/input-number/input-number.d.ts +0 -3
- package/dist/widgets/interaction/interaction.d.ts +0 -1
- package/dist/widgets/interactive-graph.d.ts +2 -3
- package/dist/widgets/interactive-graphs/backgrounds/axis-ticks.d.ts +1 -0
- package/dist/widgets/interactive-graphs/graphs/polygon.d.ts +3 -2
- package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +16 -1
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +0 -2
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +0 -2
- package/dist/widgets/label-image/label-image.d.ts +2 -2
- package/dist/widgets/matcher/matcher.d.ts +0 -2
- package/dist/widgets/matrix/matrix.d.ts +2 -4
- package/dist/widgets/measurer/measurer.d.ts +0 -1
- package/dist/widgets/mock-widgets/mock-widget-types.d.ts +0 -6
- package/dist/widgets/mock-widgets/mock-widget.d.ts +2 -5
- package/dist/widgets/molecule/molecule.d.ts +0 -1
- package/dist/widgets/number-line/number-line.d.ts +2 -3
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -3
- package/dist/widgets/orderer/orderer.d.ts +0 -3
- package/dist/widgets/passage/passage.d.ts +0 -1
- package/dist/widgets/passage-ref/passage-ref.d.ts +0 -2
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +0 -2
- package/dist/widgets/plotter/plotter.d.ts +2 -3
- package/dist/widgets/radio/radio.d.ts +2 -4
- package/dist/widgets/sorter/sorter.d.ts +2 -4
- package/dist/widgets/table/table.d.ts +2 -3
- package/dist/widgets/video/video.d.ts +0 -3
- package/dist/widgets.d.ts +1 -32
- package/package.json +46 -46
- package/dist/renderer-util.d.ts +0 -14
- package/dist/widgets/__shared__/score-noop.d.ts +0 -11
- package/dist/widgets/group/score-group.d.ts +0 -4
- package/dist/widgets/group/validate-group.d.ts +0 -4
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +0 -4
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +0 -5
- package/dist/widgets/mock-widgets/validate-mock-widget.d.ts +0 -4
package/dist/es/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import * as KAS from '@khanacademy/kas';
|
|
2
2
|
import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
|
-
import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck,
|
|
3
|
+
import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, getNumericInputPublicWidgetOptions, passageRefLogic, radioLogic, getRadioPublicWidgetOptions, getCategorizerPublicWidgetOptions, getCSProgramPublicWidgetOptions, getDropdownPublicWidgetOptions, GrapherUtil as GrapherUtil$1, getGrapherPublicWidgetOptions, getIFramePublicWidgetOptions, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getInteractiveGraphPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatrixPublicWidgetOptions, getMatrixSize, measurerLogic, getNumberLinePublicWidgetOptions, getOrdererPublicWidgetOptions, getPlotterPublicWidgetOptions, getSorterPublicWidgetOptions, getTablePublicWidgetOptions, addLibraryVersionToPerseusDebug, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
|
|
4
4
|
export { isFailure, isSuccess, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem } from '@khanacademy/perseus-core';
|
|
5
5
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
6
6
|
import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
|
|
7
|
-
import { scoreExpression, validateExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, validateRadio, scoreCategorizer, validateCategorizer, scoreCSProgram, scoreDropdown, validateDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, validateMatrix, scoreNumberLine, validateNumberLine, scoreOrderer, validateOrderer, scorePlotter, validatePlotter, scoreSorter, validateSorter, scoreTable, validateTable } from '@khanacademy/perseus-score';
|
|
8
7
|
import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
|
|
9
8
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
10
9
|
import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -21,6 +20,7 @@ import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
|
|
|
21
20
|
import $ from 'jquery';
|
|
22
21
|
import { v4 } from 'uuid';
|
|
23
22
|
import PropTypes from 'prop-types';
|
|
23
|
+
import { KhanAnswerTypes, emptyWidgetsFunctional, scoreWidgetsFunctional, flattenScores, inputNumberAnswerTypes, scoreRadio, scoreLabelImageMarker } from '@khanacademy/perseus-score';
|
|
24
24
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
25
25
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
26
26
|
import { point, KhanMath, number, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
|
|
@@ -249,7 +249,7 @@ const mockStrings = {
|
|
|
249
249
|
num,
|
|
250
250
|
x,
|
|
251
251
|
y
|
|
252
|
-
}) => `Point ${num} at ${x} comma ${y}
|
|
252
|
+
}) => `Point ${num} at ${x} comma ${y}.`,
|
|
253
253
|
srInteractiveElements: ({
|
|
254
254
|
elements
|
|
255
255
|
}) => `Interactive elements: ${elements}`,
|
|
@@ -437,7 +437,32 @@ const mockStrings = {
|
|
|
437
437
|
point2Y,
|
|
438
438
|
point3X,
|
|
439
439
|
point3Y
|
|
440
|
-
}) => `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}
|
|
440
|
+
}) => `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`,
|
|
441
|
+
srPolygonGraph: "A polygon.",
|
|
442
|
+
srPolygonGraphCoordinatePlane: "A polygon on a coordinate plane.",
|
|
443
|
+
srPolygonGraphPointsNum: ({
|
|
444
|
+
num
|
|
445
|
+
}) => `The polygon has ${num} points.`,
|
|
446
|
+
srPolygonGraphPointsOne: "The polygon has 1 point.",
|
|
447
|
+
srPolygonElementsNum: ({
|
|
448
|
+
num
|
|
449
|
+
}) => `A polygon with ${num} points.`,
|
|
450
|
+
srPolygonElementsOne: "A polygon with 1 point.",
|
|
451
|
+
srPolygonPointAngleApprox: ({
|
|
452
|
+
angle
|
|
453
|
+
}) => `Angle approximately equal to ${angle} degrees.`,
|
|
454
|
+
srPolygonPointAngle: ({
|
|
455
|
+
angle
|
|
456
|
+
}) => `Angle equal to ${angle} degrees.`,
|
|
457
|
+
srPolygonSideLength: ({
|
|
458
|
+
pointNum,
|
|
459
|
+
length
|
|
460
|
+
}) => `A line segment, length equal to ${length} units, connects to point ${pointNum}.`,
|
|
461
|
+
srPolygonSideLengthApprox: ({
|
|
462
|
+
pointNum,
|
|
463
|
+
length
|
|
464
|
+
}) => `A line segment, length approximately equal to ${length} units, connects to point ${pointNum}.`,
|
|
465
|
+
srUnlimitedPolygonEmpty: "An empty coordinate plane."
|
|
441
466
|
// The above strings are used for interactive graph SR descriptions.
|
|
442
467
|
};
|
|
443
468
|
|
|
@@ -1389,7 +1414,6 @@ const ClassNames = {
|
|
|
1389
1414
|
SELECTED: "perseus-radio-selected",
|
|
1390
1415
|
OPTION_CONTENT: "perseus-radio-option-content"
|
|
1391
1416
|
},
|
|
1392
|
-
INTERACTIVE: "perseus-interactive",
|
|
1393
1417
|
CORRECT: "perseus-correct",
|
|
1394
1418
|
INCORRECT: "perseus-incorrect",
|
|
1395
1419
|
UNANSWERED: "perseus-unanswered",
|
|
@@ -1637,7 +1661,6 @@ class Expression extends React.Component {
|
|
|
1637
1661
|
// eslint-disable-next-line react/no-string-refs
|
|
1638
1662
|
, {
|
|
1639
1663
|
ref: "input",
|
|
1640
|
-
className: ClassNames.INTERACTIVE,
|
|
1641
1664
|
value: this.props.value,
|
|
1642
1665
|
onChange: this.changeAndTrack,
|
|
1643
1666
|
convertDotToTimes: this.props.times,
|
|
@@ -1750,7 +1773,6 @@ var Expression$1 = {
|
|
|
1750
1773
|
name: "expression",
|
|
1751
1774
|
displayName: "Expression / Equation",
|
|
1752
1775
|
accessible: true,
|
|
1753
|
-
defaultAlignment: "inline-block",
|
|
1754
1776
|
widget: ExpressionWithDependencies,
|
|
1755
1777
|
transform: widgetOptions => {
|
|
1756
1778
|
const {
|
|
@@ -1775,12 +1797,6 @@ var Expression$1 = {
|
|
|
1775
1797
|
propUpgrades: expressionLogic.widgetOptionsUpgrades,
|
|
1776
1798
|
// For use by the editor
|
|
1777
1799
|
isLintable: true,
|
|
1778
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
1779
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
1780
|
-
scorer: scoreExpression,
|
|
1781
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
1782
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
1783
|
-
validator: validateExpression,
|
|
1784
1800
|
getPublicWidgetOptions: getExpressionPublicWidgetOptions,
|
|
1785
1801
|
// TODO(LEMS-2656): remove TS suppression
|
|
1786
1802
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
@@ -3061,10 +3077,6 @@ const pureMdMax$1 = "1023px";
|
|
|
3061
3077
|
const pureLgMax$1 = "1279px";
|
|
3062
3078
|
// @tableBackgroundAccent: #f9f9f9; // for striping
|
|
3063
3079
|
const tableBackgroundAccent = "#F9F9F9";
|
|
3064
|
-
// @zIndexAboveScratchpad: @zIndexScratchPad + 1;
|
|
3065
|
-
const zIndexAboveScratchpad$1 = 2;
|
|
3066
|
-
// @zIndexInteractiveComponent: @zIndexAboveScratchpad + 1;
|
|
3067
|
-
const zIndexInteractiveComponent$1 = 3;
|
|
3068
3080
|
// @phoneMargin: 16px;
|
|
3069
3081
|
const phoneMargin = 16;
|
|
3070
3082
|
const negativePhoneMargin = -16;
|
|
@@ -3108,8 +3120,6 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
3108
3120
|
pureMdMax: pureMdMax$1,
|
|
3109
3121
|
pureLgMax: pureLgMax$1,
|
|
3110
3122
|
tableBackgroundAccent: tableBackgroundAccent,
|
|
3111
|
-
zIndexAboveScratchpad: zIndexAboveScratchpad$1,
|
|
3112
|
-
zIndexInteractiveComponent: zIndexInteractiveComponent$1,
|
|
3113
3123
|
phoneMargin: phoneMargin,
|
|
3114
3124
|
negativePhoneMargin: negativePhoneMargin,
|
|
3115
3125
|
hintBorderWidth: hintBorderWidth,
|
|
@@ -9853,7 +9863,7 @@ class WrappedLine extends WrappedDrawing {
|
|
|
9853
9863
|
|
|
9854
9864
|
/* eslint-disable @babel/no-invalid-this */
|
|
9855
9865
|
const {
|
|
9856
|
-
clockwise: clockwise$
|
|
9866
|
+
clockwise: clockwise$2,
|
|
9857
9867
|
reverseVector: reverseVector$1
|
|
9858
9868
|
} = geometry;
|
|
9859
9869
|
function scaledDistanceFromAngle(angle) {
|
|
@@ -11091,7 +11101,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11091
11101
|
// Temporary objects
|
|
11092
11102
|
_.invoke(polygon.temp, "remove");
|
|
11093
11103
|
polygon.temp = [];
|
|
11094
|
-
const isClockwise = clockwise$
|
|
11104
|
+
const isClockwise = clockwise$2(polygon.coords);
|
|
11095
11105
|
|
|
11096
11106
|
// Update angle labels
|
|
11097
11107
|
if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
|
|
@@ -14482,7 +14492,7 @@ function sum(array) {
|
|
|
14482
14492
|
return memo + arg;
|
|
14483
14493
|
}, 0);
|
|
14484
14494
|
}
|
|
14485
|
-
function clockwise$
|
|
14495
|
+
function clockwise$1(points) {
|
|
14486
14496
|
const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
14487
14497
|
const areas = _.map(segments, function (segment) {
|
|
14488
14498
|
const p1 = segment[0];
|
|
@@ -14548,7 +14558,7 @@ const draw$1 = {
|
|
|
14548
14558
|
const graphie = this.graphie;
|
|
14549
14559
|
const self = this;
|
|
14550
14560
|
const coords = _.invoke(state.points, "coord");
|
|
14551
|
-
const isClockwise = clockwise$
|
|
14561
|
+
const isClockwise = clockwise$1(coords);
|
|
14552
14562
|
const n = coords.length;
|
|
14553
14563
|
|
|
14554
14564
|
// graphie.labelAngle and similar methods attempt to re-use the label
|
|
@@ -16285,9 +16295,29 @@ class Zoomable extends React.Component {
|
|
|
16285
16295
|
if (!this._isMounted) {
|
|
16286
16296
|
return;
|
|
16287
16297
|
}
|
|
16298
|
+
|
|
16299
|
+
// When a zoomable item is contained within a collapsed parent (such as an Explanation widget),
|
|
16300
|
+
// the parent node has zero width, which causes the scale to be miscalculated.
|
|
16301
|
+
// Therefore, we check to see if the node has a valid width, and if it doesn't,
|
|
16302
|
+
// then we traverse up the DOM tree and look for the closest "perseus-renderer"
|
|
16303
|
+
// (which should have a width).
|
|
16304
|
+
// To be cautious, we also account for the possibility that we can't find any parents with a valid width
|
|
16305
|
+
// (not that they don't exist, just that we can't find them).
|
|
16306
|
+
let parentNode = this._node;
|
|
16307
|
+
let currentNode = parentNode;
|
|
16308
|
+
while (currentNode && currentNode.offsetWidth === 0) {
|
|
16309
|
+
var _currentNode$parentEl, _currentNode$parentEl2;
|
|
16310
|
+
// When traversing up the DOM tree, we need to start at the "parentElement"
|
|
16311
|
+
// because "closest()" can match the current element,
|
|
16312
|
+
// and if it does so, then we are stuck in an infinite loop.
|
|
16313
|
+
currentNode = (_currentNode$parentEl = (_currentNode$parentEl2 = currentNode.parentElement) == null ? void 0 : _currentNode$parentEl2.closest(".perseus-renderer")) != null ? _currentNode$parentEl : null;
|
|
16314
|
+
}
|
|
16315
|
+
if (currentNode) {
|
|
16316
|
+
parentNode = currentNode;
|
|
16317
|
+
}
|
|
16288
16318
|
const parentBounds = {
|
|
16289
|
-
width:
|
|
16290
|
-
height:
|
|
16319
|
+
width: parentNode.offsetWidth,
|
|
16320
|
+
height: parentNode.offsetHeight
|
|
16291
16321
|
};
|
|
16292
16322
|
const childBounds = this.props.computeChildBounds(this._node, parentBounds);
|
|
16293
16323
|
|
|
@@ -17334,130 +17364,162 @@ class QuestionParagraph extends React.Component {
|
|
|
17334
17364
|
}
|
|
17335
17365
|
}
|
|
17336
17366
|
|
|
17337
|
-
const noScore = {
|
|
17338
|
-
type: "points",
|
|
17339
|
-
earned: 0,
|
|
17340
|
-
total: 0,
|
|
17341
|
-
message: null
|
|
17342
|
-
};
|
|
17343
|
-
|
|
17344
17367
|
/**
|
|
17345
|
-
*
|
|
17346
|
-
* Trying to encapsulate references to the score format.
|
|
17347
|
-
*/
|
|
17348
|
-
function scoreIsEmpty(score) {
|
|
17349
|
-
// HACK(benkomalo): ugh. this isn't great; the Perseus score objects
|
|
17350
|
-
// overload the type "invalid" for what should probably be three
|
|
17351
|
-
// distinct cases:
|
|
17352
|
-
// - truly empty or not fully filled out
|
|
17353
|
-
// - invalid or malformed inputs
|
|
17354
|
-
// - "almost correct" like inputs where the widget wants to give
|
|
17355
|
-
// feedback (e.g. a fraction needs to be reduced, or `pi` should
|
|
17356
|
-
// be used instead of 3.14)
|
|
17357
|
-
//
|
|
17358
|
-
// Unfortunately the coercion happens all over the place, as these
|
|
17359
|
-
// Perseus style score objects are created *everywhere* (basically
|
|
17360
|
-
// in every widget), so it's hard to change now. We assume that
|
|
17361
|
-
// anything with a "message" is not truly empty, and one of the
|
|
17362
|
-
// latter two cases for now.
|
|
17363
|
-
return score.type === "invalid" && (!score.message || score.message.length === 0);
|
|
17364
|
-
}
|
|
17365
|
-
|
|
17366
|
-
/**
|
|
17367
|
-
* Combine two score objects.
|
|
17368
|
+
* TranslationLinter asynchronously calls Khan Academy's poentry linter.
|
|
17368
17369
|
*
|
|
17369
|
-
*
|
|
17370
|
-
*
|
|
17370
|
+
* The poentry linter checks for issues that prevent content from being
|
|
17371
|
+
* translated. This linter is KA-specific and so TranslationLinter does not do
|
|
17372
|
+
* anything in non-KA environments.
|
|
17371
17373
|
*/
|
|
17372
|
-
function combineScores(scoreA, scoreB) {
|
|
17373
|
-
let message;
|
|
17374
|
-
if (scoreA.type === "points" && scoreB.type === "points") {
|
|
17375
|
-
if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
|
|
17376
|
-
// TODO(alpert): Figure out how to combine messages usefully
|
|
17377
|
-
message = null;
|
|
17378
|
-
} else {
|
|
17379
|
-
message = scoreA.message || scoreB.message;
|
|
17380
|
-
}
|
|
17381
|
-
return {
|
|
17382
|
-
type: "points",
|
|
17383
|
-
earned: scoreA.earned + scoreB.earned,
|
|
17384
|
-
total: scoreA.total + scoreB.total,
|
|
17385
|
-
message: message
|
|
17386
|
-
};
|
|
17387
|
-
}
|
|
17388
|
-
if (scoreA.type === "points" && scoreB.type === "invalid") {
|
|
17389
|
-
return scoreB;
|
|
17390
|
-
}
|
|
17391
|
-
if (scoreA.type === "invalid" && scoreB.type === "points") {
|
|
17392
|
-
return scoreA;
|
|
17393
|
-
}
|
|
17394
|
-
if (scoreA.type === "invalid" && scoreB.type === "invalid") {
|
|
17395
|
-
if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
|
|
17396
|
-
// TODO(alpert): Figure out how to combine messages usefully
|
|
17397
|
-
message = null;
|
|
17398
|
-
} else {
|
|
17399
|
-
message = scoreA.message || scoreB.message;
|
|
17400
|
-
}
|
|
17401
|
-
return {
|
|
17402
|
-
type: "invalid",
|
|
17403
|
-
message: message
|
|
17404
|
-
};
|
|
17405
|
-
}
|
|
17406
17374
|
|
|
17375
|
+
// A LintCB is called once the linter has been run.
|
|
17376
|
+
|
|
17377
|
+
// msec to wait before we actually call the linter after receiving the
|
|
17378
|
+
// last call. The timer is reset if the lint is called during the wait.
|
|
17379
|
+
const DEBOUNCE_TIMEOUT = 1000;
|
|
17380
|
+
class TranslationLinter {
|
|
17381
|
+
constructor() {
|
|
17382
|
+
// To avoid linting when nothing has changed.
|
|
17383
|
+
this.previousContent = null;
|
|
17384
|
+
/**
|
|
17385
|
+
* Runs the poentry linter on the given perseus string asynchronously.
|
|
17386
|
+
*
|
|
17387
|
+
* It doesn't do anything unless we're in a KA environment.
|
|
17388
|
+
*
|
|
17389
|
+
* onLintErrorsGenerated is called even if there are no errors. It is not
|
|
17390
|
+
* called if perseusStr is unchanged from the last call.
|
|
17391
|
+
*
|
|
17392
|
+
* Linting is relatively expensive -- so we debounce linting each instance
|
|
17393
|
+
* for some amount of time.
|
|
17394
|
+
*/
|
|
17395
|
+
this.runLinter = debounce$1((perseusStr, onLintErrorsGenerated) => {
|
|
17396
|
+
// TODO(FEI-5003): Do we still need to check for 'KA'?
|
|
17397
|
+
if (typeof KA === "undefined") {
|
|
17398
|
+
return;
|
|
17399
|
+
}
|
|
17400
|
+
if (perseusStr === this.previousContent) {
|
|
17401
|
+
return;
|
|
17402
|
+
}
|
|
17403
|
+
this.previousContent = perseusStr;
|
|
17404
|
+
if (perseusStr === "") {
|
|
17405
|
+
onLintErrorsGenerated([]);
|
|
17406
|
+
return;
|
|
17407
|
+
}
|
|
17408
|
+
fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
|
|
17409
|
+
headers: {
|
|
17410
|
+
"Content-Type": "application/json"
|
|
17411
|
+
},
|
|
17412
|
+
body: JSON.stringify({
|
|
17413
|
+
// The poentry linter verifies that the translation from
|
|
17414
|
+
// the original text to the translated text is sane. We
|
|
17415
|
+
// want to ensure that the translation from English to
|
|
17416
|
+
// English is sane, so msgid === mstr.
|
|
17417
|
+
msgid: perseusStr,
|
|
17418
|
+
// msgid is the original text
|
|
17419
|
+
msgstr: perseusStr,
|
|
17420
|
+
// msgstr is the translated text
|
|
17421
|
+
format: "perseus_text",
|
|
17422
|
+
filename: ""
|
|
17423
|
+
}),
|
|
17424
|
+
method: "POST"
|
|
17425
|
+
}).then(response => {
|
|
17426
|
+
if (response.status >= 400) {
|
|
17427
|
+
return {
|
|
17428
|
+
status: "error",
|
|
17429
|
+
message: "Could not run i18n linter."
|
|
17430
|
+
};
|
|
17431
|
+
}
|
|
17432
|
+
return response.json();
|
|
17433
|
+
}, rejection => {
|
|
17434
|
+
return {
|
|
17435
|
+
status: "error",
|
|
17436
|
+
message: "Could not run i18n linter."
|
|
17437
|
+
};
|
|
17438
|
+
}).then(json => {
|
|
17439
|
+
if (json.status === "error") {
|
|
17440
|
+
onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
|
|
17441
|
+
} else {
|
|
17442
|
+
onLintErrorsGenerated([]);
|
|
17443
|
+
}
|
|
17444
|
+
});
|
|
17445
|
+
}, DEBOUNCE_TIMEOUT);
|
|
17446
|
+
}
|
|
17407
17447
|
/**
|
|
17408
|
-
*
|
|
17409
|
-
*
|
|
17448
|
+
* Applies an array of errors generated by linters without position
|
|
17449
|
+
* information (TranslationLinter and the legacy getSaveWarnings()) to the
|
|
17450
|
+
* top of a Perseus tree.
|
|
17410
17451
|
*/
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17452
|
+
applyLintErrors(parsedMarkdown, translationLintErrors) {
|
|
17453
|
+
// These lint errors do not have position data associated with
|
|
17454
|
+
// them, so we just plop them at the top.
|
|
17455
|
+
if (translationLintErrors.length) {
|
|
17456
|
+
const errorText = translationLintErrors.join("\n\n");
|
|
17457
|
+
parsedMarkdown.unshift({
|
|
17458
|
+
content: {
|
|
17459
|
+
type: "text",
|
|
17460
|
+
content: ""
|
|
17461
|
+
},
|
|
17462
|
+
insideTable: false,
|
|
17463
|
+
message: errorText,
|
|
17464
|
+
ruleName: "legacy-error",
|
|
17465
|
+
severity: Rule.Severity.ERROR,
|
|
17466
|
+
type: "lint"
|
|
17467
|
+
});
|
|
17415
17468
|
}
|
|
17416
|
-
}
|
|
17417
|
-
}
|
|
17418
|
-
function flattenScores(widgetScoreMap) {
|
|
17419
|
-
return Object.values(widgetScoreMap).reduce(combineScores, noScore);
|
|
17420
|
-
}
|
|
17421
|
-
function isCorrect(score) {
|
|
17422
|
-
return score.type === "points" && score.earned >= score.total;
|
|
17469
|
+
}
|
|
17423
17470
|
}
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17471
|
+
|
|
17472
|
+
/**
|
|
17473
|
+
* Preprocess TeX code to convert things that MathJax doesn't know how to handle
|
|
17474
|
+
* to things is does.
|
|
17475
|
+
*/
|
|
17476
|
+
var preprocessTex = (texCode => texCode
|
|
17477
|
+
// Replace uses of \begin{align}...\end{align} which KaTeX doesn't
|
|
17478
|
+
// support (yet) with \begin{aligned}...\end{aligned} which renders
|
|
17479
|
+
// the same is supported by KaTeX. It does the same for align*.
|
|
17480
|
+
// TODO(kevinb) update content to use aligned instead of align.
|
|
17481
|
+
// TODO(LEMS-1608) Remove this replacement as MathJax supports the
|
|
17482
|
+
// "align" macro correctly (and, in fact, it is not synonymous with
|
|
17483
|
+
// "aligned").
|
|
17484
|
+
.replace(/\{align[*]?\}/g, "{aligned}")
|
|
17485
|
+
// Replace non-breaking spaces with regular spaces.
|
|
17486
|
+
.replace(/[\u00a0]/g, " "));
|
|
17487
|
+
|
|
17488
|
+
const {
|
|
17489
|
+
interactiveSizes
|
|
17490
|
+
} = constants;
|
|
17491
|
+
|
|
17492
|
+
// Note: these size cutoffs represent content-width cutoffs as specified in
|
|
17493
|
+
// http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
|
|
17494
|
+
// TODO(benkomalo): these values aren't used in JS outside of this file, but
|
|
17495
|
+
// are coupled to the values in styles/styles.less - DRY it up at some point
|
|
17496
|
+
const smMax = articleMaxWidthTableInPx;
|
|
17497
|
+
const mdMax = articleMaxWidthInPx;
|
|
17498
|
+
const containerSizeClass = {
|
|
17499
|
+
SMALL: "small",
|
|
17500
|
+
MEDIUM: "medium",
|
|
17501
|
+
LARGE: "large",
|
|
17502
|
+
XLARGE: "xlarge"
|
|
17503
|
+
};
|
|
17504
|
+
const getClassFromWidth = width => {
|
|
17505
|
+
if (!width) {
|
|
17506
|
+
return containerSizeClass.MEDIUM;
|
|
17435
17507
|
}
|
|
17436
|
-
if (
|
|
17437
|
-
return
|
|
17438
|
-
empty: true,
|
|
17439
|
-
correct: false,
|
|
17440
|
-
message: score.message,
|
|
17441
|
-
suppressAlmostThere: score.suppressAlmostThere,
|
|
17442
|
-
guess: guess,
|
|
17443
|
-
state: state
|
|
17444
|
-
};
|
|
17508
|
+
if (width <= smMax) {
|
|
17509
|
+
return containerSizeClass.SMALL;
|
|
17445
17510
|
}
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
}
|
|
17455
|
-
|
|
17511
|
+
if (width <= mdMax) {
|
|
17512
|
+
return containerSizeClass.MEDIUM;
|
|
17513
|
+
}
|
|
17514
|
+
return containerSizeClass.LARGE;
|
|
17515
|
+
};
|
|
17516
|
+
const getInteractiveBoxFromSizeClass = sizeClass => {
|
|
17517
|
+
if (sizeClass === containerSizeClass.SMALL) {
|
|
17518
|
+
return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
|
|
17519
|
+
}
|
|
17520
|
+
return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
|
|
17521
|
+
};
|
|
17456
17522
|
|
|
17457
|
-
const DEFAULT_ALIGNMENT$1 = "block";
|
|
17458
|
-
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
17459
|
-
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
17460
|
-
const DEFAULT_STATIC = false;
|
|
17461
17523
|
const DEFAULT_TRACKING = "";
|
|
17462
17524
|
const DEFAULT_LINTABLE = false;
|
|
17463
17525
|
const widgets = {};
|
|
@@ -17474,7 +17536,6 @@ const registerWidgets = widgets => {
|
|
|
17474
17536
|
widgets.forEach(widget => {
|
|
17475
17537
|
registerWidget(widget.name, widget);
|
|
17476
17538
|
});
|
|
17477
|
-
validateAlignments();
|
|
17478
17539
|
};
|
|
17479
17540
|
|
|
17480
17541
|
/**
|
|
@@ -17558,17 +17619,9 @@ const getWidgetExport = name => {
|
|
|
17558
17619
|
var _widgets$name3;
|
|
17559
17620
|
return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
|
|
17560
17621
|
};
|
|
17561
|
-
const getWidgetValidator = name => {
|
|
17562
|
-
var _widgets$name$validat, _widgets$name4;
|
|
17563
|
-
return (_widgets$name$validat = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.validator) != null ? _widgets$name$validat : null;
|
|
17564
|
-
};
|
|
17565
|
-
const getWidgetScorer = name => {
|
|
17566
|
-
var _widgets$name$scorer, _widgets$name5;
|
|
17567
|
-
return (_widgets$name$scorer = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.scorer) != null ? _widgets$name$scorer : null;
|
|
17568
|
-
};
|
|
17569
17622
|
const getPublicWidgetOptionsFunction = name => {
|
|
17570
|
-
var _widgets$name$getPubl, _widgets$
|
|
17571
|
-
return (_widgets$name$getPubl = (_widgets$
|
|
17623
|
+
var _widgets$name$getPubl, _widgets$name4;
|
|
17624
|
+
return (_widgets$name$getPubl = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
|
|
17572
17625
|
};
|
|
17573
17626
|
const getEditor = name => {
|
|
17574
17627
|
return _.has(editors, name) ? editors[name] : null;
|
|
@@ -17612,109 +17665,6 @@ const isAccessible = widgetInfo => {
|
|
|
17612
17665
|
const getAllWidgetTypes = () => {
|
|
17613
17666
|
return _.keys(widgets);
|
|
17614
17667
|
};
|
|
17615
|
-
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
17616
|
-
const type = oldWidgetInfo.type;
|
|
17617
|
-
// NOTE(jeremy): This looks like it could be replaced by fixing types so
|
|
17618
|
-
// that `type` is non-optional. But we're seeing this in Sentry today so I
|
|
17619
|
-
// suspect we have legacy data (potentially unpublished) and we should
|
|
17620
|
-
// figure that out before depending solely on types.
|
|
17621
|
-
if (!_.isString(type)) {
|
|
17622
|
-
throw new PerseusError("widget type must be a string, but was: " + type, Errors.Internal);
|
|
17623
|
-
}
|
|
17624
|
-
const widgetExports = widgets[type];
|
|
17625
|
-
if (widgetExports == null) {
|
|
17626
|
-
// If we have a widget that isn't registered, we can't upgrade it
|
|
17627
|
-
// TODO(aria): Figure out what the best thing to do here would be
|
|
17628
|
-
return oldWidgetInfo;
|
|
17629
|
-
}
|
|
17630
|
-
|
|
17631
|
-
// Unversioned widgets (pre-July 2014) are all implicitly 0.0
|
|
17632
|
-
const initialVersion = oldWidgetInfo.version || {
|
|
17633
|
-
major: 0,
|
|
17634
|
-
minor: 0
|
|
17635
|
-
};
|
|
17636
|
-
const latestVersion = widgetExports.version || {
|
|
17637
|
-
major: 0,
|
|
17638
|
-
minor: 0
|
|
17639
|
-
};
|
|
17640
|
-
|
|
17641
|
-
// If the widget version is later than what we understand (major
|
|
17642
|
-
// version is higher than latest, or major versions are equal and minor
|
|
17643
|
-
// version is higher than latest), don't perform any upgrades.
|
|
17644
|
-
if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
|
|
17645
|
-
return oldWidgetInfo;
|
|
17646
|
-
}
|
|
17647
|
-
|
|
17648
|
-
// We do a clone here so that it's safe to mutate the input parameter
|
|
17649
|
-
// in propUpgrades functions (which I will probably accidentally do at
|
|
17650
|
-
// some point, and we would like to not break when that happens).
|
|
17651
|
-
let newEditorProps = _.clone(oldWidgetInfo.options) || {};
|
|
17652
|
-
const upgradePropsMap = widgetExports.propUpgrades || {};
|
|
17653
|
-
|
|
17654
|
-
// Empty props usually mean a newly created widget by the editor,
|
|
17655
|
-
// and are always considerered up-to-date.
|
|
17656
|
-
// Mostly, we'd rather not run upgrade functions on props that are
|
|
17657
|
-
// not complete.
|
|
17658
|
-
if (_.keys(newEditorProps).length !== 0) {
|
|
17659
|
-
// We loop through all the versions after the current version of
|
|
17660
|
-
// the loaded widget, up to and including the latest version of the
|
|
17661
|
-
// loaded widget, and run the upgrade function to bring our loaded
|
|
17662
|
-
// widget's props up to that version.
|
|
17663
|
-
// There is a little subtlety here in that we call
|
|
17664
|
-
// upgradePropsMap[1] to upgrade *to* version 1,
|
|
17665
|
-
// (not from version 1).
|
|
17666
|
-
for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
|
|
17667
|
-
if (upgradePropsMap[String(nextVersion)]) {
|
|
17668
|
-
newEditorProps = upgradePropsMap[String(nextVersion)](newEditorProps);
|
|
17669
|
-
} else {
|
|
17670
|
-
// This is a Log.error because it is unlikely to be hit in
|
|
17671
|
-
// local testing, and a Log.error is slightly less scary in
|
|
17672
|
-
// prod than a `throw new Error`
|
|
17673
|
-
Log.error("No upgrade found for widget. Cannot render.", Errors.Internal, {
|
|
17674
|
-
loggedMetadata: {
|
|
17675
|
-
type,
|
|
17676
|
-
fromMajorVersion: nextVersion - 1,
|
|
17677
|
-
toMajorVersion: nextVersion
|
|
17678
|
-
}
|
|
17679
|
-
});
|
|
17680
|
-
// But try to keep going anyways (yolo!)
|
|
17681
|
-
// (Throwing an error here would just break the page
|
|
17682
|
-
// silently anyways, so that doesn't seem much better
|
|
17683
|
-
// than a halfhearted attempt to continue, however
|
|
17684
|
-
// shallow...)
|
|
17685
|
-
}
|
|
17686
|
-
}
|
|
17687
|
-
}
|
|
17688
|
-
|
|
17689
|
-
// Minor version upgrades (eg. new optional props) don't have
|
|
17690
|
-
// transform functions. Instead, we fill in the new props with their
|
|
17691
|
-
// defaults.
|
|
17692
|
-
const defaultProps = type in editors ? editors[type].defaultProps : {};
|
|
17693
|
-
newEditorProps = _extends({}, defaultProps, newEditorProps);
|
|
17694
|
-
let alignment = oldWidgetInfo.alignment;
|
|
17695
|
-
|
|
17696
|
-
// Widgets that support multiple alignments will "lock in" the
|
|
17697
|
-
// alignment to the alignment that would be listed first in the
|
|
17698
|
-
// select box. If the widget only supports one alignment, the
|
|
17699
|
-
// alignment value will likely just end up as "default".
|
|
17700
|
-
if (alignment == null || alignment === "default") {
|
|
17701
|
-
alignment = getSupportedAlignments(type)[0];
|
|
17702
|
-
}
|
|
17703
|
-
let widgetStatic = oldWidgetInfo.static;
|
|
17704
|
-
if (widgetStatic == null) {
|
|
17705
|
-
widgetStatic = DEFAULT_STATIC;
|
|
17706
|
-
}
|
|
17707
|
-
return _.extend({}, oldWidgetInfo, {
|
|
17708
|
-
// maintain other info, like type
|
|
17709
|
-
// After upgrading we guarantee that the version is up-to-date
|
|
17710
|
-
version: latestVersion,
|
|
17711
|
-
// Default graded to true (so null/undefined becomes true):
|
|
17712
|
-
graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
|
|
17713
|
-
alignment: alignment,
|
|
17714
|
-
static: widgetStatic,
|
|
17715
|
-
options: newEditorProps
|
|
17716
|
-
});
|
|
17717
|
-
};
|
|
17718
17668
|
const getRendererPropsForWidgetInfo = (widgetInfo, strings, problemNum) => {
|
|
17719
17669
|
const type = widgetInfo.type;
|
|
17720
17670
|
const widgetExports = widgets[type];
|
|
@@ -17756,70 +17706,6 @@ const traverseChildWidgets$3 = (widgetInfo, traverseRenderer) => {
|
|
|
17756
17706
|
return widgetInfo;
|
|
17757
17707
|
};
|
|
17758
17708
|
|
|
17759
|
-
/**
|
|
17760
|
-
* Handling for the optional alignments for widgets
|
|
17761
|
-
* See widget-container.jsx for details on how alignments are implemented.
|
|
17762
|
-
*/
|
|
17763
|
-
|
|
17764
|
-
/**
|
|
17765
|
-
* Returns the list of supported alignments for the given (string) widget
|
|
17766
|
-
* type. This is used primarily at editing time to display the choices
|
|
17767
|
-
* for the user.
|
|
17768
|
-
*
|
|
17769
|
-
* Supported alignments are given as an array of strings in the exports of
|
|
17770
|
-
* a widget's module.
|
|
17771
|
-
*/
|
|
17772
|
-
const getSupportedAlignments = type => {
|
|
17773
|
-
const widgetExport = widgets[type];
|
|
17774
|
-
// @ts-expect-error - TS2322 - Type 'string[] | readonly Alignment[]' is not assignable to type 'readonly Alignment[]'.
|
|
17775
|
-
return widgetExport && widgetExport.supportedAlignments || DEFAULT_SUPPORTED_ALIGNMENTS;
|
|
17776
|
-
};
|
|
17777
|
-
|
|
17778
|
-
/**
|
|
17779
|
-
* For the given (string) widget type, determine the default alignment for
|
|
17780
|
-
* the widget. This is used at rendering time to go from "default" alignment
|
|
17781
|
-
* to the actual alignment displayed on the screen.
|
|
17782
|
-
*
|
|
17783
|
-
* The default alignment is given either as a string (called
|
|
17784
|
-
* `defaultAlignment`) or a function (called `getDefaultAlignment`) on
|
|
17785
|
-
* the exports of a widget's module.
|
|
17786
|
-
*/
|
|
17787
|
-
const getDefaultAlignment = type => {
|
|
17788
|
-
const widgetExports = widgets[type];
|
|
17789
|
-
let alignment;
|
|
17790
|
-
if (!widgetExports) {
|
|
17791
|
-
return DEFAULT_ALIGNMENT$1;
|
|
17792
|
-
}
|
|
17793
|
-
if (widgetExports.getDefaultAlignment) {
|
|
17794
|
-
alignment = widgetExports.getDefaultAlignment();
|
|
17795
|
-
} else {
|
|
17796
|
-
alignment = widgetExports.defaultAlignment;
|
|
17797
|
-
}
|
|
17798
|
-
return alignment || DEFAULT_ALIGNMENT$1;
|
|
17799
|
-
};
|
|
17800
|
-
const validAlignments = ["block", "inline-block", "inline", "float-left", "float-right", "full-width"];
|
|
17801
|
-
|
|
17802
|
-
/**
|
|
17803
|
-
* Used at startup to fail fast if an alignment given by a widget is
|
|
17804
|
-
* invalid.
|
|
17805
|
-
*/
|
|
17806
|
-
// TODO(alex): Change this to run as a testcase (vs. being run at runtime)
|
|
17807
|
-
// TODO(LP-10707): I think this can be completely removed because our TypeScript types
|
|
17808
|
-
// enforce this!
|
|
17809
|
-
const validateAlignments = () => {
|
|
17810
|
-
_.each(widgets, function (widgetInfo) {
|
|
17811
|
-
if (widgetInfo.defaultAlignment && !_.contains(validAlignments, widgetInfo.defaultAlignment)) {
|
|
17812
|
-
throw new PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid defaultAlignment value: " + widgetInfo.defaultAlignment, Errors.InvalidInput);
|
|
17813
|
-
}
|
|
17814
|
-
if (widgetInfo.supportedAlignments) {
|
|
17815
|
-
const unknownAlignments = _.difference(widgetInfo.supportedAlignments, validAlignments);
|
|
17816
|
-
if (unknownAlignments.length) {
|
|
17817
|
-
throw new PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid value for supportedAlignments: " + unknownAlignments.join(" "), Errors.InvalidInput);
|
|
17818
|
-
}
|
|
17819
|
-
}
|
|
17820
|
-
});
|
|
17821
|
-
};
|
|
17822
|
-
|
|
17823
17709
|
/**
|
|
17824
17710
|
* Handling for static mode for widgets that support it.
|
|
17825
17711
|
*/
|
|
@@ -17873,8 +17759,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
17873
17759
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
17874
17760
|
getWidget: getWidget,
|
|
17875
17761
|
getWidgetExport: getWidgetExport,
|
|
17876
|
-
getWidgetValidator: getWidgetValidator,
|
|
17877
|
-
getWidgetScorer: getWidgetScorer,
|
|
17878
17762
|
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
17879
17763
|
getEditor: getEditor,
|
|
17880
17764
|
getTransform: getTransform,
|
|
@@ -17883,265 +17767,14 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
17883
17767
|
getPublicWidgets: getPublicWidgets,
|
|
17884
17768
|
isAccessible: isAccessible,
|
|
17885
17769
|
getAllWidgetTypes: getAllWidgetTypes,
|
|
17886
|
-
upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
|
|
17887
17770
|
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
17888
17771
|
traverseChildWidgets: traverseChildWidgets$3,
|
|
17889
|
-
getSupportedAlignments: getSupportedAlignments,
|
|
17890
|
-
getDefaultAlignment: getDefaultAlignment,
|
|
17891
|
-
validateAlignments: validateAlignments,
|
|
17892
17772
|
supportsStaticMode: supportsStaticMode,
|
|
17893
17773
|
getStaticTransform: getStaticTransform,
|
|
17894
17774
|
getTracking: getTracking,
|
|
17895
17775
|
isLintable: isLintable
|
|
17896
17776
|
});
|
|
17897
17777
|
|
|
17898
|
-
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
17899
|
-
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
17900
|
-
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
17901
|
-
const newValues = {};
|
|
17902
|
-
if (!widgetInfo.type) {
|
|
17903
|
-
// TODO: why does widget have no type?
|
|
17904
|
-
// We don't want to derive type from widget ID
|
|
17905
|
-
// see: LEMS-1845
|
|
17906
|
-
newValues.type = widgetId.split(" ")[0];
|
|
17907
|
-
}
|
|
17908
|
-
if (!widgetInfo.alignment) {
|
|
17909
|
-
newValues.alignment = "default";
|
|
17910
|
-
}
|
|
17911
|
-
widgetInfo = _extends({}, widgetInfo, newValues);
|
|
17912
|
-
}
|
|
17913
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
17914
|
-
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
17915
|
-
});
|
|
17916
|
-
}
|
|
17917
|
-
|
|
17918
|
-
/**
|
|
17919
|
-
* Checks the given user input to see if any answerable widgets have not been
|
|
17920
|
-
* "filled in" (ie. if they're empty). Another way to think about this
|
|
17921
|
-
* function is that its a check to see if we can score the provided input.
|
|
17922
|
-
*/
|
|
17923
|
-
function emptyWidgetsFunctional(widgets,
|
|
17924
|
-
// This is a port of old code, I'm not sure why
|
|
17925
|
-
// we need widgetIds vs the keys of the widgets object
|
|
17926
|
-
widgetIds, userInputMap, strings, locale) {
|
|
17927
|
-
return widgetIds.filter(id => {
|
|
17928
|
-
const widget = widgets[id];
|
|
17929
|
-
if (!widget || widget.static === true) {
|
|
17930
|
-
// Static widgets shouldn't count as empty
|
|
17931
|
-
return false;
|
|
17932
|
-
}
|
|
17933
|
-
const validator = getWidgetValidator(widget.type);
|
|
17934
|
-
const userInput = userInputMap[id];
|
|
17935
|
-
const validationData = widget.options;
|
|
17936
|
-
const score = validator == null ? void 0 : validator(userInput, validationData, strings, locale);
|
|
17937
|
-
if (score) {
|
|
17938
|
-
return scoreIsEmpty(score);
|
|
17939
|
-
}
|
|
17940
|
-
});
|
|
17941
|
-
}
|
|
17942
|
-
|
|
17943
|
-
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
17944
|
-
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
17945
|
-
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
17946
|
-
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
17947
|
-
// need to be removed before we move this to the server
|
|
17948
|
-
strings, locale) {
|
|
17949
|
-
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
17950
|
-
// widget data for widgets that are not in PerseusRenderer.content,
|
|
17951
|
-
// so this checks that the widgets are being used before scoring them
|
|
17952
|
-
const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
|
|
17953
|
-
const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
|
|
17954
|
-
return flattenScores(scores);
|
|
17955
|
-
}
|
|
17956
|
-
function scoreWidgetsFunctional(widgets,
|
|
17957
|
-
// This is a port of old code, I'm not sure why
|
|
17958
|
-
// we need widgetIds vs the keys of the widgets object
|
|
17959
|
-
widgetIds, userInputMap, strings, locale) {
|
|
17960
|
-
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
17961
|
-
const gradedWidgetIds = widgetIds.filter(id => {
|
|
17962
|
-
const props = upgradedWidgets[id];
|
|
17963
|
-
const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
|
|
17964
|
-
const widgetIsStatic = !!(props != null && props.static);
|
|
17965
|
-
// Ungraded widgets or widgets set to static shouldn't be graded.
|
|
17966
|
-
return widgetIsGraded && !widgetIsStatic;
|
|
17967
|
-
});
|
|
17968
|
-
const widgetScores = {};
|
|
17969
|
-
gradedWidgetIds.forEach(id => {
|
|
17970
|
-
var _validator;
|
|
17971
|
-
const widget = upgradedWidgets[id];
|
|
17972
|
-
if (!widget) {
|
|
17973
|
-
return;
|
|
17974
|
-
}
|
|
17975
|
-
const userInput = userInputMap[id];
|
|
17976
|
-
const validator = getWidgetValidator(widget.type);
|
|
17977
|
-
const scorer = getWidgetScorer(widget.type);
|
|
17978
|
-
|
|
17979
|
-
// We do validation (empty checks) first and then scoring. If
|
|
17980
|
-
// validation fails, it's result is itself a PerseusScore.
|
|
17981
|
-
const score = (_validator = validator == null ? void 0 : validator(userInput, widget.options, strings, locale)) != null ? _validator : scorer == null ? void 0 : scorer(userInput, widget.options, strings, locale);
|
|
17982
|
-
if (score != null) {
|
|
17983
|
-
widgetScores[id] = score;
|
|
17984
|
-
}
|
|
17985
|
-
});
|
|
17986
|
-
return widgetScores;
|
|
17987
|
-
}
|
|
17988
|
-
|
|
17989
|
-
/**
|
|
17990
|
-
* TranslationLinter asynchronously calls Khan Academy's poentry linter.
|
|
17991
|
-
*
|
|
17992
|
-
* The poentry linter checks for issues that prevent content from being
|
|
17993
|
-
* translated. This linter is KA-specific and so TranslationLinter does not do
|
|
17994
|
-
* anything in non-KA environments.
|
|
17995
|
-
*/
|
|
17996
|
-
|
|
17997
|
-
// A LintCB is called once the linter has been run.
|
|
17998
|
-
|
|
17999
|
-
// msec to wait before we actually call the linter after receiving the
|
|
18000
|
-
// last call. The timer is reset if the lint is called during the wait.
|
|
18001
|
-
const DEBOUNCE_TIMEOUT = 1000;
|
|
18002
|
-
class TranslationLinter {
|
|
18003
|
-
constructor() {
|
|
18004
|
-
// To avoid linting when nothing has changed.
|
|
18005
|
-
this.previousContent = null;
|
|
18006
|
-
/**
|
|
18007
|
-
* Runs the poentry linter on the given perseus string asynchronously.
|
|
18008
|
-
*
|
|
18009
|
-
* It doesn't do anything unless we're in a KA environment.
|
|
18010
|
-
*
|
|
18011
|
-
* onLintErrorsGenerated is called even if there are no errors. It is not
|
|
18012
|
-
* called if perseusStr is unchanged from the last call.
|
|
18013
|
-
*
|
|
18014
|
-
* Linting is relatively expensive -- so we debounce linting each instance
|
|
18015
|
-
* for some amount of time.
|
|
18016
|
-
*/
|
|
18017
|
-
this.runLinter = debounce$1((perseusStr, onLintErrorsGenerated) => {
|
|
18018
|
-
// TODO(FEI-5003): Do we still need to check for 'KA'?
|
|
18019
|
-
if (typeof KA === "undefined") {
|
|
18020
|
-
return;
|
|
18021
|
-
}
|
|
18022
|
-
if (perseusStr === this.previousContent) {
|
|
18023
|
-
return;
|
|
18024
|
-
}
|
|
18025
|
-
this.previousContent = perseusStr;
|
|
18026
|
-
if (perseusStr === "") {
|
|
18027
|
-
onLintErrorsGenerated([]);
|
|
18028
|
-
return;
|
|
18029
|
-
}
|
|
18030
|
-
fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
|
|
18031
|
-
headers: {
|
|
18032
|
-
"Content-Type": "application/json"
|
|
18033
|
-
},
|
|
18034
|
-
body: JSON.stringify({
|
|
18035
|
-
// The poentry linter verifies that the translation from
|
|
18036
|
-
// the original text to the translated text is sane. We
|
|
18037
|
-
// want to ensure that the translation from English to
|
|
18038
|
-
// English is sane, so msgid === mstr.
|
|
18039
|
-
msgid: perseusStr,
|
|
18040
|
-
// msgid is the original text
|
|
18041
|
-
msgstr: perseusStr,
|
|
18042
|
-
// msgstr is the translated text
|
|
18043
|
-
format: "perseus_text",
|
|
18044
|
-
filename: ""
|
|
18045
|
-
}),
|
|
18046
|
-
method: "POST"
|
|
18047
|
-
}).then(response => {
|
|
18048
|
-
if (response.status >= 400) {
|
|
18049
|
-
return {
|
|
18050
|
-
status: "error",
|
|
18051
|
-
message: "Could not run i18n linter."
|
|
18052
|
-
};
|
|
18053
|
-
}
|
|
18054
|
-
return response.json();
|
|
18055
|
-
}, rejection => {
|
|
18056
|
-
return {
|
|
18057
|
-
status: "error",
|
|
18058
|
-
message: "Could not run i18n linter."
|
|
18059
|
-
};
|
|
18060
|
-
}).then(json => {
|
|
18061
|
-
if (json.status === "error") {
|
|
18062
|
-
onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
|
|
18063
|
-
} else {
|
|
18064
|
-
onLintErrorsGenerated([]);
|
|
18065
|
-
}
|
|
18066
|
-
});
|
|
18067
|
-
}, DEBOUNCE_TIMEOUT);
|
|
18068
|
-
}
|
|
18069
|
-
/**
|
|
18070
|
-
* Applies an array of errors generated by linters without position
|
|
18071
|
-
* information (TranslationLinter and the legacy getSaveWarnings()) to the
|
|
18072
|
-
* top of a Perseus tree.
|
|
18073
|
-
*/
|
|
18074
|
-
applyLintErrors(parsedMarkdown, translationLintErrors) {
|
|
18075
|
-
// These lint errors do not have position data associated with
|
|
18076
|
-
// them, so we just plop them at the top.
|
|
18077
|
-
if (translationLintErrors.length) {
|
|
18078
|
-
const errorText = translationLintErrors.join("\n\n");
|
|
18079
|
-
parsedMarkdown.unshift({
|
|
18080
|
-
content: {
|
|
18081
|
-
type: "text",
|
|
18082
|
-
content: ""
|
|
18083
|
-
},
|
|
18084
|
-
insideTable: false,
|
|
18085
|
-
message: errorText,
|
|
18086
|
-
ruleName: "legacy-error",
|
|
18087
|
-
severity: Rule.Severity.ERROR,
|
|
18088
|
-
type: "lint"
|
|
18089
|
-
});
|
|
18090
|
-
}
|
|
18091
|
-
}
|
|
18092
|
-
}
|
|
18093
|
-
|
|
18094
|
-
/**
|
|
18095
|
-
* Preprocess TeX code to convert things that MathJax doesn't know how to handle
|
|
18096
|
-
* to things is does.
|
|
18097
|
-
*/
|
|
18098
|
-
var preprocessTex = (texCode => texCode
|
|
18099
|
-
// Replace uses of \begin{align}...\end{align} which KaTeX doesn't
|
|
18100
|
-
// support (yet) with \begin{aligned}...\end{aligned} which renders
|
|
18101
|
-
// the same is supported by KaTeX. It does the same for align*.
|
|
18102
|
-
// TODO(kevinb) update content to use aligned instead of align.
|
|
18103
|
-
// TODO(LEMS-1608) Remove this replacement as MathJax supports the
|
|
18104
|
-
// "align" macro correctly (and, in fact, it is not synonymous with
|
|
18105
|
-
// "aligned").
|
|
18106
|
-
.replace(/\{align[*]?\}/g, "{aligned}")
|
|
18107
|
-
// Replace non-breaking spaces with regular spaces.
|
|
18108
|
-
.replace(/[\u00a0]/g, " "));
|
|
18109
|
-
|
|
18110
|
-
const {
|
|
18111
|
-
interactiveSizes
|
|
18112
|
-
} = constants;
|
|
18113
|
-
|
|
18114
|
-
// Note: these size cutoffs represent content-width cutoffs as specified in
|
|
18115
|
-
// http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
|
|
18116
|
-
// TODO(benkomalo): these values aren't used in JS outside of this file, but
|
|
18117
|
-
// are coupled to the values in styles/styles.less - DRY it up at some point
|
|
18118
|
-
const smMax = articleMaxWidthTableInPx;
|
|
18119
|
-
const mdMax = articleMaxWidthInPx;
|
|
18120
|
-
const containerSizeClass = {
|
|
18121
|
-
SMALL: "small",
|
|
18122
|
-
MEDIUM: "medium",
|
|
18123
|
-
LARGE: "large",
|
|
18124
|
-
XLARGE: "xlarge"
|
|
18125
|
-
};
|
|
18126
|
-
const getClassFromWidth = width => {
|
|
18127
|
-
if (!width) {
|
|
18128
|
-
return containerSizeClass.MEDIUM;
|
|
18129
|
-
}
|
|
18130
|
-
if (width <= smMax) {
|
|
18131
|
-
return containerSizeClass.SMALL;
|
|
18132
|
-
}
|
|
18133
|
-
if (width <= mdMax) {
|
|
18134
|
-
return containerSizeClass.MEDIUM;
|
|
18135
|
-
}
|
|
18136
|
-
return containerSizeClass.LARGE;
|
|
18137
|
-
};
|
|
18138
|
-
const getInteractiveBoxFromSizeClass = sizeClass => {
|
|
18139
|
-
if (sizeClass === containerSizeClass.SMALL) {
|
|
18140
|
-
return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
|
|
18141
|
-
}
|
|
18142
|
-
return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
|
|
18143
|
-
};
|
|
18144
|
-
|
|
18145
17778
|
class WidgetContainer extends React.Component {
|
|
18146
17779
|
constructor(...args) {
|
|
18147
17780
|
super(...args);
|
|
@@ -18214,7 +17847,7 @@ class WidgetContainer extends React.Component {
|
|
|
18214
17847
|
}
|
|
18215
17848
|
let alignment = this.state.widgetProps.alignment;
|
|
18216
17849
|
if (alignment === "default") {
|
|
18217
|
-
alignment = getDefaultAlignment(type);
|
|
17850
|
+
alignment = CoreWidgetRegistry.getDefaultAlignment(type);
|
|
18218
17851
|
}
|
|
18219
17852
|
className += " widget-" + alignment;
|
|
18220
17853
|
const apiOptions = this.state.widgetProps.apiOptions;
|
|
@@ -18222,6 +17855,7 @@ class WidgetContainer extends React.Component {
|
|
|
18222
17855
|
// Hack to prevent interaction with static widgets: we overlay a big
|
|
18223
17856
|
// div on top of the widget and overflow: hidden the container.
|
|
18224
17857
|
// Ideally widgets themselves should know how to prevent interaction.
|
|
17858
|
+
// UPDATE HTML5: `inert` on the underlying div would be better
|
|
18225
17859
|
const isStatic = this.state.widgetProps.static || apiOptions.readOnly;
|
|
18226
17860
|
const staticContainerStyles = {
|
|
18227
17861
|
position: "relative",
|
|
@@ -18233,7 +17867,7 @@ class WidgetContainer extends React.Component {
|
|
|
18233
17867
|
position: "absolute",
|
|
18234
17868
|
top: 0,
|
|
18235
17869
|
left: 0,
|
|
18236
|
-
zIndex:
|
|
17870
|
+
zIndex: 3
|
|
18237
17871
|
};
|
|
18238
17872
|
|
|
18239
17873
|
// Some widgets may include strings of markdown that we may
|
|
@@ -19417,7 +19051,7 @@ class Renderer extends React.Component {
|
|
|
19417
19051
|
* empty if the graph is in the starting state.
|
|
19418
19052
|
*/
|
|
19419
19053
|
emptyWidgets() {
|
|
19420
|
-
return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.
|
|
19054
|
+
return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.context.locale);
|
|
19421
19055
|
}
|
|
19422
19056
|
/**
|
|
19423
19057
|
* Returns an array of the widget `.getUserInput()` results
|
|
@@ -19477,7 +19111,7 @@ class Renderer extends React.Component {
|
|
|
19477
19111
|
* @deprecated use scorePerseusItem
|
|
19478
19112
|
*/
|
|
19479
19113
|
score() {
|
|
19480
|
-
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.
|
|
19114
|
+
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.context.locale);
|
|
19481
19115
|
const combinedScore = flattenScores(scores);
|
|
19482
19116
|
return combinedScore;
|
|
19483
19117
|
}
|
|
@@ -19703,7 +19337,7 @@ function _objectWithoutPropertiesLoose(r, e) {
|
|
|
19703
19337
|
if (null == r) return {};
|
|
19704
19338
|
var t = {};
|
|
19705
19339
|
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
|
|
19706
|
-
if (e.
|
|
19340
|
+
if (-1 !== e.indexOf(n)) continue;
|
|
19707
19341
|
t[n] = r[n];
|
|
19708
19342
|
}
|
|
19709
19343
|
return t;
|
|
@@ -20017,7 +19651,7 @@ class InputWithExamples extends React.Component {
|
|
|
20017
19651
|
};
|
|
20018
19652
|
this._getInputClassName = () => {
|
|
20019
19653
|
// Otherwise, we need to add these INPUT and FOCUSED tags here.
|
|
20020
|
-
let className = ClassNames.INPUT
|
|
19654
|
+
let className = ClassNames.INPUT;
|
|
20021
19655
|
if (this.state.focused) {
|
|
20022
19656
|
className += " " + ClassNames.FOCUSED;
|
|
20023
19657
|
}
|
|
@@ -20429,14 +20063,10 @@ const propTransform$3 = widgetOptions => {
|
|
|
20429
20063
|
var InputNumber$1 = {
|
|
20430
20064
|
name: "input-number",
|
|
20431
20065
|
displayName: "Input number (deprecated - use numeric input instead)",
|
|
20432
|
-
defaultAlignment: "inline-block",
|
|
20433
20066
|
hidden: true,
|
|
20434
20067
|
widget: InputNumber,
|
|
20435
20068
|
transform: propTransform$3,
|
|
20436
20069
|
isLintable: true,
|
|
20437
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
20438
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInputNumberUserInput'.
|
|
20439
|
-
scorer: scoreInputNumber,
|
|
20440
20070
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
20441
20071
|
if (rubric.value == null) {
|
|
20442
20072
|
return;
|
|
@@ -20693,14 +20323,11 @@ const propsTransform = function propsTransform(widgetOptions) {
|
|
|
20693
20323
|
var NumericInput$1 = {
|
|
20694
20324
|
name: "numeric-input",
|
|
20695
20325
|
displayName: "Numeric input",
|
|
20696
|
-
defaultAlignment: "inline-block",
|
|
20697
20326
|
accessible: true,
|
|
20698
20327
|
widget: NumericInput,
|
|
20699
20328
|
transform: propsTransform,
|
|
20700
20329
|
isLintable: true,
|
|
20701
|
-
|
|
20702
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumericInputUserInput'.
|
|
20703
|
-
scorer: scoreNumericInput,
|
|
20330
|
+
getPublicWidgetOptions: getNumericInputPublicWidgetOptions,
|
|
20704
20331
|
// TODO(LEMS-2656): remove TS suppression
|
|
20705
20332
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
|
|
20706
20333
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
@@ -20761,23 +20388,6 @@ const getPromptJSON$p = renderProps => {
|
|
|
20761
20388
|
};
|
|
20762
20389
|
};
|
|
20763
20390
|
|
|
20764
|
-
/**
|
|
20765
|
-
* Several widgets don't have "right"/"wrong" scoring logic,
|
|
20766
|
-
* so this just says to move on past those widgets
|
|
20767
|
-
*
|
|
20768
|
-
* TODO(LEMS-2543) widgets that use this probably shouldn't have any
|
|
20769
|
-
* scoring logic and the thing scoring an exercise
|
|
20770
|
-
* should just know to skip these
|
|
20771
|
-
*/
|
|
20772
|
-
function scoreNoop(points = 0) {
|
|
20773
|
-
return {
|
|
20774
|
-
type: "points",
|
|
20775
|
-
earned: points,
|
|
20776
|
-
total: points,
|
|
20777
|
-
message: null
|
|
20778
|
-
};
|
|
20779
|
-
}
|
|
20780
|
-
|
|
20781
20391
|
/**
|
|
20782
20392
|
* Utility functions for manipulating ranges of highlightable content.
|
|
20783
20393
|
*/
|
|
@@ -22953,8 +22563,7 @@ var Passage$1 = {
|
|
|
22953
22563
|
transform: editorProps => {
|
|
22954
22564
|
return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
22955
22565
|
},
|
|
22956
|
-
isLintable: true
|
|
22957
|
-
scorer: () => scoreNoop()
|
|
22566
|
+
isLintable: true
|
|
22958
22567
|
};
|
|
22959
22568
|
|
|
22960
22569
|
/**
|
|
@@ -23076,16 +22685,13 @@ var PassageRef$1 = {
|
|
|
23076
22685
|
name: "passage-ref",
|
|
23077
22686
|
displayName: "PassageRef (SAT only)",
|
|
23078
22687
|
hidden: true,
|
|
23079
|
-
defaultAlignment: "inline",
|
|
23080
22688
|
widget: PassageRef,
|
|
23081
22689
|
transform: widgetOptions => ({
|
|
23082
22690
|
passageNumber: widgetOptions.passageNumber,
|
|
23083
22691
|
referenceNumber: widgetOptions.referenceNumber,
|
|
23084
22692
|
summaryText: widgetOptions.summaryText
|
|
23085
22693
|
}),
|
|
23086
|
-
version: passageRefLogic.version
|
|
23087
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
23088
|
-
scorer: () => scoreNoop()
|
|
22694
|
+
version: passageRefLogic.version
|
|
23089
22695
|
};
|
|
23090
22696
|
|
|
23091
22697
|
/**
|
|
@@ -23127,109 +22733,6 @@ var mediaQueries = {
|
|
|
23127
22733
|
xlOrLarger: `@media screen and (min-width: ${pureXlMin})`
|
|
23128
22734
|
};
|
|
23129
22735
|
|
|
23130
|
-
const {
|
|
23131
|
-
zIndexAboveScratchpad,
|
|
23132
|
-
zIndexInteractiveComponent,
|
|
23133
|
-
radioBorderColor,
|
|
23134
|
-
checkedColor,
|
|
23135
|
-
circleSize,
|
|
23136
|
-
radioMarginWidth
|
|
23137
|
-
} = constants;
|
|
23138
|
-
var sharedStyles = StyleSheet.create({
|
|
23139
|
-
perseusInteractive: {
|
|
23140
|
-
zIndex: zIndexInteractiveComponent,
|
|
23141
|
-
position: "relative"
|
|
23142
|
-
},
|
|
23143
|
-
aboveScratchpad: {
|
|
23144
|
-
position: "relative",
|
|
23145
|
-
zIndex: zIndexAboveScratchpad
|
|
23146
|
-
},
|
|
23147
|
-
blankBackground: {
|
|
23148
|
-
// TODO(emily): Use WB colors?
|
|
23149
|
-
backgroundColor: "#FDFDFD"
|
|
23150
|
-
},
|
|
23151
|
-
perseusSrOnly: {
|
|
23152
|
-
border: 0,
|
|
23153
|
-
clip: "rect(0,0,0,0)",
|
|
23154
|
-
height: 1,
|
|
23155
|
-
margin: -1,
|
|
23156
|
-
overflow: "hidden",
|
|
23157
|
-
padding: 0,
|
|
23158
|
-
position: "absolute",
|
|
23159
|
-
width: 1
|
|
23160
|
-
},
|
|
23161
|
-
responsiveLabel: {
|
|
23162
|
-
// NOTE(charlie): The values used here should be kept in sync with the
|
|
23163
|
-
// caption text sizing in Perseus' styles.less.
|
|
23164
|
-
// TODO(charlie): Migrate the captions over to using this style.
|
|
23165
|
-
[mediaQueries.smOrSmaller]: {
|
|
23166
|
-
fontSize: 14,
|
|
23167
|
-
lineHeight: 1.3
|
|
23168
|
-
},
|
|
23169
|
-
[mediaQueries.md]: {
|
|
23170
|
-
fontSize: 17,
|
|
23171
|
-
lineHeight: 1.4
|
|
23172
|
-
},
|
|
23173
|
-
[mediaQueries.lgOrLarger]: {
|
|
23174
|
-
fontSize: 20,
|
|
23175
|
-
lineHeight: 1.4
|
|
23176
|
-
}
|
|
23177
|
-
},
|
|
23178
|
-
responsiveInput: {
|
|
23179
|
-
display: "inline-block",
|
|
23180
|
-
WebkitAppearance: "none",
|
|
23181
|
-
appearance: "none",
|
|
23182
|
-
"::-ms-check": {
|
|
23183
|
-
display: "none"
|
|
23184
|
-
},
|
|
23185
|
-
backgroundColor: "#fff",
|
|
23186
|
-
border: "2px solid #fff",
|
|
23187
|
-
boxShadow: `0 0px 0px 1px ${radioBorderColor}`,
|
|
23188
|
-
outline: "none",
|
|
23189
|
-
boxSizing: "border-box",
|
|
23190
|
-
flexShrink: 0,
|
|
23191
|
-
marginBottom: 1,
|
|
23192
|
-
marginLeft: 1,
|
|
23193
|
-
marginRight: 1,
|
|
23194
|
-
marginTop: 1,
|
|
23195
|
-
height: circleSize - 2,
|
|
23196
|
-
width: circleSize - 2
|
|
23197
|
-
},
|
|
23198
|
-
responsiveRadioInput: {
|
|
23199
|
-
borderRadius: "50%",
|
|
23200
|
-
":checked": {
|
|
23201
|
-
backgroundColor: checkedColor,
|
|
23202
|
-
border: "none",
|
|
23203
|
-
borderRadius: "50%",
|
|
23204
|
-
boxShadow: `inset 0px 0px 0px 2px white, ` + `0 0px 0px 2px ${checkedColor}`,
|
|
23205
|
-
marginTop: radioMarginWidth,
|
|
23206
|
-
marginBottom: radioMarginWidth,
|
|
23207
|
-
marginLeft: radioMarginWidth,
|
|
23208
|
-
marginRight: radioMarginWidth,
|
|
23209
|
-
height: circleSize - 2 * radioMarginWidth,
|
|
23210
|
-
width: circleSize - 2 * radioMarginWidth
|
|
23211
|
-
}
|
|
23212
|
-
},
|
|
23213
|
-
responsiveRadioInputActive: {
|
|
23214
|
-
backgroundColor: "#fff",
|
|
23215
|
-
border: "2px solid #fff",
|
|
23216
|
-
borderRadius: "50%",
|
|
23217
|
-
boxShadow: `0 0px 0px 2px ${checkedColor}`,
|
|
23218
|
-
marginTop: radioMarginWidth,
|
|
23219
|
-
marginBottom: radioMarginWidth,
|
|
23220
|
-
marginLeft: radioMarginWidth,
|
|
23221
|
-
marginRight: radioMarginWidth,
|
|
23222
|
-
height: circleSize - 2 * radioMarginWidth,
|
|
23223
|
-
width: circleSize - 2 * radioMarginWidth,
|
|
23224
|
-
":checked": {
|
|
23225
|
-
backgroundColor: "#fff"
|
|
23226
|
-
}
|
|
23227
|
-
},
|
|
23228
|
-
disableTextSelection: {
|
|
23229
|
-
userSelect: "none"
|
|
23230
|
-
}
|
|
23231
|
-
});
|
|
23232
|
-
|
|
23233
22736
|
/**
|
|
23234
22737
|
* A small collection of useful scrolling utility functions.
|
|
23235
22738
|
*/
|
|
@@ -23603,8 +23106,8 @@ const styles$s = StyleSheet.create({
|
|
|
23603
23106
|
crossOutLine: {
|
|
23604
23107
|
// Center the icon within the container.
|
|
23605
23108
|
position: "absolute",
|
|
23606
|
-
top: `
|
|
23607
|
-
left: `
|
|
23109
|
+
top: `2px`,
|
|
23110
|
+
left: `2px`
|
|
23608
23111
|
}
|
|
23609
23112
|
});
|
|
23610
23113
|
|
|
@@ -23631,43 +23134,6 @@ function ChoiceInner(props) {
|
|
|
23631
23134
|
}
|
|
23632
23135
|
return /*#__PURE__*/React.createElement(InlineIcon, iconMinus);
|
|
23633
23136
|
}
|
|
23634
|
-
|
|
23635
|
-
// Handle dynamic styling of the multiple choice icon. Most
|
|
23636
|
-
// MC icon styles are constant, but we do allow the caller
|
|
23637
|
-
// to specify the selected color, and thus must control styles
|
|
23638
|
-
// related to the selected state dynamically.
|
|
23639
|
-
function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct) {
|
|
23640
|
-
let backgroundColor;
|
|
23641
|
-
let borderColor;
|
|
23642
|
-
let color$1;
|
|
23643
|
-
if (!showCorrectness && pressed) {
|
|
23644
|
-
borderColor = color.blue;
|
|
23645
|
-
color$1 = color.blue;
|
|
23646
|
-
backgroundColor = "transparent";
|
|
23647
|
-
} else if (checked) {
|
|
23648
|
-
const bg = showCorrectness && correct ? color.green : color.blue;
|
|
23649
|
-
color$1 = white;
|
|
23650
|
-
backgroundColor = bg;
|
|
23651
|
-
borderColor = bg;
|
|
23652
|
-
} else {
|
|
23653
|
-
borderColor = color.offBlack64;
|
|
23654
|
-
color$1 = color.offBlack64;
|
|
23655
|
-
}
|
|
23656
|
-
|
|
23657
|
-
// define shape
|
|
23658
|
-
let borderRadius;
|
|
23659
|
-
if (multipleSelect) {
|
|
23660
|
-
borderRadius = 3;
|
|
23661
|
-
} else {
|
|
23662
|
-
borderRadius = CHOICE_ICON_SIZE;
|
|
23663
|
-
}
|
|
23664
|
-
return {
|
|
23665
|
-
backgroundColor,
|
|
23666
|
-
borderColor,
|
|
23667
|
-
color: color$1,
|
|
23668
|
-
borderRadius
|
|
23669
|
-
};
|
|
23670
|
-
}
|
|
23671
23137
|
const ChoiceIcon = function ChoiceIcon(props) {
|
|
23672
23138
|
const {
|
|
23673
23139
|
checked,
|
|
@@ -23679,9 +23145,41 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
23679
23145
|
multipleSelect,
|
|
23680
23146
|
pos,
|
|
23681
23147
|
previouslyAnswered,
|
|
23148
|
+
// Used in "review mode"/"show rationale" to show that option was previously chosen
|
|
23682
23149
|
pressed
|
|
23683
23150
|
} = props;
|
|
23684
|
-
|
|
23151
|
+
|
|
23152
|
+
// Accounts for incorrect choice still displaying as learner tries again
|
|
23153
|
+
const choiceIsChecked = checked || showCorrectness && !correct && previouslyAnswered;
|
|
23154
|
+
|
|
23155
|
+
// Core styling
|
|
23156
|
+
const choiceStyling = [styles$r.choiceBase, multipleSelect ? styles$r.multiSelectShape : styles$r.singleSelectShape, showCorrectness ? styles$r.choiceHasIcon : styles$r.choiceHasLetter, choiceIsChecked ? styles$r.choiceIsChecked : styles$r.choiceIsUnchecked];
|
|
23157
|
+
|
|
23158
|
+
// Color styling
|
|
23159
|
+
// Handle dynamic styling of the multiple choice icon. Most
|
|
23160
|
+
// MC icon styles are constant, but we do allow the caller
|
|
23161
|
+
// to specify the selected color, and thus must control styles
|
|
23162
|
+
// related to the selected state dynamically.
|
|
23163
|
+
let crossOutColor;
|
|
23164
|
+
if (showCorrectness && correct && checked) {
|
|
23165
|
+
choiceStyling.push(styles$r.choiceCorrect);
|
|
23166
|
+
crossOutColor = color.green;
|
|
23167
|
+
} else if (showCorrectness && !correct && (checked || previouslyAnswered)) {
|
|
23168
|
+
choiceStyling.push(styles$r.choiceIncorrect);
|
|
23169
|
+
crossOutColor = color.red;
|
|
23170
|
+
} else if (checked) {
|
|
23171
|
+
// Show filled neutral blue color (showCorrectness is false)
|
|
23172
|
+
choiceStyling.push(styles$r.choiceNeutral);
|
|
23173
|
+
crossOutColor = color.blue;
|
|
23174
|
+
} else if (pressed) {
|
|
23175
|
+
// Show outlined neutral blue color (showCorrectness is false)
|
|
23176
|
+
choiceStyling.push(styles$r.activeNeutral);
|
|
23177
|
+
crossOutColor = color.blue;
|
|
23178
|
+
} else {
|
|
23179
|
+
// choice is not checked
|
|
23180
|
+
choiceStyling.push(styles$r.uncheckedColors);
|
|
23181
|
+
crossOutColor = color.offBlack64;
|
|
23182
|
+
}
|
|
23685
23183
|
return /*#__PURE__*/React.createElement("div", {
|
|
23686
23184
|
className: css(styles$t.iconWrapper)
|
|
23687
23185
|
}, /*#__PURE__*/React.createElement(FocusRing, {
|
|
@@ -23689,9 +23187,8 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
23689
23187
|
visible: focused || hovered,
|
|
23690
23188
|
multipleSelect: multipleSelect
|
|
23691
23189
|
}, /*#__PURE__*/React.createElement("div", {
|
|
23692
|
-
style: dynamicStyles,
|
|
23693
23190
|
"data-testid": "choice-icon__library-choice-icon",
|
|
23694
|
-
className: css(
|
|
23191
|
+
className: css(...choiceStyling)
|
|
23695
23192
|
// used in BaseRadio editMode to check
|
|
23696
23193
|
// if we actually clicked on the radio icon
|
|
23697
23194
|
,
|
|
@@ -23703,7 +23200,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
23703
23200
|
showCorrectness: showCorrectness,
|
|
23704
23201
|
correct: correct
|
|
23705
23202
|
})))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
|
|
23706
|
-
color:
|
|
23203
|
+
color: crossOutColor
|
|
23707
23204
|
}));
|
|
23708
23205
|
};
|
|
23709
23206
|
const styles$r = StyleSheet.create({
|
|
@@ -23715,8 +23212,7 @@ const styles$r = StyleSheet.create({
|
|
|
23715
23212
|
alignItems: "center",
|
|
23716
23213
|
justifyContent: "center"
|
|
23717
23214
|
},
|
|
23718
|
-
|
|
23719
|
-
// Make the circle
|
|
23215
|
+
choiceBase: {
|
|
23720
23216
|
width: CHOICE_ICON_SIZE,
|
|
23721
23217
|
height: CHOICE_ICON_SIZE,
|
|
23722
23218
|
boxSizing: "border-box",
|
|
@@ -23728,30 +23224,48 @@ const styles$r = StyleSheet.create({
|
|
|
23728
23224
|
// "bold font family" so that characters which fall back to the default
|
|
23729
23225
|
// font get bolded too.
|
|
23730
23226
|
fontWeight: "bold",
|
|
23731
|
-
fontSize: 12,
|
|
23732
23227
|
// Center the icon wrapper.
|
|
23733
23228
|
display: "flex",
|
|
23734
23229
|
alignItems: "center",
|
|
23735
|
-
justifyContent: "center"
|
|
23736
|
-
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
lineHeight: "1px"
|
|
23742
|
-
},
|
|
23743
|
-
circleCorrect: {
|
|
23230
|
+
justifyContent: "center"
|
|
23231
|
+
},
|
|
23232
|
+
choiceHasLetter: {
|
|
23233
|
+
fontSize: 12
|
|
23234
|
+
},
|
|
23235
|
+
choiceHasIcon: {
|
|
23744
23236
|
fontSize: CHOICE_ICON_SIZE
|
|
23745
23237
|
},
|
|
23746
|
-
|
|
23747
|
-
|
|
23748
|
-
borderColor: gray68,
|
|
23749
|
-
color: gray68
|
|
23238
|
+
choiceIsChecked: {
|
|
23239
|
+
color: color.white
|
|
23750
23240
|
},
|
|
23751
|
-
|
|
23241
|
+
choiceIsUnchecked: {
|
|
23242
|
+
color: color.offBlack64
|
|
23243
|
+
},
|
|
23244
|
+
choiceCorrect: {
|
|
23245
|
+
backgroundColor: color.green,
|
|
23246
|
+
borderColor: color.green
|
|
23247
|
+
},
|
|
23248
|
+
choiceIncorrect: {
|
|
23752
23249
|
backgroundColor: color.red,
|
|
23753
|
-
borderColor: color.red
|
|
23754
|
-
|
|
23250
|
+
borderColor: color.red
|
|
23251
|
+
},
|
|
23252
|
+
choiceNeutral: {
|
|
23253
|
+
backgroundColor: color.blue,
|
|
23254
|
+
borderColor: color.blue
|
|
23255
|
+
},
|
|
23256
|
+
activeNeutral: {
|
|
23257
|
+
color: color.blue,
|
|
23258
|
+
borderColor: color.blue,
|
|
23259
|
+
backgroundColor: "transparent"
|
|
23260
|
+
},
|
|
23261
|
+
multiSelectShape: {
|
|
23262
|
+
borderRadius: 3
|
|
23263
|
+
},
|
|
23264
|
+
singleSelectShape: {
|
|
23265
|
+
borderRadius: CHOICE_ICON_SIZE
|
|
23266
|
+
},
|
|
23267
|
+
uncheckedColors: {
|
|
23268
|
+
borderColor: color.offBlack64
|
|
23755
23269
|
}
|
|
23756
23270
|
});
|
|
23757
23271
|
|
|
@@ -24288,7 +23802,7 @@ const BaseRadio = function BaseRadio({
|
|
|
24288
23802
|
const aphroditeClassName = checked => {
|
|
24289
23803
|
// Whether or not to show correctness borders
|
|
24290
23804
|
// for this choice and the next choice.
|
|
24291
|
-
return css(
|
|
23805
|
+
return css(styles$o.item, styles$o.responsiveItem, checked && styles$o.selectedItem, checked && choice.highlighted && styles$o.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$o.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$o.nextHighlighted);
|
|
24292
23806
|
};
|
|
24293
23807
|
|
|
24294
23808
|
// HACK(abdulrahman): Preloads the selection-state
|
|
@@ -24878,17 +24392,105 @@ var Radio = {
|
|
|
24878
24392
|
version: radioLogic.version,
|
|
24879
24393
|
propUpgrades: radioLogic.widgetOptionsUpgrades,
|
|
24880
24394
|
isLintable: true,
|
|
24881
|
-
|
|
24882
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
24883
|
-
scorer: scoreRadio,
|
|
24884
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
24885
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
24886
|
-
validator: validateRadio
|
|
24395
|
+
getPublicWidgetOptions: getRadioPublicWidgetOptions
|
|
24887
24396
|
};
|
|
24888
24397
|
|
|
24889
24398
|
/* eslint-disable import/no-named-as-default */
|
|
24890
24399
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
24891
24400
|
|
|
24401
|
+
const {
|
|
24402
|
+
radioBorderColor,
|
|
24403
|
+
checkedColor,
|
|
24404
|
+
circleSize,
|
|
24405
|
+
radioMarginWidth
|
|
24406
|
+
} = constants;
|
|
24407
|
+
var sharedStyles = StyleSheet.create({
|
|
24408
|
+
blankBackground: {
|
|
24409
|
+
// TODO(emily): Use WB colors?
|
|
24410
|
+
backgroundColor: "#FDFDFD"
|
|
24411
|
+
},
|
|
24412
|
+
perseusSrOnly: {
|
|
24413
|
+
border: 0,
|
|
24414
|
+
clip: "rect(0,0,0,0)",
|
|
24415
|
+
height: 1,
|
|
24416
|
+
margin: -1,
|
|
24417
|
+
overflow: "hidden",
|
|
24418
|
+
padding: 0,
|
|
24419
|
+
position: "absolute",
|
|
24420
|
+
width: 1
|
|
24421
|
+
},
|
|
24422
|
+
responsiveLabel: {
|
|
24423
|
+
// NOTE(charlie): The values used here should be kept in sync with the
|
|
24424
|
+
// caption text sizing in Perseus' styles.less.
|
|
24425
|
+
// TODO(charlie): Migrate the captions over to using this style.
|
|
24426
|
+
[mediaQueries.smOrSmaller]: {
|
|
24427
|
+
fontSize: 14,
|
|
24428
|
+
lineHeight: 1.3
|
|
24429
|
+
},
|
|
24430
|
+
[mediaQueries.md]: {
|
|
24431
|
+
fontSize: 17,
|
|
24432
|
+
lineHeight: 1.4
|
|
24433
|
+
},
|
|
24434
|
+
[mediaQueries.lgOrLarger]: {
|
|
24435
|
+
fontSize: 20,
|
|
24436
|
+
lineHeight: 1.4
|
|
24437
|
+
}
|
|
24438
|
+
},
|
|
24439
|
+
responsiveInput: {
|
|
24440
|
+
display: "inline-block",
|
|
24441
|
+
WebkitAppearance: "none",
|
|
24442
|
+
appearance: "none",
|
|
24443
|
+
"::-ms-check": {
|
|
24444
|
+
display: "none"
|
|
24445
|
+
},
|
|
24446
|
+
backgroundColor: "#fff",
|
|
24447
|
+
border: "2px solid #fff",
|
|
24448
|
+
boxShadow: `0 0px 0px 1px ${radioBorderColor}`,
|
|
24449
|
+
outline: "none",
|
|
24450
|
+
boxSizing: "border-box",
|
|
24451
|
+
flexShrink: 0,
|
|
24452
|
+
marginBottom: 1,
|
|
24453
|
+
marginLeft: 1,
|
|
24454
|
+
marginRight: 1,
|
|
24455
|
+
marginTop: 1,
|
|
24456
|
+
height: circleSize - 2,
|
|
24457
|
+
width: circleSize - 2
|
|
24458
|
+
},
|
|
24459
|
+
responsiveRadioInput: {
|
|
24460
|
+
borderRadius: "50%",
|
|
24461
|
+
":checked": {
|
|
24462
|
+
backgroundColor: checkedColor,
|
|
24463
|
+
border: "none",
|
|
24464
|
+
borderRadius: "50%",
|
|
24465
|
+
boxShadow: `inset 0px 0px 0px 2px white, ` + `0 0px 0px 2px ${checkedColor}`,
|
|
24466
|
+
marginTop: radioMarginWidth,
|
|
24467
|
+
marginBottom: radioMarginWidth,
|
|
24468
|
+
marginLeft: radioMarginWidth,
|
|
24469
|
+
marginRight: radioMarginWidth,
|
|
24470
|
+
height: circleSize - 2 * radioMarginWidth,
|
|
24471
|
+
width: circleSize - 2 * radioMarginWidth
|
|
24472
|
+
}
|
|
24473
|
+
},
|
|
24474
|
+
responsiveRadioInputActive: {
|
|
24475
|
+
backgroundColor: "#fff",
|
|
24476
|
+
border: "2px solid #fff",
|
|
24477
|
+
borderRadius: "50%",
|
|
24478
|
+
boxShadow: `0 0px 0px 2px ${checkedColor}`,
|
|
24479
|
+
marginTop: radioMarginWidth,
|
|
24480
|
+
marginBottom: radioMarginWidth,
|
|
24481
|
+
marginLeft: radioMarginWidth,
|
|
24482
|
+
marginRight: radioMarginWidth,
|
|
24483
|
+
height: circleSize - 2 * radioMarginWidth,
|
|
24484
|
+
width: circleSize - 2 * radioMarginWidth,
|
|
24485
|
+
":checked": {
|
|
24486
|
+
backgroundColor: "#fff"
|
|
24487
|
+
}
|
|
24488
|
+
},
|
|
24489
|
+
disableTextSelection: {
|
|
24490
|
+
userSelect: "none"
|
|
24491
|
+
}
|
|
24492
|
+
});
|
|
24493
|
+
|
|
24892
24494
|
const getPromptJSON$n = (renderProps, userInput) => {
|
|
24893
24495
|
return {
|
|
24894
24496
|
type: "categorizer",
|
|
@@ -24979,7 +24581,6 @@ class Categorizer extends React.Component {
|
|
|
24979
24581
|
className: "category " + css(styles$n.cell),
|
|
24980
24582
|
key: catNum
|
|
24981
24583
|
}, /*#__PURE__*/React.createElement("div", {
|
|
24982
|
-
className: ClassNames.INTERACTIVE,
|
|
24983
24584
|
role: "button",
|
|
24984
24585
|
"aria-label": catName,
|
|
24985
24586
|
onClick: () => this.onChange(itemNum, catNum)
|
|
@@ -25078,12 +24679,6 @@ var Categorizer$1 = {
|
|
|
25078
24679
|
return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
25079
24680
|
},
|
|
25080
24681
|
isLintable: true,
|
|
25081
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
25082
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25083
|
-
scorer: scoreCategorizer,
|
|
25084
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
25085
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25086
|
-
validator: validateCategorizer,
|
|
25087
24682
|
getPublicWidgetOptions: getCategorizerPublicWidgetOptions
|
|
25088
24683
|
};
|
|
25089
24684
|
|
|
@@ -25289,12 +24884,9 @@ const styles$m = StyleSheet.create({
|
|
|
25289
24884
|
var CSProgram$1 = {
|
|
25290
24885
|
name: "cs-program",
|
|
25291
24886
|
displayName: "CS Program",
|
|
25292
|
-
supportedAlignments: ["block", "full-width"],
|
|
25293
24887
|
widget: CSProgram,
|
|
25294
24888
|
hidden: true,
|
|
25295
|
-
|
|
25296
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25297
|
-
scorer: scoreCSProgram
|
|
24889
|
+
getPublicWidgetOptions: getCSProgramPublicWidgetOptions
|
|
25298
24890
|
};
|
|
25299
24891
|
|
|
25300
24892
|
const getPromptJSON$l = renderProps => {
|
|
@@ -25367,11 +24959,8 @@ var Definition$1 = {
|
|
|
25367
24959
|
name: "definition",
|
|
25368
24960
|
displayName: "Definition",
|
|
25369
24961
|
accessible: true,
|
|
25370
|
-
defaultAlignment: "inline",
|
|
25371
24962
|
widget: Definition,
|
|
25372
|
-
transform: x => x
|
|
25373
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
25374
|
-
scorer: () => scoreNoop()
|
|
24963
|
+
transform: x => x
|
|
25375
24964
|
};
|
|
25376
24965
|
|
|
25377
24966
|
// There are no required props, but this component can receive props from any
|
|
@@ -25401,9 +24990,7 @@ var DeprecatedStandin$1 = {
|
|
|
25401
24990
|
name: "deprecated-standin",
|
|
25402
24991
|
displayName: "Deprecated Standin",
|
|
25403
24992
|
widget: DeprecatedStandin,
|
|
25404
|
-
hidden: true
|
|
25405
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
25406
|
-
scorer: () => scoreNoop(1)
|
|
24993
|
+
hidden: true
|
|
25407
24994
|
};
|
|
25408
24995
|
|
|
25409
24996
|
const getPromptJSON$k = (renderProps, userInput) => {
|
|
@@ -25515,16 +25102,10 @@ const optionsTransform = widgetOptions => {
|
|
|
25515
25102
|
var Dropdown$1 = {
|
|
25516
25103
|
name: "dropdown",
|
|
25517
25104
|
displayName: "Drop down",
|
|
25518
|
-
defaultAlignment: "inline-block",
|
|
25519
25105
|
accessible: true,
|
|
25520
25106
|
widget: Dropdown,
|
|
25521
25107
|
transform: optionsTransform,
|
|
25522
|
-
|
|
25523
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25524
|
-
scorer: scoreDropdown,
|
|
25525
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
25526
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25527
|
-
validator: validateDropdown
|
|
25108
|
+
getPublicWidgetOptions: getDropdownPublicWidgetOptions
|
|
25528
25109
|
};
|
|
25529
25110
|
|
|
25530
25111
|
const getPromptJSON$j = renderProps => {
|
|
@@ -25679,12 +25260,9 @@ var Explanation$1 = {
|
|
|
25679
25260
|
name: "explanation",
|
|
25680
25261
|
displayName: "Explanation",
|
|
25681
25262
|
accessible: true,
|
|
25682
|
-
defaultAlignment: "inline",
|
|
25683
25263
|
widget: Explanation,
|
|
25684
25264
|
transform: _.identity,
|
|
25685
|
-
isLintable: true
|
|
25686
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
25687
|
-
scorer: () => scoreNoop()
|
|
25265
|
+
isLintable: true
|
|
25688
25266
|
};
|
|
25689
25267
|
|
|
25690
25268
|
const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
|
|
@@ -26784,7 +26362,7 @@ class FunctionGrapher extends React.Component {
|
|
|
26784
26362
|
boxSizing: "initial"
|
|
26785
26363
|
}
|
|
26786
26364
|
}, /*#__PURE__*/React.createElement("div", {
|
|
26787
|
-
className: "graphie-container
|
|
26365
|
+
className: "graphie-container blank-background",
|
|
26788
26366
|
style: {
|
|
26789
26367
|
width: box[0],
|
|
26790
26368
|
height: box[1]
|
|
@@ -26923,8 +26501,7 @@ class Grapher extends React.Component {
|
|
|
26923
26501
|
const coords = this.props.plot.coords;
|
|
26924
26502
|
const asymptote = this.props.plot.asymptote;
|
|
26925
26503
|
const typeSelector = /*#__PURE__*/React.createElement("div", {
|
|
26926
|
-
style: typeSelectorStyle
|
|
26927
|
-
className: "above-scratchpad"
|
|
26504
|
+
style: typeSelectorStyle
|
|
26928
26505
|
}, /*#__PURE__*/React.createElement(ButtonGroup, {
|
|
26929
26506
|
value: type,
|
|
26930
26507
|
allowEmpty: true,
|
|
@@ -27003,9 +26580,7 @@ var Grapher$1 = {
|
|
|
27003
26580
|
widget: Grapher,
|
|
27004
26581
|
transform: propTransform$2,
|
|
27005
26582
|
staticTransform: staticTransform$4,
|
|
27006
|
-
|
|
27007
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGrapherUserInput'.
|
|
27008
|
-
scorer: scoreGrapher
|
|
26583
|
+
getPublicWidgetOptions: getGrapherPublicWidgetOptions
|
|
27009
26584
|
};
|
|
27010
26585
|
|
|
27011
26586
|
const getPromptJSON$f = rendererJSON => {
|
|
@@ -27021,24 +26596,6 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
27021
26596
|
});
|
|
27022
26597
|
};
|
|
27023
26598
|
|
|
27024
|
-
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
27025
|
-
// it. As such, scoring a group means scoring all widgets it contains.
|
|
27026
|
-
function scoreGroup(userInput, rubric, strings, locale) {
|
|
27027
|
-
const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
|
|
27028
|
-
return flattenScores(scores);
|
|
27029
|
-
}
|
|
27030
|
-
|
|
27031
|
-
function validateGroup(userInput, validationData, strings, locale) {
|
|
27032
|
-
const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
|
|
27033
|
-
if (emptyWidgets.length === 0) {
|
|
27034
|
-
return null;
|
|
27035
|
-
}
|
|
27036
|
-
return {
|
|
27037
|
-
type: "invalid",
|
|
27038
|
-
message: null
|
|
27039
|
-
};
|
|
27040
|
-
}
|
|
27041
|
-
|
|
27042
26599
|
// exports has no 'transform'
|
|
27043
26600
|
|
|
27044
26601
|
class Group extends React.Component {
|
|
@@ -27193,12 +26750,6 @@ var Group$1 = {
|
|
|
27193
26750
|
displayName: "Group (SAT only)",
|
|
27194
26751
|
widget: Group,
|
|
27195
26752
|
traverseChildWidgets: traverseChildWidgets,
|
|
27196
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
27197
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27198
|
-
scorer: scoreGroup,
|
|
27199
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
27200
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27201
|
-
validator: validateGroup,
|
|
27202
26753
|
hidden: true,
|
|
27203
26754
|
isLintable: true
|
|
27204
26755
|
};
|
|
@@ -27335,9 +26886,7 @@ var Iframe$1 = {
|
|
|
27335
26886
|
widget: Iframe,
|
|
27336
26887
|
// Let's not expose it to all content creators yet
|
|
27337
26888
|
hidden: true,
|
|
27338
|
-
|
|
27339
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusIframeUserInput'.
|
|
27340
|
-
scorer: scoreIframe
|
|
26889
|
+
getPublicWidgetOptions: getIFramePublicWidgetOptions
|
|
27341
26890
|
};
|
|
27342
26891
|
|
|
27343
26892
|
const getPromptJSON$d = renderProps => {
|
|
@@ -27359,8 +26908,6 @@ const defaultBackgroundImage$2 = {
|
|
|
27359
26908
|
width: 0,
|
|
27360
26909
|
height: 0
|
|
27361
26910
|
};
|
|
27362
|
-
const editorAlignments = ["block", "full-width"];
|
|
27363
|
-
const DEFAULT_ALIGNMENT = "block";
|
|
27364
26911
|
|
|
27365
26912
|
// there is no transform as part of exports
|
|
27366
26913
|
|
|
@@ -27509,7 +27056,7 @@ class ImageWidget extends React.Component {
|
|
|
27509
27056
|
}
|
|
27510
27057
|
ImageWidget.contextType = PerseusI18nContext;
|
|
27511
27058
|
ImageWidget.defaultProps = {
|
|
27512
|
-
alignment:
|
|
27059
|
+
alignment: "block",
|
|
27513
27060
|
title: "",
|
|
27514
27061
|
range: [defaultRange, defaultRange],
|
|
27515
27062
|
box: [defaultBoxSize, defaultBoxSize],
|
|
@@ -27527,13 +27074,9 @@ var Image$1 = {
|
|
|
27527
27074
|
const bgImage = widgetOptions.backgroundImage;
|
|
27528
27075
|
return !(bgImage && bgImage.url && !widgetOptions.alt);
|
|
27529
27076
|
},
|
|
27530
|
-
defaultAlignment: DEFAULT_ALIGNMENT,
|
|
27531
|
-
supportedAlignments: editorAlignments,
|
|
27532
27077
|
displayName: "Image",
|
|
27533
27078
|
widget: ImageWidget,
|
|
27534
|
-
isLintable: true
|
|
27535
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27536
|
-
scorer: () => scoreNoop()
|
|
27079
|
+
isLintable: true
|
|
27537
27080
|
};
|
|
27538
27081
|
|
|
27539
27082
|
const getPromptJSON$c = () => {
|
|
@@ -28131,9 +27674,7 @@ var Interactive = {
|
|
|
28131
27674
|
displayName: "Interaction",
|
|
28132
27675
|
widget: Interaction,
|
|
28133
27676
|
transform: _.identity,
|
|
28134
|
-
hidden: true
|
|
28135
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
28136
|
-
scorer: () => scoreNoop()
|
|
27677
|
+
hidden: true
|
|
28137
27678
|
};
|
|
28138
27679
|
|
|
28139
27680
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -28402,7 +27943,7 @@ class Graph extends React.Component {
|
|
|
28402
27943
|
image = null;
|
|
28403
27944
|
}
|
|
28404
27945
|
return /*#__PURE__*/React.createElement("div", {
|
|
28405
|
-
className: "graphie-container
|
|
27946
|
+
className: "graphie-container blank-background",
|
|
28406
27947
|
style: {
|
|
28407
27948
|
width: this.props.box[0],
|
|
28408
27949
|
height: this.props.box[1]
|
|
@@ -29037,7 +28578,8 @@ const tickLabelSize = 14;
|
|
|
29037
28578
|
const YGridTick = ({
|
|
29038
28579
|
y,
|
|
29039
28580
|
range,
|
|
29040
|
-
tickStep
|
|
28581
|
+
tickStep,
|
|
28582
|
+
showPi
|
|
29041
28583
|
}) => {
|
|
29042
28584
|
// If the graph requires out-of-bounds labels, we want to make sure to set the
|
|
29043
28585
|
// coordinates to the edge of the visible range of the graph. Otherwise,
|
|
@@ -29073,6 +28615,7 @@ const YGridTick = ({
|
|
|
29073
28615
|
// If the graph displays both the y and x axis lines within the graph, we want
|
|
29074
28616
|
// to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
|
|
29075
28617
|
const showLabel = shouldShowLabel(y, range, tickStep);
|
|
28618
|
+
const yLabel = showPi ? divideByAndShowPi(y) : y.toString();
|
|
29076
28619
|
return /*#__PURE__*/React.createElement("g", {
|
|
29077
28620
|
className: "tick",
|
|
29078
28621
|
"aria-hidden": true
|
|
@@ -29090,11 +28633,12 @@ const YGridTick = ({
|
|
|
29090
28633
|
textAnchor: "end",
|
|
29091
28634
|
x: xPositionText,
|
|
29092
28635
|
y: yPositionText
|
|
29093
|
-
},
|
|
28636
|
+
}, yLabel));
|
|
29094
28637
|
};
|
|
29095
28638
|
const XGridTick = ({
|
|
29096
28639
|
x,
|
|
29097
|
-
range
|
|
28640
|
+
range,
|
|
28641
|
+
showPi
|
|
29098
28642
|
}) => {
|
|
29099
28643
|
// If the graph requires out-of-bounds labels, we want to make sure to set the
|
|
29100
28644
|
// coordinates to the edge of the visible range of the graph. Otherwise,
|
|
@@ -29133,6 +28677,7 @@ const XGridTick = ({
|
|
|
29133
28677
|
// Apply the adjustments to the x and y positions for the text
|
|
29134
28678
|
const xPositionText = xPosition + xAdjustment;
|
|
29135
28679
|
const yPositionText = yPosition + yAdjustment;
|
|
28680
|
+
const xLabel = showPi ? divideByAndShowPi(x) : x.toString();
|
|
29136
28681
|
return /*#__PURE__*/React.createElement("g", {
|
|
29137
28682
|
className: "tick",
|
|
29138
28683
|
"aria-hidden": true
|
|
@@ -29150,7 +28695,7 @@ const XGridTick = ({
|
|
|
29150
28695
|
textAnchor: "middle",
|
|
29151
28696
|
x: xPositionText,
|
|
29152
28697
|
y: yPositionText
|
|
29153
|
-
},
|
|
28698
|
+
}, xLabel));
|
|
29154
28699
|
};
|
|
29155
28700
|
|
|
29156
28701
|
// Determines whether to show the label for the given tick
|
|
@@ -29197,6 +28742,22 @@ const countSignificantDecimals = number => {
|
|
|
29197
28742
|
}
|
|
29198
28743
|
return numStr.split(".")[1].length;
|
|
29199
28744
|
};
|
|
28745
|
+
|
|
28746
|
+
// Show the given value as a multiple of pi (already assumed to be
|
|
28747
|
+
// a multiple of pi). Exported for testing
|
|
28748
|
+
function divideByAndShowPi(value) {
|
|
28749
|
+
const dividedValue = value / Math.PI;
|
|
28750
|
+
switch (dividedValue) {
|
|
28751
|
+
case 1:
|
|
28752
|
+
return "π";
|
|
28753
|
+
case -1:
|
|
28754
|
+
return "-π";
|
|
28755
|
+
case 0:
|
|
28756
|
+
return "0";
|
|
28757
|
+
default:
|
|
28758
|
+
return dividedValue + "π";
|
|
28759
|
+
}
|
|
28760
|
+
}
|
|
29200
28761
|
const AxisTicks = () => {
|
|
29201
28762
|
const {
|
|
29202
28763
|
tickStep,
|
|
@@ -29219,6 +28780,10 @@ const AxisTicks = () => {
|
|
|
29219
28780
|
key: `y-grid-tick-${y}`,
|
|
29220
28781
|
range: range,
|
|
29221
28782
|
tickStep: tickStep[Y]
|
|
28783
|
+
// Show the tick labels as multiples of pi
|
|
28784
|
+
// if the tick step is a multiple of pi.
|
|
28785
|
+
,
|
|
28786
|
+
showPi: tickStep[Y] % Math.PI === 0
|
|
29222
28787
|
});
|
|
29223
28788
|
})), /*#__PURE__*/React.createElement("g", {
|
|
29224
28789
|
className: "x-axis-ticks"
|
|
@@ -29227,6 +28792,10 @@ const AxisTicks = () => {
|
|
|
29227
28792
|
x: x,
|
|
29228
28793
|
key: `x-grid-tick-${x}`,
|
|
29229
28794
|
range: range
|
|
28795
|
+
// Show the tick labels as multiples of pi
|
|
28796
|
+
// if the tick step is a multiple of pi.
|
|
28797
|
+
,
|
|
28798
|
+
showPi: tickStep[X] % Math.PI === 0
|
|
29230
28799
|
});
|
|
29231
28800
|
})));
|
|
29232
28801
|
};
|
|
@@ -29534,6 +29103,15 @@ const styles$d = StyleSheet.create({
|
|
|
29534
29103
|
}
|
|
29535
29104
|
});
|
|
29536
29105
|
|
|
29106
|
+
function isPiMultiple(value) {
|
|
29107
|
+
// Early return for integers that clearly aren't multiples
|
|
29108
|
+
// of pi to save some computation.
|
|
29109
|
+
if (Number.isInteger(value)) {
|
|
29110
|
+
return false;
|
|
29111
|
+
}
|
|
29112
|
+
return value % Math.PI === 0 || value % (Math.PI / 2) === 0 || value % (Math.PI / 3) === 0 || value % (Math.PI / 4) === 0 || value % (Math.PI / 6) === 0;
|
|
29113
|
+
}
|
|
29114
|
+
|
|
29537
29115
|
const _excluded$1 = ["onFormatChange", "checkValidity", "useArrowKeys"];
|
|
29538
29116
|
const {
|
|
29539
29117
|
firstNumericalParse,
|
|
@@ -29671,6 +29249,19 @@ class NumberInput extends React.Component {
|
|
|
29671
29249
|
this._getInput().value = toNumericString(val, format);
|
|
29672
29250
|
};
|
|
29673
29251
|
}
|
|
29252
|
+
componentDidMount() {
|
|
29253
|
+
// If the value is a multiple of pi, but it is not in the pi format,
|
|
29254
|
+
// then convert it to the pi format and show it as a multiple of pi.
|
|
29255
|
+
const value = this.getValue();
|
|
29256
|
+
if (this.props.allowPiTruncation && value !== null && value !== 0) {
|
|
29257
|
+
if (this.state.format !== "pi" && isPiMultiple(value)) {
|
|
29258
|
+
this._setValue(value / Math.PI, "pi");
|
|
29259
|
+
this.setState({
|
|
29260
|
+
format: "pi"
|
|
29261
|
+
});
|
|
29262
|
+
}
|
|
29263
|
+
}
|
|
29264
|
+
}
|
|
29674
29265
|
componentDidUpdate(prevProps) {
|
|
29675
29266
|
if (!number.equal(this.getValue(), this.props.value)) {
|
|
29676
29267
|
this._setValue(this.props.value, this.state.format);
|
|
@@ -29713,7 +29304,8 @@ NumberInput.propTypes = {
|
|
|
29713
29304
|
onFormatChange: PropTypes.func,
|
|
29714
29305
|
checkValidity: PropTypes.func,
|
|
29715
29306
|
size: PropTypes.oneOf(["mini", "small", "normal"]),
|
|
29716
|
-
label: PropTypes.oneOf(["put your labels outside your inputs!"])
|
|
29307
|
+
label: PropTypes.oneOf(["put your labels outside your inputs!"]),
|
|
29308
|
+
allowPiTruncation: PropTypes.bool
|
|
29717
29309
|
};
|
|
29718
29310
|
NumberInput.defaultProps = {
|
|
29719
29311
|
value: null,
|
|
@@ -29752,14 +29344,16 @@ class RangeInput extends React.Component {
|
|
|
29752
29344
|
// eslint-disable-next-line react/jsx-no-bind
|
|
29753
29345
|
,
|
|
29754
29346
|
onChange: this.onChange.bind(this, 0),
|
|
29755
|
-
placeholder: this.props.placeholder[0]
|
|
29347
|
+
placeholder: this.props.placeholder[0],
|
|
29348
|
+
allowPiTruncation: this.props.allowPiTruncation
|
|
29756
29349
|
})), /*#__PURE__*/React.createElement(NumberInput, _extends({}, this.props, {
|
|
29757
29350
|
value: value[1],
|
|
29758
29351
|
checkValidity: val => checkValidity([value[0], val])
|
|
29759
29352
|
// eslint-disable-next-line react/jsx-no-bind
|
|
29760
29353
|
,
|
|
29761
29354
|
onChange: this.onChange.bind(this, 1),
|
|
29762
|
-
placeholder: this.props.placeholder[1]
|
|
29355
|
+
placeholder: this.props.placeholder[1],
|
|
29356
|
+
allowPiTruncation: this.props.allowPiTruncation
|
|
29763
29357
|
})));
|
|
29764
29358
|
}
|
|
29765
29359
|
}
|
|
@@ -29767,7 +29361,8 @@ RangeInput.propTypes = {
|
|
|
29767
29361
|
value: PropTypes.array.isRequired,
|
|
29768
29362
|
onChange: PropTypes.func.isRequired,
|
|
29769
29363
|
placeholder: PropTypes.array,
|
|
29770
|
-
checkValidity: PropTypes.func
|
|
29364
|
+
checkValidity: PropTypes.func,
|
|
29365
|
+
allowPiTruncation: PropTypes.bool
|
|
29771
29366
|
};
|
|
29772
29367
|
RangeInput.defaultProps = {
|
|
29773
29368
|
placeholder: [null, null]
|
|
@@ -29998,10 +29593,10 @@ function Vector(props) {
|
|
|
29998
29593
|
}));
|
|
29999
29594
|
}
|
|
30000
29595
|
|
|
30001
|
-
function srFormatNumber(a, locale) {
|
|
29596
|
+
function srFormatNumber(a, locale, maximumFractionDigits) {
|
|
30002
29597
|
// adding zero here converts negative zero to positive zero.
|
|
30003
29598
|
return (0 + a).toLocaleString(locale, {
|
|
30004
|
-
maximumFractionDigits: 3,
|
|
29599
|
+
maximumFractionDigits: maximumFractionDigits != null ? maximumFractionDigits : 3,
|
|
30005
29600
|
useGrouping: false // no thousands separators
|
|
30006
29601
|
});
|
|
30007
29602
|
}
|
|
@@ -30177,6 +29772,95 @@ a, b, c) {
|
|
|
30177
29772
|
return [x1, x2];
|
|
30178
29773
|
}
|
|
30179
29774
|
|
|
29775
|
+
/**
|
|
29776
|
+
* Given a list of points, return the angle made by the point at index i
|
|
29777
|
+
* with the points at indices i - 1 and i + 1.
|
|
29778
|
+
*/
|
|
29779
|
+
function getAngleFromPoints(points, i) {
|
|
29780
|
+
// Make sure the index is valid.
|
|
29781
|
+
if (i < 0 || i >= points.length || !Number.isInteger(i)) {
|
|
29782
|
+
return null;
|
|
29783
|
+
}
|
|
29784
|
+
|
|
29785
|
+
// If there are fewer than 3 points, there is no angle.
|
|
29786
|
+
if (points.length < 3) {
|
|
29787
|
+
return null;
|
|
29788
|
+
}
|
|
29789
|
+
|
|
29790
|
+
// Get the points at the current index and the two adjacent points.
|
|
29791
|
+
const point = points.at(i);
|
|
29792
|
+
const pt1 = points.at(i - 1);
|
|
29793
|
+
const pt2 = points[(i + 1) % points.length];
|
|
29794
|
+
if (!point || !pt1 || !pt2) {
|
|
29795
|
+
return null;
|
|
29796
|
+
}
|
|
29797
|
+
|
|
29798
|
+
// Get the lengths of adjacent sides, making a triangle
|
|
29799
|
+
// we can use to calculate the angle.
|
|
29800
|
+
const a = vec.dist(point, pt1);
|
|
29801
|
+
const b = vec.dist(point, pt2);
|
|
29802
|
+
const c = vec.dist(pt1, pt2);
|
|
29803
|
+
|
|
29804
|
+
// Law of cosines
|
|
29805
|
+
const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
|
|
29806
|
+
return angle;
|
|
29807
|
+
}
|
|
29808
|
+
function getSideLengthsFromPoints(points, i, isPolygonOpen) {
|
|
29809
|
+
if (i < 0 || i >= points.length || !Number.isInteger(i)) {
|
|
29810
|
+
return [];
|
|
29811
|
+
}
|
|
29812
|
+
if (points.length < 2) {
|
|
29813
|
+
return [];
|
|
29814
|
+
}
|
|
29815
|
+
const returnArray = [];
|
|
29816
|
+
const point = points[i];
|
|
29817
|
+
// If this point is the first point, then side 1 is the
|
|
29818
|
+
// last point in the list.
|
|
29819
|
+
const point1Index = i === 0 ? points.length - 1 : i - 1;
|
|
29820
|
+
const point1 = points[point1Index];
|
|
29821
|
+
// Make sure the previous point is not the same
|
|
29822
|
+
// as the current point.
|
|
29823
|
+
const side1 = i !== point1Index ? vec.dist(point, point1) : null;
|
|
29824
|
+
// The first side of the first point does not exist if
|
|
29825
|
+
// the polygon is open.
|
|
29826
|
+
if (side1 && !(isPolygonOpen && i === 0)) {
|
|
29827
|
+
returnArray.push({
|
|
29828
|
+
pointIndex: point1Index,
|
|
29829
|
+
sideLength: side1
|
|
29830
|
+
});
|
|
29831
|
+
}
|
|
29832
|
+
|
|
29833
|
+
// See if there is a side 2.
|
|
29834
|
+
const point2Index = (i + 1) % points.length;
|
|
29835
|
+
const point2 = points[point2Index];
|
|
29836
|
+
// Make sure that the next point is not the same as the
|
|
29837
|
+
// current point, and don't repeat the first point.
|
|
29838
|
+
const side2 = i !== point2Index && point2Index !== point1Index ? vec.dist(point, point2) : null;
|
|
29839
|
+
// The second side of the last point does not exist if
|
|
29840
|
+
// the polygon is open.
|
|
29841
|
+
if (side2 && !(isPolygonOpen && i === points.length - 1)) {
|
|
29842
|
+
returnArray.push({
|
|
29843
|
+
pointIndex: point2Index,
|
|
29844
|
+
sideLength: side2
|
|
29845
|
+
});
|
|
29846
|
+
}
|
|
29847
|
+
return returnArray;
|
|
29848
|
+
}
|
|
29849
|
+
|
|
29850
|
+
/**
|
|
29851
|
+
* Determine whether to determine the string with the
|
|
29852
|
+
* exact side length or the approximate side length.
|
|
29853
|
+
*/
|
|
29854
|
+
function getPolygonSideString(sideLength, pointIndex, strings, locale) {
|
|
29855
|
+
return Number.isInteger(sideLength) ? strings.srPolygonSideLength({
|
|
29856
|
+
pointNum: pointIndex + 1,
|
|
29857
|
+
length: `${sideLength}`
|
|
29858
|
+
}) : strings.srPolygonSideLengthApprox({
|
|
29859
|
+
pointNum: pointIndex + 1,
|
|
29860
|
+
length: srFormatNumber(sideLength, locale, 1)
|
|
29861
|
+
});
|
|
29862
|
+
}
|
|
29863
|
+
|
|
30180
29864
|
const {
|
|
30181
29865
|
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
30182
29866
|
} = angles;
|
|
@@ -30622,7 +30306,7 @@ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*
|
|
|
30622
30306
|
})));
|
|
30623
30307
|
|
|
30624
30308
|
const {
|
|
30625
|
-
clockwise
|
|
30309
|
+
clockwise
|
|
30626
30310
|
} = geometry;
|
|
30627
30311
|
const {
|
|
30628
30312
|
getAngleFromVertex: getAngleFromVertex$1
|
|
@@ -30636,7 +30320,7 @@ const PolygonAngle = ({
|
|
|
30636
30320
|
snapTo
|
|
30637
30321
|
}) => {
|
|
30638
30322
|
const [centerX, centerY] = centerPoint;
|
|
30639
|
-
const areClockwise = clockwise
|
|
30323
|
+
const areClockwise = clockwise([centerPoint, ...endPoints]);
|
|
30640
30324
|
const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
|
|
30641
30325
|
const radius = 0.3;
|
|
30642
30326
|
const a = vec.dist(centerPoint, endPoints[0]);
|
|
@@ -30708,7 +30392,7 @@ const Angle = ({
|
|
|
30708
30392
|
range
|
|
30709
30393
|
}) => {
|
|
30710
30394
|
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
30711
|
-
const areClockwise = clockwise
|
|
30395
|
+
const areClockwise = clockwise([...coords, vertex]);
|
|
30712
30396
|
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
30713
30397
|
|
|
30714
30398
|
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
@@ -31641,9 +31325,6 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
31641
31325
|
};
|
|
31642
31326
|
}
|
|
31643
31327
|
|
|
31644
|
-
const {
|
|
31645
|
-
clockwise
|
|
31646
|
-
} = geometry;
|
|
31647
31328
|
function getGradableGraph(state, initialGraph) {
|
|
31648
31329
|
if (!state.hasBeenInteractedWith) {
|
|
31649
31330
|
return _extends({}, initialGraph);
|
|
@@ -31707,16 +31388,8 @@ function getGradableGraph(state, initialGraph) {
|
|
|
31707
31388
|
});
|
|
31708
31389
|
}
|
|
31709
31390
|
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
31710
|
-
// We're going to reverse the coords for scoring if the angle is clockwise and
|
|
31711
|
-
// we don't allow reflex angles. This is because the angle is largely defined
|
|
31712
|
-
// by the order of the points in the coords array, and we want to maintain
|
|
31713
|
-
// the same angle scoring with the legacy graph (which had a bug).
|
|
31714
|
-
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
31715
|
-
const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
|
|
31716
|
-
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
31717
|
-
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
31718
31391
|
return _extends({}, initialGraph, {
|
|
31719
|
-
coords,
|
|
31392
|
+
coords: state.coords,
|
|
31720
31393
|
allowReflexAngles: state.allowReflexAngles
|
|
31721
31394
|
});
|
|
31722
31395
|
}
|
|
@@ -32355,17 +32028,20 @@ function describePointGraph(state, i18n) {
|
|
|
32355
32028
|
y: srFormatNumber(y, locale)
|
|
32356
32029
|
}));
|
|
32357
32030
|
return strings.srInteractiveElements({
|
|
32358
|
-
elements: pointDescriptions.join("
|
|
32031
|
+
elements: pointDescriptions.join(" ")
|
|
32359
32032
|
});
|
|
32360
32033
|
}
|
|
32361
32034
|
|
|
32362
|
-
|
|
32035
|
+
const {
|
|
32036
|
+
convertRadiansToDegrees
|
|
32037
|
+
} = angles;
|
|
32038
|
+
function renderPolygonGraph(state, dispatch, i18n, markings) {
|
|
32363
32039
|
return {
|
|
32364
32040
|
graph: /*#__PURE__*/React.createElement(PolygonGraph, {
|
|
32365
32041
|
graphState: state,
|
|
32366
32042
|
dispatch: dispatch
|
|
32367
32043
|
}),
|
|
32368
|
-
interactiveElementsDescription:
|
|
32044
|
+
interactiveElementsDescription: getPolygonGraphDescription(state, i18n, markings)
|
|
32369
32045
|
};
|
|
32370
32046
|
}
|
|
32371
32047
|
const PolygonGraph = props => {
|
|
@@ -32450,7 +32126,7 @@ const PolygonGraph = props => {
|
|
|
32450
32126
|
focusVisible,
|
|
32451
32127
|
setFocusVisible
|
|
32452
32128
|
});
|
|
32453
|
-
return numSides === "unlimited" ? UnlimitedPolygonGraph
|
|
32129
|
+
return numSides === "unlimited" ? /*#__PURE__*/React.createElement(UnlimitedPolygonGraph, statefulProps) : /*#__PURE__*/React.createElement(LimitedPolygonGraph, statefulProps);
|
|
32454
32130
|
};
|
|
32455
32131
|
const LimitedPolygonGraph = statefulProps => {
|
|
32456
32132
|
const {
|
|
@@ -32475,8 +32151,39 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32475
32151
|
const {
|
|
32476
32152
|
disableKeyboardInteraction
|
|
32477
32153
|
} = graphConfig;
|
|
32154
|
+
const {
|
|
32155
|
+
strings,
|
|
32156
|
+
locale
|
|
32157
|
+
} = usePerseusI18n();
|
|
32158
|
+
const id = React.useId();
|
|
32159
|
+
const pointsOffArray = Array(points.length).fill("off");
|
|
32160
|
+
// When moving an element, set its aria-live to "polite" and the others
|
|
32161
|
+
// to "off". Otherwise, other connected elements that move at the same
|
|
32162
|
+
// time might override the currently focused element's aria live.
|
|
32163
|
+
const [ariaLives, setAriaLives] = React.useState([
|
|
32164
|
+
// First one represents the aria-live value for the polygon itself.
|
|
32165
|
+
"off",
|
|
32166
|
+
// The rest represent the points.
|
|
32167
|
+
...pointsOffArray]);
|
|
32478
32168
|
const lines = getLines(points);
|
|
32479
|
-
|
|
32169
|
+
const polygonPointsNumId = id + "-points-num";
|
|
32170
|
+
const polygonPointsId = id + "-points";
|
|
32171
|
+
|
|
32172
|
+
// Aria label srings
|
|
32173
|
+
const {
|
|
32174
|
+
srPolygonGraph,
|
|
32175
|
+
srPolygonGraphPointsNum,
|
|
32176
|
+
srPolygonGraphPoints,
|
|
32177
|
+
srPolygonElementsNum
|
|
32178
|
+
} = describePolygonGraph(statefulProps.graphState, {
|
|
32179
|
+
strings,
|
|
32180
|
+
locale
|
|
32181
|
+
}, statefulProps.graphConfig.markings);
|
|
32182
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32183
|
+
// Outer graph minimal description
|
|
32184
|
+
"aria-label": srPolygonGraph,
|
|
32185
|
+
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
32186
|
+
}, /*#__PURE__*/React.createElement(Polygon, {
|
|
32480
32187
|
points: [...points],
|
|
32481
32188
|
color: "var(--movable-line-stroke-color)",
|
|
32482
32189
|
svgPolygonProps: {
|
|
@@ -32501,13 +32208,17 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32501
32208
|
snapTo: snapTo
|
|
32502
32209
|
});
|
|
32503
32210
|
}), showSides && lines.map(([start, end], i) => {
|
|
32211
|
+
// Use x and y to find the position of the label
|
|
32504
32212
|
const [x, y] = vec.midpoint(start, end);
|
|
32505
|
-
const length =
|
|
32213
|
+
const length = vec.dist(start, end);
|
|
32214
|
+
// Check if the length needs to indicate
|
|
32215
|
+
// that it's an approximation.
|
|
32216
|
+
const isApprox = !Number.isInteger(length);
|
|
32506
32217
|
return /*#__PURE__*/React.createElement(TextLabel, {
|
|
32507
32218
|
key: "side-" + i,
|
|
32508
32219
|
x: x,
|
|
32509
32220
|
y: y
|
|
32510
|
-
},
|
|
32221
|
+
}, isApprox ? `≈ ${length.toFixed(snapTo === "sides" ? 0 : 1)}` : length);
|
|
32511
32222
|
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
32512
32223
|
points: [...points],
|
|
32513
32224
|
color: "transparent",
|
|
@@ -32527,27 +32238,80 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32527
32238
|
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
32528
32239
|
},
|
|
32529
32240
|
// Required for lines to darken on focus
|
|
32530
|
-
onFocus: () =>
|
|
32241
|
+
onFocus: () => {
|
|
32242
|
+
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
32243
|
+
setAriaLives(() => ["polite", ...pointsOffArray]);
|
|
32244
|
+
},
|
|
32531
32245
|
// Required for line weighting to update on blur. Without this,
|
|
32532
32246
|
// the user has to hover over the shape for it to update
|
|
32533
32247
|
onBlur: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
|
|
32534
|
-
className: "movable-polygon"
|
|
32535
|
-
|
|
32536
|
-
|
|
32537
|
-
|
|
32538
|
-
|
|
32539
|
-
point: point,
|
|
32540
|
-
sequenceNumber: i + 1,
|
|
32541
|
-
onMove: destination => {
|
|
32542
|
-
const now = Date.now();
|
|
32543
|
-
const targetFPS = 40;
|
|
32544
|
-
const moveThresholdTime = 1000 / targetFPS;
|
|
32545
|
-
if (now - lastMoveTimeRef.current > moveThresholdTime) {
|
|
32546
|
-
dispatch(actions.polygon.movePoint(i, destination));
|
|
32547
|
-
lastMoveTimeRef.current = now;
|
|
32548
|
-
}
|
|
32248
|
+
className: "movable-polygon",
|
|
32249
|
+
// Accessibility-related fields
|
|
32250
|
+
role: "button",
|
|
32251
|
+
"aria-label": srPolygonGraphPoints ? `${srPolygonElementsNum} ${srPolygonGraphPoints}` : srPolygonElementsNum,
|
|
32252
|
+
"aria-live": ariaLives[0]
|
|
32549
32253
|
}
|
|
32550
|
-
}))
|
|
32254
|
+
}), points.map((point, i) => {
|
|
32255
|
+
const angleId = `${id}-angle-${i}`;
|
|
32256
|
+
const side1Id = `${id}-point-${i}-side-1`;
|
|
32257
|
+
const side2Id = `${id}-point-${i}-side-2`;
|
|
32258
|
+
|
|
32259
|
+
// Limited polygons always have an angle at each vertex.
|
|
32260
|
+
const angle = getAngleFromPoints(points, i);
|
|
32261
|
+
const angleDegree = angle ? convertRadiansToDegrees(angle) : null;
|
|
32262
|
+
|
|
32263
|
+
// Limited polygons always two sides attached to each vertex.
|
|
32264
|
+
const sidesArray = getSideLengthsFromPoints(points, i);
|
|
32265
|
+
const {
|
|
32266
|
+
pointIndex: point1Index,
|
|
32267
|
+
sideLength: side1Length
|
|
32268
|
+
} = sidesArray[0];
|
|
32269
|
+
const {
|
|
32270
|
+
pointIndex: point2Index,
|
|
32271
|
+
sideLength: side2Length
|
|
32272
|
+
} = sidesArray[1];
|
|
32273
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32274
|
+
key: "point-" + i
|
|
32275
|
+
}, /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
32276
|
+
ariaDescribedBy: `${angleId} ${side1Id} ${side2Id}`,
|
|
32277
|
+
ariaLive: ariaLives[i + 1],
|
|
32278
|
+
constrain: constrain,
|
|
32279
|
+
point: point,
|
|
32280
|
+
sequenceNumber: i + 1,
|
|
32281
|
+
onMove: destination => {
|
|
32282
|
+
const now = Date.now();
|
|
32283
|
+
const targetFPS = 40;
|
|
32284
|
+
const moveThresholdTime = 1000 / targetFPS;
|
|
32285
|
+
if (now - lastMoveTimeRef.current > moveThresholdTime) {
|
|
32286
|
+
dispatch(actions.polygon.movePoint(i, destination));
|
|
32287
|
+
lastMoveTimeRef.current = now;
|
|
32288
|
+
}
|
|
32289
|
+
},
|
|
32290
|
+
onFocus: () => {
|
|
32291
|
+
const newPointAriaLives = [...pointsOffArray];
|
|
32292
|
+
newPointAriaLives[i] = "polite";
|
|
32293
|
+
// Whole polygon is "off", and the current
|
|
32294
|
+
// point is "polite".
|
|
32295
|
+
setAriaLives(["off", ...newPointAriaLives]);
|
|
32296
|
+
}
|
|
32297
|
+
}), angleDegree && /*#__PURE__*/React.createElement("g", {
|
|
32298
|
+
id: angleId
|
|
32299
|
+
}, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
|
|
32300
|
+
angle: angleDegree
|
|
32301
|
+
}) : strings.srPolygonPointAngleApprox({
|
|
32302
|
+
angle: srFormatNumber(angleDegree, locale, 1)
|
|
32303
|
+
})), /*#__PURE__*/React.createElement("g", {
|
|
32304
|
+
id: side1Id
|
|
32305
|
+
}, getPolygonSideString(side1Length, point1Index, strings, locale)), /*#__PURE__*/React.createElement("g", {
|
|
32306
|
+
id: side2Id
|
|
32307
|
+
}, getPolygonSideString(side2Length, point2Index, strings, locale)));
|
|
32308
|
+
}), /*#__PURE__*/React.createElement("g", {
|
|
32309
|
+
id: polygonPointsNumId,
|
|
32310
|
+
style: a11y.srOnly
|
|
32311
|
+
}, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement("g", {
|
|
32312
|
+
id: polygonPointsId,
|
|
32313
|
+
style: a11y.srOnly
|
|
32314
|
+
}, srPolygonGraphPoints));
|
|
32551
32315
|
};
|
|
32552
32316
|
const UnlimitedPolygonGraph = statefulProps => {
|
|
32553
32317
|
const {
|
|
@@ -32562,6 +32326,19 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
32562
32326
|
coords,
|
|
32563
32327
|
closedPolygon
|
|
32564
32328
|
} = statefulProps.graphState;
|
|
32329
|
+
const {
|
|
32330
|
+
strings,
|
|
32331
|
+
locale
|
|
32332
|
+
} = usePerseusI18n();
|
|
32333
|
+
const id = React.useId();
|
|
32334
|
+
const polygonPointsNumId = id + "-points-num";
|
|
32335
|
+
const polygonPointsId = id + "-points";
|
|
32336
|
+
|
|
32337
|
+
// When moving an element, set its aria-live to "polite" and the others
|
|
32338
|
+
// to "off". Otherwise, other connected elements that move at the same
|
|
32339
|
+
// time might override the currently focused element's aria live.
|
|
32340
|
+
const pointsOffArray = Array(points.length).fill("off");
|
|
32341
|
+
const [ariaLives, setAriaLives] = React.useState(pointsOffArray);
|
|
32565
32342
|
|
|
32566
32343
|
// If the polygon is closed, return a LimitedPolygon component.
|
|
32567
32344
|
if (closedPolygon) {
|
|
@@ -32575,7 +32352,22 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
32575
32352
|
} = graphConfig;
|
|
32576
32353
|
const widthPx = graphDimensionsInPixels[0];
|
|
32577
32354
|
const heightPx = graphDimensionsInPixels[1];
|
|
32578
|
-
|
|
32355
|
+
|
|
32356
|
+
// Aria label srings
|
|
32357
|
+
const emptyGraph = coords.length === 0;
|
|
32358
|
+
const {
|
|
32359
|
+
srPolygonGraph,
|
|
32360
|
+
srPolygonGraphPointsNum,
|
|
32361
|
+
srPolygonGraphPoints
|
|
32362
|
+
} = describePolygonGraph(statefulProps.graphState, {
|
|
32363
|
+
strings,
|
|
32364
|
+
locale
|
|
32365
|
+
}, statefulProps.graphConfig.markings);
|
|
32366
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32367
|
+
// Outer graph minimal description
|
|
32368
|
+
"aria-label": emptyGraph ? strings.srUnlimitedPolygonEmpty : srPolygonGraph,
|
|
32369
|
+
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
32370
|
+
}, /*#__PURE__*/React.createElement(Polyline, {
|
|
32579
32371
|
points: [...points],
|
|
32580
32372
|
color: "var(--movable-line-stroke-color)",
|
|
32581
32373
|
svgPolylineProps: {
|
|
@@ -32600,28 +32392,68 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
32600
32392
|
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
32601
32393
|
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
32602
32394
|
}
|
|
32603
|
-
}), coords.map((point, i) =>
|
|
32604
|
-
|
|
32605
|
-
|
|
32606
|
-
|
|
32607
|
-
|
|
32608
|
-
|
|
32609
|
-
|
|
32610
|
-
|
|
32611
|
-
|
|
32612
|
-
|
|
32613
|
-
|
|
32614
|
-
|
|
32615
|
-
// If the point being clicked is the first point and
|
|
32616
|
-
// there's enough non-duplicated points to form
|
|
32617
|
-
// a polygon (3 or more), close the shape before
|
|
32618
|
-
// setting focus.
|
|
32619
|
-
if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
|
|
32620
|
-
dispatch(actions.polygon.closePolygon());
|
|
32621
|
-
}
|
|
32622
|
-
dispatch(actions.polygon.clickPoint(i));
|
|
32395
|
+
}), coords.map((point, i) => {
|
|
32396
|
+
const angleId = `${id}-angle-${i}`;
|
|
32397
|
+
let sideIds = "";
|
|
32398
|
+
|
|
32399
|
+
// This point only has an angle if it's not the first
|
|
32400
|
+
// or last point in this open polygon.
|
|
32401
|
+
const hasAngle = i > 0 && i < coords.length - 1;
|
|
32402
|
+
const angle = hasAngle ? getAngleFromPoints(points, i) : null;
|
|
32403
|
+
const angleDegree = angle ? convertRadiansToDegrees(angle) : null;
|
|
32404
|
+
const sidesArray = getSideLengthsFromPoints(points, i, true);
|
|
32405
|
+
for (let sideIndex = 0; sideIndex < sidesArray.length; sideIndex++) {
|
|
32406
|
+
sideIds += `${id}-point-${i}-side-${sideIndex} `;
|
|
32623
32407
|
}
|
|
32624
|
-
|
|
32408
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32409
|
+
key: "point-" + i
|
|
32410
|
+
}, /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
32411
|
+
ariaDescribedBy: `${angleId} ${sideIds}`,
|
|
32412
|
+
ariaLive: ariaLives[i],
|
|
32413
|
+
point: point,
|
|
32414
|
+
sequenceNumber: i + 1,
|
|
32415
|
+
onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
|
|
32416
|
+
ref: ref => {
|
|
32417
|
+
pointsRef.current[i] = ref;
|
|
32418
|
+
},
|
|
32419
|
+
onFocus: () => {
|
|
32420
|
+
dispatch(actions.polygon.focusPoint(i));
|
|
32421
|
+
const newPointAriaLives = [...pointsOffArray];
|
|
32422
|
+
newPointAriaLives[i] = "polite";
|
|
32423
|
+
// Whole polygon is "off", and the current
|
|
32424
|
+
// point is "polite".
|
|
32425
|
+
setAriaLives([...newPointAriaLives]);
|
|
32426
|
+
},
|
|
32427
|
+
onClick: () => {
|
|
32428
|
+
// If the point being clicked is the first point and
|
|
32429
|
+
// there's enough non-duplicated points to form
|
|
32430
|
+
// a polygon (3 or more), close the shape before
|
|
32431
|
+
// setting focus.
|
|
32432
|
+
if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
|
|
32433
|
+
dispatch(actions.polygon.closePolygon());
|
|
32434
|
+
}
|
|
32435
|
+
dispatch(actions.polygon.clickPoint(i));
|
|
32436
|
+
}
|
|
32437
|
+
}), angleDegree && /*#__PURE__*/React.createElement("g", {
|
|
32438
|
+
id: angleId
|
|
32439
|
+
}, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
|
|
32440
|
+
angle: angleDegree
|
|
32441
|
+
}) : strings.srPolygonPointAngleApprox({
|
|
32442
|
+
angle: srFormatNumber(angleDegree, locale, 1)
|
|
32443
|
+
})), sidesArray.map(({
|
|
32444
|
+
pointIndex,
|
|
32445
|
+
sideLength
|
|
32446
|
+
}, j) => /*#__PURE__*/React.createElement("g", {
|
|
32447
|
+
key: `${id}-point-${i}-side-${j}`,
|
|
32448
|
+
id: `${id}-point-${i}-side-${j}`
|
|
32449
|
+
}, getPolygonSideString(sideLength, pointIndex, strings, locale))));
|
|
32450
|
+
}), coords.length > 0 && /*#__PURE__*/React.createElement("g", {
|
|
32451
|
+
id: polygonPointsNumId,
|
|
32452
|
+
style: a11y.srOnly
|
|
32453
|
+
}, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement("g", {
|
|
32454
|
+
id: polygonPointsId,
|
|
32455
|
+
style: a11y.srOnly
|
|
32456
|
+
}, srPolygonGraphPoints));
|
|
32625
32457
|
};
|
|
32626
32458
|
function getLines(points) {
|
|
32627
32459
|
return points.map((point, i) => {
|
|
@@ -32643,6 +32475,54 @@ const hasFocusVisible = element => {
|
|
|
32643
32475
|
return matches(":focus");
|
|
32644
32476
|
}
|
|
32645
32477
|
};
|
|
32478
|
+
function getPolygonGraphDescription(state, i18n, markings) {
|
|
32479
|
+
const strings = describePolygonGraph(state, i18n, markings);
|
|
32480
|
+
return strings.srPolygonInteractiveElements;
|
|
32481
|
+
}
|
|
32482
|
+
function describePolygonGraph(state, i18n, markings) {
|
|
32483
|
+
const {
|
|
32484
|
+
strings,
|
|
32485
|
+
locale
|
|
32486
|
+
} = i18n;
|
|
32487
|
+
const {
|
|
32488
|
+
coords
|
|
32489
|
+
} = state;
|
|
32490
|
+
const isCoordinatePlane = markings === "axes" || markings === "graph";
|
|
32491
|
+
const hasOnePoint = coords.length === 1;
|
|
32492
|
+
|
|
32493
|
+
// Figure out graph aria label based on markings.
|
|
32494
|
+
const srPolygonGraph = isCoordinatePlane ? strings.srPolygonGraphCoordinatePlane : strings.srPolygonGraph;
|
|
32495
|
+
const srPolygonGraphPointsNum = hasOnePoint ? strings.srPolygonGraphPointsOne : strings.srPolygonGraphPointsNum({
|
|
32496
|
+
num: coords.length
|
|
32497
|
+
});
|
|
32498
|
+
let srPolygonGraphPoints;
|
|
32499
|
+
// Figure out graph description based on markings.
|
|
32500
|
+
// If the graph is not on a coordinate plane, we should not include
|
|
32501
|
+
// the points' coordinates in the description.
|
|
32502
|
+
if (isCoordinatePlane) {
|
|
32503
|
+
const pointsString = coords.map((coord, i) => {
|
|
32504
|
+
return strings.srPointAtCoordinates({
|
|
32505
|
+
num: i + 1,
|
|
32506
|
+
x: srFormatNumber(coord[0], locale),
|
|
32507
|
+
y: srFormatNumber(coord[1], locale)
|
|
32508
|
+
});
|
|
32509
|
+
});
|
|
32510
|
+
srPolygonGraphPoints = pointsString.join(" ");
|
|
32511
|
+
}
|
|
32512
|
+
const srPolygonElementsNum = hasOnePoint ? strings.srPolygonElementsOne : strings.srPolygonElementsNum({
|
|
32513
|
+
num: coords.length
|
|
32514
|
+
});
|
|
32515
|
+
const srPolygonInteractiveElements = coords.length > 0 ? strings.srInteractiveElements({
|
|
32516
|
+
elements: [srPolygonElementsNum, srPolygonGraphPoints].join(" ")
|
|
32517
|
+
}) : null;
|
|
32518
|
+
return {
|
|
32519
|
+
srPolygonGraph,
|
|
32520
|
+
srPolygonGraphPointsNum,
|
|
32521
|
+
srPolygonGraphPoints,
|
|
32522
|
+
srPolygonElementsNum,
|
|
32523
|
+
srPolygonInteractiveElements
|
|
32524
|
+
};
|
|
32525
|
+
}
|
|
32646
32526
|
|
|
32647
32527
|
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
32648
32528
|
return {
|
|
@@ -33342,7 +33222,8 @@ const MafsGraph = props => {
|
|
|
33342
33222
|
} = renderGraphElements({
|
|
33343
33223
|
state,
|
|
33344
33224
|
dispatch,
|
|
33345
|
-
i18n
|
|
33225
|
+
i18n,
|
|
33226
|
+
markings: props.markings
|
|
33346
33227
|
});
|
|
33347
33228
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
33348
33229
|
value: {
|
|
@@ -33377,7 +33258,7 @@ const MafsGraph = props => {
|
|
|
33377
33258
|
handleKeyboardEvent(event, state, dispatch);
|
|
33378
33259
|
},
|
|
33379
33260
|
"aria-label": fullGraphAriaLabel,
|
|
33380
|
-
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) &&
|
|
33261
|
+
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
|
|
33381
33262
|
ref: graphRef,
|
|
33382
33263
|
tabIndex: 0,
|
|
33383
33264
|
onFocus: event => {
|
|
@@ -33403,7 +33284,19 @@ const MafsGraph = props => {
|
|
|
33403
33284
|
bottom: 0,
|
|
33404
33285
|
left: 0
|
|
33405
33286
|
}
|
|
33406
|
-
}, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(
|
|
33287
|
+
}, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(View
|
|
33288
|
+
// If we have locked figures, we set aria-hidden
|
|
33289
|
+
// to false so that screen readers can read the
|
|
33290
|
+
// locked figures. If we don't have any locked
|
|
33291
|
+
// figures, we need to set aria-hidden to true so
|
|
33292
|
+
// that screen readers don't read this Mafs element
|
|
33293
|
+
// as an empty image.
|
|
33294
|
+
// Note: Adding this in a wrapping View element
|
|
33295
|
+
// so that `aria-hidden` is a valid property.
|
|
33296
|
+
// It does not work on `Mafs` or svg elements.
|
|
33297
|
+
, {
|
|
33298
|
+
"aria-hidden": props.lockedFigures && props.lockedFigures.length > 0 ? false : true
|
|
33299
|
+
}, /*#__PURE__*/React.createElement(Mafs, {
|
|
33407
33300
|
preserveAspectRatio: false,
|
|
33408
33301
|
viewBox: {
|
|
33409
33302
|
x: state.range[X],
|
|
@@ -33423,10 +33316,10 @@ const MafsGraph = props => {
|
|
|
33423
33316
|
height: height
|
|
33424
33317
|
})),
|
|
33425
33318
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
33426
|
-
(props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes,
|
|
33319
|
+
(props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), props.lockedFigures && props.lockedFigures.length > 0 && /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
33427
33320
|
lockedFigures: props.lockedFigures,
|
|
33428
33321
|
range: state.range
|
|
33429
|
-
}))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
33322
|
+
})))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
33430
33323
|
lockedFigures: props.lockedFigures
|
|
33431
33324
|
}), /*#__PURE__*/React.createElement(View, {
|
|
33432
33325
|
style: {
|
|
@@ -33721,7 +33614,8 @@ const renderGraphElements = props => {
|
|
|
33721
33614
|
const {
|
|
33722
33615
|
state,
|
|
33723
33616
|
dispatch,
|
|
33724
|
-
i18n
|
|
33617
|
+
i18n,
|
|
33618
|
+
markings
|
|
33725
33619
|
} = props;
|
|
33726
33620
|
const {
|
|
33727
33621
|
type
|
|
@@ -33738,7 +33632,7 @@ const renderGraphElements = props => {
|
|
|
33738
33632
|
case "ray":
|
|
33739
33633
|
return renderRayGraph(state, dispatch);
|
|
33740
33634
|
case "polygon":
|
|
33741
|
-
return renderPolygonGraph(state, dispatch);
|
|
33635
|
+
return renderPolygonGraph(state, dispatch, i18n, markings);
|
|
33742
33636
|
case "point":
|
|
33743
33637
|
return renderPointGraph(state, dispatch);
|
|
33744
33638
|
case "circle":
|
|
@@ -36375,26 +36269,17 @@ class InteractiveGraph extends React.Component {
|
|
|
36375
36269
|
}
|
|
36376
36270
|
render() {
|
|
36377
36271
|
var _this$props$apiOption;
|
|
36378
|
-
|
|
36379
|
-
const
|
|
36380
|
-
|
|
36381
|
-
|
|
36382
|
-
|
|
36383
|
-
|
|
36384
|
-
|
|
36385
|
-
|
|
36386
|
-
|
|
36387
|
-
|
|
36388
|
-
|
|
36389
|
-
snapStep: snapStep,
|
|
36390
|
-
box: box,
|
|
36391
|
-
showTooltips: !!this.props.showTooltips,
|
|
36392
|
-
readOnly: (_this$props$apiOption3 = this.props.apiOptions) == null ? void 0 : _this$props$apiOption3.readOnly
|
|
36393
|
-
}));
|
|
36394
|
-
}
|
|
36395
|
-
return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
|
|
36396
|
-
ref: this.legacyGraphRef
|
|
36397
|
-
}, this.props));
|
|
36272
|
+
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
36273
|
+
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
36274
|
+
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
36275
|
+
return /*#__PURE__*/React.createElement(StatefulMafsGraph, _extends({}, this.props, {
|
|
36276
|
+
ref: this.mafsRef,
|
|
36277
|
+
gridStep: gridStep,
|
|
36278
|
+
snapStep: snapStep,
|
|
36279
|
+
box: box,
|
|
36280
|
+
showTooltips: !!this.props.showTooltips,
|
|
36281
|
+
readOnly: (_this$props$apiOption = this.props.apiOptions) == null ? void 0 : _this$props$apiOption.readOnly
|
|
36282
|
+
}));
|
|
36398
36283
|
}
|
|
36399
36284
|
|
|
36400
36285
|
/**
|
|
@@ -36710,7 +36595,7 @@ class InteractiveGraph extends React.Component {
|
|
|
36710
36595
|
}
|
|
36711
36596
|
}
|
|
36712
36597
|
|
|
36713
|
-
//
|
|
36598
|
+
// We don't need to change any of the original props for static mode
|
|
36714
36599
|
InteractiveGraph.defaultProps = {
|
|
36715
36600
|
labels: ["x", "y"],
|
|
36716
36601
|
range: [[-10, 10], [-10, 10]],
|
|
@@ -36723,37 +36608,13 @@ InteractiveGraph.defaultProps = {
|
|
|
36723
36608
|
type: "linear"
|
|
36724
36609
|
}
|
|
36725
36610
|
};
|
|
36726
|
-
function shouldUseMafs(mafsFlags, graph) {
|
|
36727
|
-
if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
|
|
36728
|
-
return false;
|
|
36729
|
-
}
|
|
36730
|
-
switch (graph.type) {
|
|
36731
|
-
case "none":
|
|
36732
|
-
return true;
|
|
36733
|
-
case "point":
|
|
36734
|
-
if (graph.numPoints === UNLIMITED) {
|
|
36735
|
-
return Boolean(mafsFlags["unlimited-point"]);
|
|
36736
|
-
}
|
|
36737
|
-
return Boolean(mafsFlags["point"]);
|
|
36738
|
-
case "polygon":
|
|
36739
|
-
if (graph.numSides === UNLIMITED) {
|
|
36740
|
-
return Boolean(mafsFlags["unlimited-polygon"]);
|
|
36741
|
-
}
|
|
36742
|
-
return Boolean(mafsFlags["polygon"]);
|
|
36743
|
-
default:
|
|
36744
|
-
return Boolean(mafsFlags[graph.type]);
|
|
36745
|
-
}
|
|
36746
|
-
}
|
|
36747
|
-
// We don't need to change any of the original props for static mode
|
|
36748
36611
|
const staticTransform$3 = _.identity;
|
|
36749
36612
|
var InteractiveGraph$1 = {
|
|
36750
36613
|
name: "interactive-graph",
|
|
36751
36614
|
displayName: "Interactive graph (Assessments only)",
|
|
36752
36615
|
widget: InteractiveGraph,
|
|
36753
36616
|
staticTransform: staticTransform$3,
|
|
36754
|
-
|
|
36755
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInteractiveGraphUserInput'.
|
|
36756
|
-
scorer: scoreInteractiveGraph
|
|
36617
|
+
getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
|
|
36757
36618
|
};
|
|
36758
36619
|
|
|
36759
36620
|
// TODO: can we use WB typography?
|
|
@@ -37695,9 +37556,7 @@ var LabelImage$1 = {
|
|
|
37695
37556
|
widget: LabelImageWithDependencies,
|
|
37696
37557
|
accessible: true,
|
|
37697
37558
|
isLintable: true,
|
|
37698
|
-
|
|
37699
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusLabelImageUserInput'.
|
|
37700
|
-
scorer: scoreLabelImage
|
|
37559
|
+
getPublicWidgetOptions: getLabelImagePublicWidgetOptions
|
|
37701
37560
|
};
|
|
37702
37561
|
|
|
37703
37562
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
@@ -37930,7 +37789,7 @@ class Draggable extends React.Component {
|
|
|
37930
37789
|
// We need to keep backwards compatbility with rules specified directly
|
|
37931
37790
|
// in CSS. Hence the hacky tacking on of manual classNames.
|
|
37932
37791
|
// See sortable.less for details.
|
|
37933
|
-
let className = css(styles$8.card, styles$8.draggable, layout === "horizontal" && styles$8.horizontalCard, layout === "vertical" && styles$8.verticalCard, type === ItemState.DRAGGING && styles$8.dragging, type === ItemState.DISABLED && styles$8.disabled, !includePadding && styles$8.unpaddedCard) + " " +
|
|
37792
|
+
let className = css(styles$8.card, styles$8.draggable, layout === "horizontal" && styles$8.horizontalCard, layout === "vertical" && styles$8.verticalCard, type === ItemState.DRAGGING && styles$8.dragging, type === ItemState.DISABLED && styles$8.disabled, !includePadding && styles$8.unpaddedCard) + " " + "perseus-sortable-draggable";
|
|
37934
37793
|
if (!includePadding) {
|
|
37935
37794
|
className += " perseus-sortable-draggable-unpadded";
|
|
37936
37795
|
}
|
|
@@ -38631,10 +38490,7 @@ var Matcher$1 = {
|
|
|
38631
38490
|
name: "matcher",
|
|
38632
38491
|
displayName: "Matcher (two column)",
|
|
38633
38492
|
widget: Matcher,
|
|
38634
|
-
isLintable: true
|
|
38635
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
38636
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatcherUserInput'.
|
|
38637
|
-
scorer: scoreMatcher
|
|
38493
|
+
isLintable: true
|
|
38638
38494
|
};
|
|
38639
38495
|
|
|
38640
38496
|
const getPromptJSON$8 = (renderProps, userInput) => {
|
|
@@ -39039,12 +38895,7 @@ var Matrix$1 = {
|
|
|
39039
38895
|
transform: propTransform$1,
|
|
39040
38896
|
staticTransform: staticTransform$2,
|
|
39041
38897
|
isLintable: true,
|
|
39042
|
-
|
|
39043
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39044
|
-
scorer: scoreMatrix,
|
|
39045
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
39046
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39047
|
-
validator: validateMatrix
|
|
38898
|
+
getPublicWidgetOptions: getMatrixPublicWidgetOptions
|
|
39048
38899
|
};
|
|
39049
38900
|
|
|
39050
38901
|
const getPromptJSON$7 = () => {
|
|
@@ -39124,7 +38975,7 @@ class Measurer extends React.Component {
|
|
|
39124
38975
|
// TODO(scottgrant): This isn't a11y-friendly! We should insist on
|
|
39125
38976
|
// finding some valid alt text when this widget is used.
|
|
39126
38977
|
return /*#__PURE__*/React.createElement("div", {
|
|
39127
|
-
className: "perseus-widget perseus-widget-measurer " + "graphie-container
|
|
38978
|
+
className: "perseus-widget perseus-widget-measurer " + "graphie-container blank-background",
|
|
39128
38979
|
style: {
|
|
39129
38980
|
width: this.props.box[0],
|
|
39130
38981
|
height: this.props.box[1]
|
|
@@ -39161,9 +39012,7 @@ var Measurer$1 = {
|
|
|
39161
39012
|
hidden: true,
|
|
39162
39013
|
widget: Measurer,
|
|
39163
39014
|
version: measurerLogic.version,
|
|
39164
|
-
propUpgrades: measurerLogic.widgetOptionsUpgrades
|
|
39165
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
39166
|
-
scorer: () => scoreNoop(1)
|
|
39015
|
+
propUpgrades: measurerLogic.widgetOptionsUpgrades
|
|
39167
39016
|
};
|
|
39168
39017
|
|
|
39169
39018
|
/**
|
|
@@ -40051,8 +39900,7 @@ var Molecule$1 = {
|
|
|
40051
39900
|
name: "molecule-renderer",
|
|
40052
39901
|
displayName: "Molecule renderer",
|
|
40053
39902
|
hidden: true,
|
|
40054
|
-
widget: MoleculeWidget
|
|
40055
|
-
scorer: () => scoreNoop()
|
|
39903
|
+
widget: MoleculeWidget
|
|
40056
39904
|
};
|
|
40057
39905
|
|
|
40058
39906
|
const getPromptJSON$6 = (renderProps, userInput) => {
|
|
@@ -40621,12 +40469,7 @@ var NumberLine$1 = {
|
|
|
40621
40469
|
widget: NumberLine,
|
|
40622
40470
|
transform: numberLineTransform,
|
|
40623
40471
|
staticTransform: staticTransform$1,
|
|
40624
|
-
|
|
40625
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40626
|
-
scorer: scoreNumberLine,
|
|
40627
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
40628
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40629
|
-
validator: validateNumberLine
|
|
40472
|
+
getPublicWidgetOptions: getNumberLinePublicWidgetOptions
|
|
40630
40473
|
};
|
|
40631
40474
|
|
|
40632
40475
|
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
@@ -40644,7 +40487,7 @@ const getPromptJSON$5 = (renderProps, userInput) => {
|
|
|
40644
40487
|
class PlaceholderCard extends React.Component {
|
|
40645
40488
|
render() {
|
|
40646
40489
|
return /*#__PURE__*/React.createElement("div", {
|
|
40647
|
-
className: "card-wrap
|
|
40490
|
+
className: "card-wrap",
|
|
40648
40491
|
style: {
|
|
40649
40492
|
width: this.props.width
|
|
40650
40493
|
}
|
|
@@ -40659,7 +40502,7 @@ class PlaceholderCard extends React.Component {
|
|
|
40659
40502
|
class DragHintCard extends React.Component {
|
|
40660
40503
|
render() {
|
|
40661
40504
|
return /*#__PURE__*/React.createElement("div", {
|
|
40662
|
-
className: "card-wrap
|
|
40505
|
+
className: "card-wrap"
|
|
40663
40506
|
}, /*#__PURE__*/React.createElement("div", {
|
|
40664
40507
|
className: "card drag-hint"
|
|
40665
40508
|
}));
|
|
@@ -40795,7 +40638,7 @@ class Card extends React.Component {
|
|
|
40795
40638
|
const rendererProps = _.pick(this.props, "content");
|
|
40796
40639
|
const onMouseDown = this.props.animating ? $.noop : this.onMouseDown;
|
|
40797
40640
|
return /*#__PURE__*/React.createElement("div", {
|
|
40798
|
-
className: "card-wrap
|
|
40641
|
+
className: "card-wrap",
|
|
40799
40642
|
style: style,
|
|
40800
40643
|
onMouseDown: onMouseDown,
|
|
40801
40644
|
onTouchStart: onMouseDown,
|
|
@@ -41166,7 +41009,7 @@ class Orderer extends React.Component {
|
|
|
41166
41009
|
});
|
|
41167
41010
|
}, this));
|
|
41168
41011
|
return /*#__PURE__*/React.createElement("div", {
|
|
41169
|
-
className: "draggy-boxy-thing orderer " + "height-" + this.props.height + " " + "layout-" + this.props.layout + " " + "
|
|
41012
|
+
className: "draggy-boxy-thing orderer " + "height-" + this.props.height + " " + "layout-" + this.props.layout + " " + "blank-background " + "perseus-clearfix "
|
|
41170
41013
|
// eslint-disable-next-line react/no-string-refs
|
|
41171
41014
|
,
|
|
41172
41015
|
ref: "orderer"
|
|
@@ -41187,12 +41030,6 @@ var Orderer$1 = {
|
|
|
41187
41030
|
hidden: true,
|
|
41188
41031
|
widget: Orderer,
|
|
41189
41032
|
isLintable: true,
|
|
41190
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
41191
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41192
|
-
scorer: scoreOrderer,
|
|
41193
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
41194
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41195
|
-
validator: validateOrderer,
|
|
41196
41033
|
getPublicWidgetOptions: getOrdererPublicWidgetOptions
|
|
41197
41034
|
};
|
|
41198
41035
|
|
|
@@ -41231,15 +41068,12 @@ PassageRefTarget.defaultProps = {
|
|
|
41231
41068
|
var PassageRefTarget$1 = {
|
|
41232
41069
|
name: "passage-ref-target",
|
|
41233
41070
|
displayName: "PassageRefTarget",
|
|
41234
|
-
defaultAlignment: "inline",
|
|
41235
41071
|
widget: PassageRefTarget,
|
|
41236
41072
|
hidden: true,
|
|
41237
41073
|
transform: editorProps => {
|
|
41238
41074
|
return _.pick(editorProps, "content");
|
|
41239
41075
|
},
|
|
41240
|
-
isLintable: true
|
|
41241
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41242
|
-
scorer: () => scoreNoop()
|
|
41076
|
+
isLintable: true
|
|
41243
41077
|
};
|
|
41244
41078
|
|
|
41245
41079
|
const getPromptJSON$4 = () => {
|
|
@@ -42226,7 +42060,7 @@ class Plotter extends React.Component {
|
|
|
42226
42060
|
marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0
|
|
42227
42061
|
};
|
|
42228
42062
|
return /*#__PURE__*/React.createElement("div", {
|
|
42229
|
-
className: "perseus-widget-plotter graphie
|
|
42063
|
+
className: "perseus-widget-plotter graphie"
|
|
42230
42064
|
// eslint-disable-next-line react/no-string-refs
|
|
42231
42065
|
,
|
|
42232
42066
|
ref: "graphieDiv",
|
|
@@ -42257,12 +42091,7 @@ var Plotter$1 = {
|
|
|
42257
42091
|
hidden: true,
|
|
42258
42092
|
widget: Plotter,
|
|
42259
42093
|
staticTransform: staticTransform,
|
|
42260
|
-
|
|
42261
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42262
|
-
scorer: scorePlotter,
|
|
42263
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
42264
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42265
|
-
validator: validatePlotter
|
|
42094
|
+
getPublicWidgetOptions: getPlotterPublicWidgetOptions
|
|
42266
42095
|
};
|
|
42267
42096
|
|
|
42268
42097
|
const getPromptJSON$2 = () => {
|
|
@@ -42425,12 +42254,7 @@ var Sorter$1 = {
|
|
|
42425
42254
|
displayName: "Sorter",
|
|
42426
42255
|
widget: Sorter,
|
|
42427
42256
|
isLintable: true,
|
|
42428
|
-
|
|
42429
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42430
|
-
scorer: scoreSorter,
|
|
42431
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
42432
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42433
|
-
validator: validateSorter
|
|
42257
|
+
getPublicWidgetOptions: getSorterPublicWidgetOptions
|
|
42434
42258
|
};
|
|
42435
42259
|
|
|
42436
42260
|
const {
|
|
@@ -42650,12 +42474,7 @@ var Table$1 = {
|
|
|
42650
42474
|
transform: propTransform,
|
|
42651
42475
|
hidden: true,
|
|
42652
42476
|
isLintable: true,
|
|
42653
|
-
|
|
42654
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
42655
|
-
scorer: scoreTable,
|
|
42656
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
42657
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
42658
|
-
validator: validateTable
|
|
42477
|
+
getPublicWidgetOptions: getTablePublicWidgetOptions
|
|
42659
42478
|
};
|
|
42660
42479
|
|
|
42661
42480
|
const getPromptJSON = () => {
|
|
@@ -42815,11 +42634,7 @@ Video.contextType = PerseusI18nContext;
|
|
|
42815
42634
|
var Video$1 = {
|
|
42816
42635
|
name: "video",
|
|
42817
42636
|
displayName: "Video",
|
|
42818
|
-
|
|
42819
|
-
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
42820
|
-
widget: Video,
|
|
42821
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
42822
|
-
scorer: () => scoreNoop()
|
|
42637
|
+
widget: Video
|
|
42823
42638
|
};
|
|
42824
42639
|
|
|
42825
42640
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -42838,7 +42653,7 @@ const init = function init() {
|
|
|
42838
42653
|
|
|
42839
42654
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
42840
42655
|
const libName = "@khanacademy/perseus";
|
|
42841
|
-
const libVersion = "
|
|
42656
|
+
const libVersion = "53.1.0";
|
|
42842
42657
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
42843
42658
|
|
|
42844
42659
|
const apiVersion = {
|
|
@@ -43705,6 +43520,42 @@ const ref = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.cre
|
|
|
43705
43520
|
ref: ref
|
|
43706
43521
|
}))));
|
|
43707
43522
|
|
|
43523
|
+
function isCorrect(score) {
|
|
43524
|
+
return score.type === "points" && score.earned >= score.total;
|
|
43525
|
+
}
|
|
43526
|
+
function keScoreFromPerseusScore(score,
|
|
43527
|
+
// It's weird, but this is what we're passing it
|
|
43528
|
+
guess, state) {
|
|
43529
|
+
if (score.type === "points") {
|
|
43530
|
+
return {
|
|
43531
|
+
empty: false,
|
|
43532
|
+
correct: isCorrect(score),
|
|
43533
|
+
message: score.message,
|
|
43534
|
+
guess: guess,
|
|
43535
|
+
state: state
|
|
43536
|
+
};
|
|
43537
|
+
}
|
|
43538
|
+
if (score.type === "invalid") {
|
|
43539
|
+
return {
|
|
43540
|
+
empty: true,
|
|
43541
|
+
correct: false,
|
|
43542
|
+
message: score.message,
|
|
43543
|
+
suppressAlmostThere: score.suppressAlmostThere,
|
|
43544
|
+
guess: guess,
|
|
43545
|
+
state: state
|
|
43546
|
+
};
|
|
43547
|
+
}
|
|
43548
|
+
throw new PerseusError(
|
|
43549
|
+
// @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
|
|
43550
|
+
"Invalid score type: " + score.type, Errors.InvalidInput, {
|
|
43551
|
+
metadata: {
|
|
43552
|
+
score: JSON.stringify(score),
|
|
43553
|
+
guess: JSON.stringify(guess),
|
|
43554
|
+
state: JSON.stringify(state)
|
|
43555
|
+
}
|
|
43556
|
+
});
|
|
43557
|
+
}
|
|
43558
|
+
|
|
43708
43559
|
/**
|
|
43709
43560
|
* Traverses a {content, widgets, images} renderer props object,
|
|
43710
43561
|
* such as `itemData.question`
|
|
@@ -45081,5 +44932,5 @@ const ScoringUtil = {
|
|
|
45081
44932
|
* Types
|
|
45082
44933
|
*/
|
|
45083
44934
|
|
|
45084
|
-
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, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, preprocessTex, registerAllWidgetsForTesting,
|
|
44935
|
+
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, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, preprocessTex, registerAllWidgetsForTesting, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
45085
44936
|
//# sourceMappingURL=index.js.map
|