@khanacademy/perseus 53.0.0 → 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/es/index.js +524 -193
- 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.js +545 -1410
- package/dist/index.js.map +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/types.d.ts +2 -14
- package/dist/widgets/expression/expression.d.ts +0 -6
- package/dist/widgets/grapher/grapher.d.ts +2 -0
- package/dist/widgets/iframe/iframe.d.ts +2 -1
- package/dist/widgets/interactive-graph.d.ts +2 -1
- 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/matrix/matrix.d.ts +2 -1
- package/dist/widgets/plotter/plotter.d.ts +2 -0
- package/dist/widgets/radio/radio.d.ts +2 -1
- package/dist/widgets/table/table.d.ts +2 -0
- package/package.json +46 -46
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +0 -4
package/dist/es/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, getNumericInputPublicWidgetOptions, passageRefLogic, radioLogic, getCategorizerPublicWidgetOptions, getCSProgramPublicWidgetOptions, getDropdownPublicWidgetOptions, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getLabelImagePublicWidgetOptions, getMatrixSize, measurerLogic, getNumberLinePublicWidgetOptions, getOrdererPublicWidgetOptions, getSorterPublicWidgetOptions, addLibraryVersionToPerseusDebug, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
|
|
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';
|
|
@@ -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
|
|
|
@@ -9838,7 +9863,7 @@ class WrappedLine extends WrappedDrawing {
|
|
|
9838
9863
|
|
|
9839
9864
|
/* eslint-disable @babel/no-invalid-this */
|
|
9840
9865
|
const {
|
|
9841
|
-
clockwise: clockwise$
|
|
9866
|
+
clockwise: clockwise$2,
|
|
9842
9867
|
reverseVector: reverseVector$1
|
|
9843
9868
|
} = geometry;
|
|
9844
9869
|
function scaledDistanceFromAngle(angle) {
|
|
@@ -11076,7 +11101,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11076
11101
|
// Temporary objects
|
|
11077
11102
|
_.invoke(polygon.temp, "remove");
|
|
11078
11103
|
polygon.temp = [];
|
|
11079
|
-
const isClockwise = clockwise$
|
|
11104
|
+
const isClockwise = clockwise$2(polygon.coords);
|
|
11080
11105
|
|
|
11081
11106
|
// Update angle labels
|
|
11082
11107
|
if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
|
|
@@ -14467,7 +14492,7 @@ function sum(array) {
|
|
|
14467
14492
|
return memo + arg;
|
|
14468
14493
|
}, 0);
|
|
14469
14494
|
}
|
|
14470
|
-
function clockwise$
|
|
14495
|
+
function clockwise$1(points) {
|
|
14471
14496
|
const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
14472
14497
|
const areas = _.map(segments, function (segment) {
|
|
14473
14498
|
const p1 = segment[0];
|
|
@@ -14533,7 +14558,7 @@ const draw$1 = {
|
|
|
14533
14558
|
const graphie = this.graphie;
|
|
14534
14559
|
const self = this;
|
|
14535
14560
|
const coords = _.invoke(state.points, "coord");
|
|
14536
|
-
const isClockwise = clockwise$
|
|
14561
|
+
const isClockwise = clockwise$1(coords);
|
|
14537
14562
|
const n = coords.length;
|
|
14538
14563
|
|
|
14539
14564
|
// graphie.labelAngle and similar methods attempt to re-use the label
|
|
@@ -16270,9 +16295,29 @@ class Zoomable extends React.Component {
|
|
|
16270
16295
|
if (!this._isMounted) {
|
|
16271
16296
|
return;
|
|
16272
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
|
+
}
|
|
16273
16318
|
const parentBounds = {
|
|
16274
|
-
width:
|
|
16275
|
-
height:
|
|
16319
|
+
width: parentNode.offsetWidth,
|
|
16320
|
+
height: parentNode.offsetHeight
|
|
16276
16321
|
};
|
|
16277
16322
|
const childBounds = this.props.computeChildBounds(this._node, parentBounds);
|
|
16278
16323
|
|
|
@@ -19292,7 +19337,7 @@ function _objectWithoutPropertiesLoose(r, e) {
|
|
|
19292
19337
|
if (null == r) return {};
|
|
19293
19338
|
var t = {};
|
|
19294
19339
|
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
|
|
19295
|
-
if (e.
|
|
19340
|
+
if (-1 !== e.indexOf(n)) continue;
|
|
19296
19341
|
t[n] = r[n];
|
|
19297
19342
|
}
|
|
19298
19343
|
return t;
|
|
@@ -23061,8 +23106,8 @@ const styles$s = StyleSheet.create({
|
|
|
23061
23106
|
crossOutLine: {
|
|
23062
23107
|
// Center the icon within the container.
|
|
23063
23108
|
position: "absolute",
|
|
23064
|
-
top: `
|
|
23065
|
-
left: `
|
|
23109
|
+
top: `2px`,
|
|
23110
|
+
left: `2px`
|
|
23066
23111
|
}
|
|
23067
23112
|
});
|
|
23068
23113
|
|
|
@@ -23089,43 +23134,6 @@ function ChoiceInner(props) {
|
|
|
23089
23134
|
}
|
|
23090
23135
|
return /*#__PURE__*/React.createElement(InlineIcon, iconMinus);
|
|
23091
23136
|
}
|
|
23092
|
-
|
|
23093
|
-
// Handle dynamic styling of the multiple choice icon. Most
|
|
23094
|
-
// MC icon styles are constant, but we do allow the caller
|
|
23095
|
-
// to specify the selected color, and thus must control styles
|
|
23096
|
-
// related to the selected state dynamically.
|
|
23097
|
-
function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct) {
|
|
23098
|
-
let backgroundColor;
|
|
23099
|
-
let borderColor;
|
|
23100
|
-
let color$1;
|
|
23101
|
-
if (!showCorrectness && pressed) {
|
|
23102
|
-
borderColor = color.blue;
|
|
23103
|
-
color$1 = color.blue;
|
|
23104
|
-
backgroundColor = "transparent";
|
|
23105
|
-
} else if (checked) {
|
|
23106
|
-
const bg = showCorrectness && correct ? color.green : color.blue;
|
|
23107
|
-
color$1 = white;
|
|
23108
|
-
backgroundColor = bg;
|
|
23109
|
-
borderColor = bg;
|
|
23110
|
-
} else {
|
|
23111
|
-
borderColor = color.offBlack64;
|
|
23112
|
-
color$1 = color.offBlack64;
|
|
23113
|
-
}
|
|
23114
|
-
|
|
23115
|
-
// define shape
|
|
23116
|
-
let borderRadius;
|
|
23117
|
-
if (multipleSelect) {
|
|
23118
|
-
borderRadius = 3;
|
|
23119
|
-
} else {
|
|
23120
|
-
borderRadius = CHOICE_ICON_SIZE;
|
|
23121
|
-
}
|
|
23122
|
-
return {
|
|
23123
|
-
backgroundColor,
|
|
23124
|
-
borderColor,
|
|
23125
|
-
color: color$1,
|
|
23126
|
-
borderRadius
|
|
23127
|
-
};
|
|
23128
|
-
}
|
|
23129
23137
|
const ChoiceIcon = function ChoiceIcon(props) {
|
|
23130
23138
|
const {
|
|
23131
23139
|
checked,
|
|
@@ -23137,9 +23145,41 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
23137
23145
|
multipleSelect,
|
|
23138
23146
|
pos,
|
|
23139
23147
|
previouslyAnswered,
|
|
23148
|
+
// Used in "review mode"/"show rationale" to show that option was previously chosen
|
|
23140
23149
|
pressed
|
|
23141
23150
|
} = props;
|
|
23142
|
-
|
|
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
|
+
}
|
|
23143
23183
|
return /*#__PURE__*/React.createElement("div", {
|
|
23144
23184
|
className: css(styles$t.iconWrapper)
|
|
23145
23185
|
}, /*#__PURE__*/React.createElement(FocusRing, {
|
|
@@ -23147,9 +23187,8 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
23147
23187
|
visible: focused || hovered,
|
|
23148
23188
|
multipleSelect: multipleSelect
|
|
23149
23189
|
}, /*#__PURE__*/React.createElement("div", {
|
|
23150
|
-
style: dynamicStyles,
|
|
23151
23190
|
"data-testid": "choice-icon__library-choice-icon",
|
|
23152
|
-
className: css(
|
|
23191
|
+
className: css(...choiceStyling)
|
|
23153
23192
|
// used in BaseRadio editMode to check
|
|
23154
23193
|
// if we actually clicked on the radio icon
|
|
23155
23194
|
,
|
|
@@ -23161,7 +23200,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
23161
23200
|
showCorrectness: showCorrectness,
|
|
23162
23201
|
correct: correct
|
|
23163
23202
|
})))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
|
|
23164
|
-
color:
|
|
23203
|
+
color: crossOutColor
|
|
23165
23204
|
}));
|
|
23166
23205
|
};
|
|
23167
23206
|
const styles$r = StyleSheet.create({
|
|
@@ -23173,8 +23212,7 @@ const styles$r = StyleSheet.create({
|
|
|
23173
23212
|
alignItems: "center",
|
|
23174
23213
|
justifyContent: "center"
|
|
23175
23214
|
},
|
|
23176
|
-
|
|
23177
|
-
// Make the circle
|
|
23215
|
+
choiceBase: {
|
|
23178
23216
|
width: CHOICE_ICON_SIZE,
|
|
23179
23217
|
height: CHOICE_ICON_SIZE,
|
|
23180
23218
|
boxSizing: "border-box",
|
|
@@ -23186,30 +23224,48 @@ const styles$r = StyleSheet.create({
|
|
|
23186
23224
|
// "bold font family" so that characters which fall back to the default
|
|
23187
23225
|
// font get bolded too.
|
|
23188
23226
|
fontWeight: "bold",
|
|
23189
|
-
fontSize: 12,
|
|
23190
23227
|
// Center the icon wrapper.
|
|
23191
23228
|
display: "flex",
|
|
23192
23229
|
alignItems: "center",
|
|
23193
|
-
justifyContent: "center"
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
|
|
23197
|
-
|
|
23198
|
-
|
|
23199
|
-
lineHeight: "1px"
|
|
23200
|
-
},
|
|
23201
|
-
circleCorrect: {
|
|
23230
|
+
justifyContent: "center"
|
|
23231
|
+
},
|
|
23232
|
+
choiceHasLetter: {
|
|
23233
|
+
fontSize: 12
|
|
23234
|
+
},
|
|
23235
|
+
choiceHasIcon: {
|
|
23202
23236
|
fontSize: CHOICE_ICON_SIZE
|
|
23203
23237
|
},
|
|
23204
|
-
|
|
23205
|
-
|
|
23206
|
-
|
|
23207
|
-
|
|
23238
|
+
choiceIsChecked: {
|
|
23239
|
+
color: color.white
|
|
23240
|
+
},
|
|
23241
|
+
choiceIsUnchecked: {
|
|
23242
|
+
color: color.offBlack64
|
|
23208
23243
|
},
|
|
23209
|
-
|
|
23244
|
+
choiceCorrect: {
|
|
23245
|
+
backgroundColor: color.green,
|
|
23246
|
+
borderColor: color.green
|
|
23247
|
+
},
|
|
23248
|
+
choiceIncorrect: {
|
|
23210
23249
|
backgroundColor: color.red,
|
|
23211
|
-
borderColor: color.red
|
|
23212
|
-
|
|
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
|
|
23213
23269
|
}
|
|
23214
23270
|
});
|
|
23215
23271
|
|
|
@@ -24335,7 +24391,8 @@ var Radio = {
|
|
|
24335
24391
|
staticTransform: transform,
|
|
24336
24392
|
version: radioLogic.version,
|
|
24337
24393
|
propUpgrades: radioLogic.widgetOptionsUpgrades,
|
|
24338
|
-
isLintable: true
|
|
24394
|
+
isLintable: true,
|
|
24395
|
+
getPublicWidgetOptions: getRadioPublicWidgetOptions
|
|
24339
24396
|
};
|
|
24340
24397
|
|
|
24341
24398
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -26522,7 +26579,8 @@ var Grapher$1 = {
|
|
|
26522
26579
|
hidden: true,
|
|
26523
26580
|
widget: Grapher,
|
|
26524
26581
|
transform: propTransform$2,
|
|
26525
|
-
staticTransform: staticTransform$4
|
|
26582
|
+
staticTransform: staticTransform$4,
|
|
26583
|
+
getPublicWidgetOptions: getGrapherPublicWidgetOptions
|
|
26526
26584
|
};
|
|
26527
26585
|
|
|
26528
26586
|
const getPromptJSON$f = rendererJSON => {
|
|
@@ -26827,7 +26885,8 @@ var Iframe$1 = {
|
|
|
26827
26885
|
displayName: "Iframe (deprecated)",
|
|
26828
26886
|
widget: Iframe,
|
|
26829
26887
|
// Let's not expose it to all content creators yet
|
|
26830
|
-
hidden: true
|
|
26888
|
+
hidden: true,
|
|
26889
|
+
getPublicWidgetOptions: getIFramePublicWidgetOptions
|
|
26831
26890
|
};
|
|
26832
26891
|
|
|
26833
26892
|
const getPromptJSON$d = renderProps => {
|
|
@@ -29534,10 +29593,10 @@ function Vector(props) {
|
|
|
29534
29593
|
}));
|
|
29535
29594
|
}
|
|
29536
29595
|
|
|
29537
|
-
function srFormatNumber(a, locale) {
|
|
29596
|
+
function srFormatNumber(a, locale, maximumFractionDigits) {
|
|
29538
29597
|
// adding zero here converts negative zero to positive zero.
|
|
29539
29598
|
return (0 + a).toLocaleString(locale, {
|
|
29540
|
-
maximumFractionDigits: 3,
|
|
29599
|
+
maximumFractionDigits: maximumFractionDigits != null ? maximumFractionDigits : 3,
|
|
29541
29600
|
useGrouping: false // no thousands separators
|
|
29542
29601
|
});
|
|
29543
29602
|
}
|
|
@@ -29713,6 +29772,95 @@ a, b, c) {
|
|
|
29713
29772
|
return [x1, x2];
|
|
29714
29773
|
}
|
|
29715
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
|
+
|
|
29716
29864
|
const {
|
|
29717
29865
|
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
29718
29866
|
} = angles;
|
|
@@ -30158,7 +30306,7 @@ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*
|
|
|
30158
30306
|
})));
|
|
30159
30307
|
|
|
30160
30308
|
const {
|
|
30161
|
-
clockwise
|
|
30309
|
+
clockwise
|
|
30162
30310
|
} = geometry;
|
|
30163
30311
|
const {
|
|
30164
30312
|
getAngleFromVertex: getAngleFromVertex$1
|
|
@@ -30172,7 +30320,7 @@ const PolygonAngle = ({
|
|
|
30172
30320
|
snapTo
|
|
30173
30321
|
}) => {
|
|
30174
30322
|
const [centerX, centerY] = centerPoint;
|
|
30175
|
-
const areClockwise = clockwise
|
|
30323
|
+
const areClockwise = clockwise([centerPoint, ...endPoints]);
|
|
30176
30324
|
const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
|
|
30177
30325
|
const radius = 0.3;
|
|
30178
30326
|
const a = vec.dist(centerPoint, endPoints[0]);
|
|
@@ -30244,7 +30392,7 @@ const Angle = ({
|
|
|
30244
30392
|
range
|
|
30245
30393
|
}) => {
|
|
30246
30394
|
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
30247
|
-
const areClockwise = clockwise
|
|
30395
|
+
const areClockwise = clockwise([...coords, vertex]);
|
|
30248
30396
|
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
30249
30397
|
|
|
30250
30398
|
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
@@ -31177,9 +31325,6 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
31177
31325
|
};
|
|
31178
31326
|
}
|
|
31179
31327
|
|
|
31180
|
-
const {
|
|
31181
|
-
clockwise
|
|
31182
|
-
} = geometry;
|
|
31183
31328
|
function getGradableGraph(state, initialGraph) {
|
|
31184
31329
|
if (!state.hasBeenInteractedWith) {
|
|
31185
31330
|
return _extends({}, initialGraph);
|
|
@@ -31243,16 +31388,8 @@ function getGradableGraph(state, initialGraph) {
|
|
|
31243
31388
|
});
|
|
31244
31389
|
}
|
|
31245
31390
|
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
31246
|
-
// We're going to reverse the coords for scoring if the angle is clockwise and
|
|
31247
|
-
// we don't allow reflex angles. This is because the angle is largely defined
|
|
31248
|
-
// by the order of the points in the coords array, and we want to maintain
|
|
31249
|
-
// the same angle scoring with the legacy graph (which had a bug).
|
|
31250
|
-
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
31251
|
-
const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
|
|
31252
|
-
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
31253
|
-
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
31254
31391
|
return _extends({}, initialGraph, {
|
|
31255
|
-
coords,
|
|
31392
|
+
coords: state.coords,
|
|
31256
31393
|
allowReflexAngles: state.allowReflexAngles
|
|
31257
31394
|
});
|
|
31258
31395
|
}
|
|
@@ -31891,17 +32028,20 @@ function describePointGraph(state, i18n) {
|
|
|
31891
32028
|
y: srFormatNumber(y, locale)
|
|
31892
32029
|
}));
|
|
31893
32030
|
return strings.srInteractiveElements({
|
|
31894
|
-
elements: pointDescriptions.join("
|
|
32031
|
+
elements: pointDescriptions.join(" ")
|
|
31895
32032
|
});
|
|
31896
32033
|
}
|
|
31897
32034
|
|
|
31898
|
-
|
|
32035
|
+
const {
|
|
32036
|
+
convertRadiansToDegrees
|
|
32037
|
+
} = angles;
|
|
32038
|
+
function renderPolygonGraph(state, dispatch, i18n, markings) {
|
|
31899
32039
|
return {
|
|
31900
32040
|
graph: /*#__PURE__*/React.createElement(PolygonGraph, {
|
|
31901
32041
|
graphState: state,
|
|
31902
32042
|
dispatch: dispatch
|
|
31903
32043
|
}),
|
|
31904
|
-
interactiveElementsDescription:
|
|
32044
|
+
interactiveElementsDescription: getPolygonGraphDescription(state, i18n, markings)
|
|
31905
32045
|
};
|
|
31906
32046
|
}
|
|
31907
32047
|
const PolygonGraph = props => {
|
|
@@ -31986,7 +32126,7 @@ const PolygonGraph = props => {
|
|
|
31986
32126
|
focusVisible,
|
|
31987
32127
|
setFocusVisible
|
|
31988
32128
|
});
|
|
31989
|
-
return numSides === "unlimited" ? UnlimitedPolygonGraph
|
|
32129
|
+
return numSides === "unlimited" ? /*#__PURE__*/React.createElement(UnlimitedPolygonGraph, statefulProps) : /*#__PURE__*/React.createElement(LimitedPolygonGraph, statefulProps);
|
|
31990
32130
|
};
|
|
31991
32131
|
const LimitedPolygonGraph = statefulProps => {
|
|
31992
32132
|
const {
|
|
@@ -32011,8 +32151,39 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32011
32151
|
const {
|
|
32012
32152
|
disableKeyboardInteraction
|
|
32013
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]);
|
|
32014
32168
|
const lines = getLines(points);
|
|
32015
|
-
|
|
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, {
|
|
32016
32187
|
points: [...points],
|
|
32017
32188
|
color: "var(--movable-line-stroke-color)",
|
|
32018
32189
|
svgPolygonProps: {
|
|
@@ -32037,13 +32208,17 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32037
32208
|
snapTo: snapTo
|
|
32038
32209
|
});
|
|
32039
32210
|
}), showSides && lines.map(([start, end], i) => {
|
|
32211
|
+
// Use x and y to find the position of the label
|
|
32040
32212
|
const [x, y] = vec.midpoint(start, end);
|
|
32041
|
-
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);
|
|
32042
32217
|
return /*#__PURE__*/React.createElement(TextLabel, {
|
|
32043
32218
|
key: "side-" + i,
|
|
32044
32219
|
x: x,
|
|
32045
32220
|
y: y
|
|
32046
|
-
},
|
|
32221
|
+
}, isApprox ? `≈ ${length.toFixed(snapTo === "sides" ? 0 : 1)}` : length);
|
|
32047
32222
|
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
32048
32223
|
points: [...points],
|
|
32049
32224
|
color: "transparent",
|
|
@@ -32063,27 +32238,80 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32063
32238
|
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
32064
32239
|
},
|
|
32065
32240
|
// Required for lines to darken on focus
|
|
32066
|
-
onFocus: () =>
|
|
32241
|
+
onFocus: () => {
|
|
32242
|
+
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
32243
|
+
setAriaLives(() => ["polite", ...pointsOffArray]);
|
|
32244
|
+
},
|
|
32067
32245
|
// Required for line weighting to update on blur. Without this,
|
|
32068
32246
|
// the user has to hover over the shape for it to update
|
|
32069
32247
|
onBlur: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
|
|
32070
|
-
className: "movable-polygon"
|
|
32071
|
-
|
|
32072
|
-
|
|
32073
|
-
|
|
32074
|
-
|
|
32075
|
-
point: point,
|
|
32076
|
-
sequenceNumber: i + 1,
|
|
32077
|
-
onMove: destination => {
|
|
32078
|
-
const now = Date.now();
|
|
32079
|
-
const targetFPS = 40;
|
|
32080
|
-
const moveThresholdTime = 1000 / targetFPS;
|
|
32081
|
-
if (now - lastMoveTimeRef.current > moveThresholdTime) {
|
|
32082
|
-
dispatch(actions.polygon.movePoint(i, destination));
|
|
32083
|
-
lastMoveTimeRef.current = now;
|
|
32084
|
-
}
|
|
32248
|
+
className: "movable-polygon",
|
|
32249
|
+
// Accessibility-related fields
|
|
32250
|
+
role: "button",
|
|
32251
|
+
"aria-label": srPolygonGraphPoints ? `${srPolygonElementsNum} ${srPolygonGraphPoints}` : srPolygonElementsNum,
|
|
32252
|
+
"aria-live": ariaLives[0]
|
|
32085
32253
|
}
|
|
32086
|
-
}))
|
|
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));
|
|
32087
32315
|
};
|
|
32088
32316
|
const UnlimitedPolygonGraph = statefulProps => {
|
|
32089
32317
|
const {
|
|
@@ -32098,6 +32326,19 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
32098
32326
|
coords,
|
|
32099
32327
|
closedPolygon
|
|
32100
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);
|
|
32101
32342
|
|
|
32102
32343
|
// If the polygon is closed, return a LimitedPolygon component.
|
|
32103
32344
|
if (closedPolygon) {
|
|
@@ -32111,7 +32352,22 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
32111
32352
|
} = graphConfig;
|
|
32112
32353
|
const widthPx = graphDimensionsInPixels[0];
|
|
32113
32354
|
const heightPx = graphDimensionsInPixels[1];
|
|
32114
|
-
|
|
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, {
|
|
32115
32371
|
points: [...points],
|
|
32116
32372
|
color: "var(--movable-line-stroke-color)",
|
|
32117
32373
|
svgPolylineProps: {
|
|
@@ -32136,28 +32392,68 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
32136
32392
|
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
32137
32393
|
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
32138
32394
|
}
|
|
32139
|
-
}), coords.map((point, i) =>
|
|
32140
|
-
|
|
32141
|
-
|
|
32142
|
-
|
|
32143
|
-
|
|
32144
|
-
|
|
32145
|
-
|
|
32146
|
-
|
|
32147
|
-
|
|
32148
|
-
|
|
32149
|
-
|
|
32150
|
-
|
|
32151
|
-
// If the point being clicked is the first point and
|
|
32152
|
-
// there's enough non-duplicated points to form
|
|
32153
|
-
// a polygon (3 or more), close the shape before
|
|
32154
|
-
// setting focus.
|
|
32155
|
-
if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
|
|
32156
|
-
dispatch(actions.polygon.closePolygon());
|
|
32157
|
-
}
|
|
32158
|
-
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} `;
|
|
32159
32407
|
}
|
|
32160
|
-
|
|
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));
|
|
32161
32457
|
};
|
|
32162
32458
|
function getLines(points) {
|
|
32163
32459
|
return points.map((point, i) => {
|
|
@@ -32179,6 +32475,54 @@ const hasFocusVisible = element => {
|
|
|
32179
32475
|
return matches(":focus");
|
|
32180
32476
|
}
|
|
32181
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
|
+
}
|
|
32182
32526
|
|
|
32183
32527
|
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
32184
32528
|
return {
|
|
@@ -32878,7 +33222,8 @@ const MafsGraph = props => {
|
|
|
32878
33222
|
} = renderGraphElements({
|
|
32879
33223
|
state,
|
|
32880
33224
|
dispatch,
|
|
32881
|
-
i18n
|
|
33225
|
+
i18n,
|
|
33226
|
+
markings: props.markings
|
|
32882
33227
|
});
|
|
32883
33228
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
32884
33229
|
value: {
|
|
@@ -32913,7 +33258,7 @@ const MafsGraph = props => {
|
|
|
32913
33258
|
handleKeyboardEvent(event, state, dispatch);
|
|
32914
33259
|
},
|
|
32915
33260
|
"aria-label": fullGraphAriaLabel,
|
|
32916
|
-
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) &&
|
|
33261
|
+
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
|
|
32917
33262
|
ref: graphRef,
|
|
32918
33263
|
tabIndex: 0,
|
|
32919
33264
|
onFocus: event => {
|
|
@@ -32939,7 +33284,19 @@ const MafsGraph = props => {
|
|
|
32939
33284
|
bottom: 0,
|
|
32940
33285
|
left: 0
|
|
32941
33286
|
}
|
|
32942
|
-
}, (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, {
|
|
32943
33300
|
preserveAspectRatio: false,
|
|
32944
33301
|
viewBox: {
|
|
32945
33302
|
x: state.range[X],
|
|
@@ -32959,10 +33316,10 @@ const MafsGraph = props => {
|
|
|
32959
33316
|
height: height
|
|
32960
33317
|
})),
|
|
32961
33318
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
32962
|
-
(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, {
|
|
32963
33320
|
lockedFigures: props.lockedFigures,
|
|
32964
33321
|
range: state.range
|
|
32965
|
-
}))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
33322
|
+
})))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
32966
33323
|
lockedFigures: props.lockedFigures
|
|
32967
33324
|
}), /*#__PURE__*/React.createElement(View, {
|
|
32968
33325
|
style: {
|
|
@@ -33257,7 +33614,8 @@ const renderGraphElements = props => {
|
|
|
33257
33614
|
const {
|
|
33258
33615
|
state,
|
|
33259
33616
|
dispatch,
|
|
33260
|
-
i18n
|
|
33617
|
+
i18n,
|
|
33618
|
+
markings
|
|
33261
33619
|
} = props;
|
|
33262
33620
|
const {
|
|
33263
33621
|
type
|
|
@@ -33274,7 +33632,7 @@ const renderGraphElements = props => {
|
|
|
33274
33632
|
case "ray":
|
|
33275
33633
|
return renderRayGraph(state, dispatch);
|
|
33276
33634
|
case "polygon":
|
|
33277
|
-
return renderPolygonGraph(state, dispatch);
|
|
33635
|
+
return renderPolygonGraph(state, dispatch, i18n, markings);
|
|
33278
33636
|
case "point":
|
|
33279
33637
|
return renderPointGraph(state, dispatch);
|
|
33280
33638
|
case "circle":
|
|
@@ -35911,26 +36269,17 @@ class InteractiveGraph extends React.Component {
|
|
|
35911
36269
|
}
|
|
35912
36270
|
render() {
|
|
35913
36271
|
var _this$props$apiOption;
|
|
35914
|
-
|
|
35915
|
-
const
|
|
35916
|
-
|
|
35917
|
-
|
|
35918
|
-
|
|
35919
|
-
|
|
35920
|
-
|
|
35921
|
-
|
|
35922
|
-
|
|
35923
|
-
|
|
35924
|
-
|
|
35925
|
-
snapStep: snapStep,
|
|
35926
|
-
box: box,
|
|
35927
|
-
showTooltips: !!this.props.showTooltips,
|
|
35928
|
-
readOnly: (_this$props$apiOption3 = this.props.apiOptions) == null ? void 0 : _this$props$apiOption3.readOnly
|
|
35929
|
-
}));
|
|
35930
|
-
}
|
|
35931
|
-
return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
|
|
35932
|
-
ref: this.legacyGraphRef
|
|
35933
|
-
}, 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
|
+
}));
|
|
35934
36283
|
}
|
|
35935
36284
|
|
|
35936
36285
|
/**
|
|
@@ -36246,7 +36595,7 @@ class InteractiveGraph extends React.Component {
|
|
|
36246
36595
|
}
|
|
36247
36596
|
}
|
|
36248
36597
|
|
|
36249
|
-
//
|
|
36598
|
+
// We don't need to change any of the original props for static mode
|
|
36250
36599
|
InteractiveGraph.defaultProps = {
|
|
36251
36600
|
labels: ["x", "y"],
|
|
36252
36601
|
range: [[-10, 10], [-10, 10]],
|
|
@@ -36259,34 +36608,13 @@ InteractiveGraph.defaultProps = {
|
|
|
36259
36608
|
type: "linear"
|
|
36260
36609
|
}
|
|
36261
36610
|
};
|
|
36262
|
-
function shouldUseMafs(mafsFlags, graph) {
|
|
36263
|
-
if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
|
|
36264
|
-
return false;
|
|
36265
|
-
}
|
|
36266
|
-
switch (graph.type) {
|
|
36267
|
-
case "none":
|
|
36268
|
-
return true;
|
|
36269
|
-
case "point":
|
|
36270
|
-
if (graph.numPoints === UNLIMITED) {
|
|
36271
|
-
return Boolean(mafsFlags["unlimited-point"]);
|
|
36272
|
-
}
|
|
36273
|
-
return Boolean(mafsFlags["point"]);
|
|
36274
|
-
case "polygon":
|
|
36275
|
-
if (graph.numSides === UNLIMITED) {
|
|
36276
|
-
return Boolean(mafsFlags["unlimited-polygon"]);
|
|
36277
|
-
}
|
|
36278
|
-
return Boolean(mafsFlags["polygon"]);
|
|
36279
|
-
default:
|
|
36280
|
-
return Boolean(mafsFlags[graph.type]);
|
|
36281
|
-
}
|
|
36282
|
-
}
|
|
36283
|
-
// We don't need to change any of the original props for static mode
|
|
36284
36611
|
const staticTransform$3 = _.identity;
|
|
36285
36612
|
var InteractiveGraph$1 = {
|
|
36286
36613
|
name: "interactive-graph",
|
|
36287
36614
|
displayName: "Interactive graph (Assessments only)",
|
|
36288
36615
|
widget: InteractiveGraph,
|
|
36289
|
-
staticTransform: staticTransform$3
|
|
36616
|
+
staticTransform: staticTransform$3,
|
|
36617
|
+
getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
|
|
36290
36618
|
};
|
|
36291
36619
|
|
|
36292
36620
|
// TODO: can we use WB typography?
|
|
@@ -38566,7 +38894,8 @@ var Matrix$1 = {
|
|
|
38566
38894
|
widget: Matrix,
|
|
38567
38895
|
transform: propTransform$1,
|
|
38568
38896
|
staticTransform: staticTransform$2,
|
|
38569
|
-
isLintable: true
|
|
38897
|
+
isLintable: true,
|
|
38898
|
+
getPublicWidgetOptions: getMatrixPublicWidgetOptions
|
|
38570
38899
|
};
|
|
38571
38900
|
|
|
38572
38901
|
const getPromptJSON$7 = () => {
|
|
@@ -41761,7 +42090,8 @@ var Plotter$1 = {
|
|
|
41761
42090
|
displayName: "Plotter",
|
|
41762
42091
|
hidden: true,
|
|
41763
42092
|
widget: Plotter,
|
|
41764
|
-
staticTransform: staticTransform
|
|
42093
|
+
staticTransform: staticTransform,
|
|
42094
|
+
getPublicWidgetOptions: getPlotterPublicWidgetOptions
|
|
41765
42095
|
};
|
|
41766
42096
|
|
|
41767
42097
|
const getPromptJSON$2 = () => {
|
|
@@ -42143,7 +42473,8 @@ var Table$1 = {
|
|
|
42143
42473
|
widget: Table,
|
|
42144
42474
|
transform: propTransform,
|
|
42145
42475
|
hidden: true,
|
|
42146
|
-
isLintable: true
|
|
42476
|
+
isLintable: true,
|
|
42477
|
+
getPublicWidgetOptions: getTablePublicWidgetOptions
|
|
42147
42478
|
};
|
|
42148
42479
|
|
|
42149
42480
|
const getPromptJSON = () => {
|
|
@@ -42322,7 +42653,7 @@ const init = function init() {
|
|
|
42322
42653
|
|
|
42323
42654
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
42324
42655
|
const libName = "@khanacademy/perseus";
|
|
42325
|
-
const libVersion = "53.
|
|
42656
|
+
const libVersion = "53.1.0";
|
|
42326
42657
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
42327
42658
|
|
|
42328
42659
|
const apiVersion = {
|