@khanacademy/perseus 20.8.1 → 21.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 +716 -359
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +739 -366
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/util/extract-perseus-data.d.ts +7 -8
- package/dist/util/snowman-utils.d.ts +0 -72
- package/dist/util/test-utils.d.ts +37 -2
- package/dist/util/widget-enum-utils.d.ts +1 -3
- package/dist/widget-type-utils.d.ts +64 -0
- package/dist/widgets/interactive-graph.d.ts +3 -3
- package/dist/widgets/interactive-graphs/axis-arrows.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/components/angle.d.ts +12 -2
- package/dist/widgets/interactive-graphs/graphs/components/css-transform-wrapper.d.ts +7 -0
- package/dist/widgets/interactive-graphs/graphs/components/text-label.d.ts +6 -0
- package/dist/widgets/interactive-graphs/graphs/point.d.ts +5 -0
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +2 -0
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +15 -74
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +10 -3
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-reducer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/use-graph-state.d.ts +11 -0
- package/dist/widgets/interactive-graphs/types.d.ts +30 -9
- package/dist/widgets/interactive-graphs/utils.d.ts +2 -2
- package/package.json +1 -1
package/dist/es/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import * as i18n from '@khanacademy/wonder-blocks-i18n';
|
|
|
7
7
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
8
8
|
import classNames from 'classnames';
|
|
9
9
|
import * as React from 'react';
|
|
10
|
-
import React__default, { useState, useEffect, useRef } from 'react';
|
|
10
|
+
import React__default, { useState, useEffect, useRef, createContext } from 'react';
|
|
11
11
|
import _, { debounce as debounce$1 } from 'underscore';
|
|
12
12
|
import Clickable from '@khanacademy/wonder-blocks-clickable';
|
|
13
13
|
import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
|
|
@@ -1921,7 +1921,7 @@ const KhanAnswerTypes = {
|
|
|
1921
1921
|
}
|
|
1922
1922
|
};
|
|
1923
1923
|
|
|
1924
|
-
const _excluded$
|
|
1924
|
+
const _excluded$5 = ["scoring"];
|
|
1925
1925
|
const ERROR_TITLE = i18n._("Oops!");
|
|
1926
1926
|
const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
|
|
1927
1927
|
|
|
@@ -2009,7 +2009,7 @@ class Expression extends React.Component {
|
|
|
2009
2009
|
let {
|
|
2010
2010
|
scoring
|
|
2011
2011
|
} = _ref,
|
|
2012
|
-
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$
|
|
2012
|
+
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
|
|
2013
2013
|
const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
|
|
2014
2014
|
|
|
2015
2015
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
@@ -18832,7 +18832,7 @@ const isAccessible = widgetInfo => {
|
|
|
18832
18832
|
}
|
|
18833
18833
|
return !!accessible;
|
|
18834
18834
|
};
|
|
18835
|
-
const getAllWidgetTypes
|
|
18835
|
+
const getAllWidgetTypes = () => {
|
|
18836
18836
|
return _.keys(widgets);
|
|
18837
18837
|
};
|
|
18838
18838
|
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
@@ -19101,7 +19101,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19101
19101
|
getVersionVector: getVersionVector,
|
|
19102
19102
|
getPublicWidgets: getPublicWidgets,
|
|
19103
19103
|
isAccessible: isAccessible,
|
|
19104
|
-
getAllWidgetTypes: getAllWidgetTypes
|
|
19104
|
+
getAllWidgetTypes: getAllWidgetTypes,
|
|
19105
19105
|
upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
|
|
19106
19106
|
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
19107
19107
|
traverseChildWidgets: traverseChildWidgets$3,
|
|
@@ -19339,6 +19339,9 @@ class Renderer extends React.Component {
|
|
|
19339
19339
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19340
19340
|
const newValues = {};
|
|
19341
19341
|
if (!widgetInfo.type) {
|
|
19342
|
+
// TODO: why does widget have no type?
|
|
19343
|
+
// We don't want to derive type from widget ID
|
|
19344
|
+
// see: LEMS-1845
|
|
19342
19345
|
newValues.type = widgetId.split(" ")[0];
|
|
19343
19346
|
}
|
|
19344
19347
|
if (!widgetInfo.alignment) {
|
|
@@ -21428,7 +21431,7 @@ InputWithExamples.defaultProps = {
|
|
|
21428
21431
|
className: ""
|
|
21429
21432
|
};
|
|
21430
21433
|
|
|
21431
|
-
const _excluded$
|
|
21434
|
+
const _excluded$4 = ["keypadElement", "onFocus", "value"];
|
|
21432
21435
|
class SimpleKeypadInput extends React.Component {
|
|
21433
21436
|
constructor(...args) {
|
|
21434
21437
|
super(...args);
|
|
@@ -21467,7 +21470,7 @@ class SimpleKeypadInput extends React.Component {
|
|
|
21467
21470
|
onFocus,
|
|
21468
21471
|
value
|
|
21469
21472
|
} = _this$props,
|
|
21470
|
-
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$
|
|
21473
|
+
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
|
|
21471
21474
|
return (
|
|
21472
21475
|
/*#__PURE__*/
|
|
21473
21476
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
@@ -23298,14 +23301,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
|
|
|
23298
23301
|
forwardedRef: ref
|
|
23299
23302
|
})));
|
|
23300
23303
|
|
|
23301
|
-
const _excluded$
|
|
23304
|
+
const _excluded$3 = ["showContent", "content", "forwardedRef"];
|
|
23302
23305
|
const ChoiceNoneAbove = function ChoiceNoneAbove(props) {
|
|
23303
23306
|
const {
|
|
23304
23307
|
showContent,
|
|
23305
23308
|
content,
|
|
23306
23309
|
forwardedRef
|
|
23307
23310
|
} = props,
|
|
23308
|
-
rest = _objectWithoutPropertiesLoose(props, _excluded$
|
|
23311
|
+
rest = _objectWithoutPropertiesLoose(props, _excluded$3);
|
|
23309
23312
|
const choiceProps = _extends({}, rest, {
|
|
23310
23313
|
content: showContent ? content :
|
|
23311
23314
|
/*#__PURE__*/
|
|
@@ -24898,9 +24901,22 @@ class Explanation extends React.Component {
|
|
|
24898
24901
|
// Special styling is needed to fit the button in a block of text without throwing off the line spacing.
|
|
24899
24902
|
// While the button is not normally included in a block of text, it needs to be able to accommodate such a case.
|
|
24900
24903
|
const buttonStyleOverrides = {
|
|
24901
|
-
height: "
|
|
24904
|
+
height: "auto",
|
|
24902
24905
|
marginLeft: "-2px",
|
|
24903
|
-
|
|
24906
|
+
// transfer space on the left side to the padding
|
|
24907
|
+
marginRight: "2px",
|
|
24908
|
+
// adds space between the right border and any text that follows
|
|
24909
|
+
paddingLeft: "2px" // adds space between the left border and the button text
|
|
24910
|
+
};
|
|
24911
|
+
|
|
24912
|
+
const labelStyle = {
|
|
24913
|
+
fontSize: "18px",
|
|
24914
|
+
lineHeight: "20px",
|
|
24915
|
+
// The following property adjusts the large space between the button text and the caret icon.
|
|
24916
|
+
// Since we are unable to adjust the styling of the icon (where the extra space exists),
|
|
24917
|
+
// we are adjusting it on the text label by using a negative margin.
|
|
24918
|
+
marginRight: "-6px",
|
|
24919
|
+
"white-space": "normal"
|
|
24904
24920
|
};
|
|
24905
24921
|
const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
|
|
24906
24922
|
return /*#__PURE__*/React.createElement(UniqueIDProvider, {
|
|
@@ -24911,8 +24927,9 @@ class Explanation extends React.Component {
|
|
|
24911
24927
|
"aria-controls": ids.get("content"),
|
|
24912
24928
|
endIcon: caretIcon,
|
|
24913
24929
|
kind: "tertiary",
|
|
24930
|
+
labelStyle: labelStyle,
|
|
24914
24931
|
onClick: this._onClick,
|
|
24915
|
-
size: "
|
|
24932
|
+
size: "small",
|
|
24916
24933
|
style: buttonStyleOverrides
|
|
24917
24934
|
}, promptText), /*#__PURE__*/React.createElement(View, {
|
|
24918
24935
|
id: ids.get("content"),
|
|
@@ -28187,6 +28204,14 @@ const moveAll = delta => ({
|
|
|
28187
28204
|
type: MOVE_ALL,
|
|
28188
28205
|
delta
|
|
28189
28206
|
});
|
|
28207
|
+
const MOVE_POINT = "move-point";
|
|
28208
|
+
function movePoint(index, destination) {
|
|
28209
|
+
return {
|
|
28210
|
+
type: MOVE_POINT,
|
|
28211
|
+
index,
|
|
28212
|
+
destination
|
|
28213
|
+
};
|
|
28214
|
+
}
|
|
28190
28215
|
|
|
28191
28216
|
/**
|
|
28192
28217
|
* 44 is touch best practice and AAA compliant for WCAG
|
|
@@ -28261,6 +28286,10 @@ const getRayIntersectionCoords = (collinearPoint, extendFrom, range) => {
|
|
|
28261
28286
|
return [xMax, yAtXMax];
|
|
28262
28287
|
}
|
|
28263
28288
|
};
|
|
28289
|
+
const getLines = points => points.map((point, i) => {
|
|
28290
|
+
const next = points[(i + 1) % points.length];
|
|
28291
|
+
return [point, next];
|
|
28292
|
+
});
|
|
28264
28293
|
|
|
28265
28294
|
const defaultStroke = "var(--movable-line-stroke-color)";
|
|
28266
28295
|
const MovableLine = props => {
|
|
@@ -28405,7 +28434,9 @@ const SegmentGraph = props => {
|
|
|
28405
28434
|
onMoveLine: delta => {
|
|
28406
28435
|
dispatch(moveLine(i, delta));
|
|
28407
28436
|
},
|
|
28408
|
-
onMovePoint: (endpointIndex, destination) =>
|
|
28437
|
+
onMovePoint: (endpointIndex, destination) => {
|
|
28438
|
+
dispatch(moveControlPoint(endpointIndex, destination, i));
|
|
28439
|
+
},
|
|
28409
28440
|
"data-testid": "segment" + i
|
|
28410
28441
|
})));
|
|
28411
28442
|
};
|
|
@@ -28521,11 +28552,52 @@ const RayGraph = props => {
|
|
|
28521
28552
|
}));
|
|
28522
28553
|
};
|
|
28523
28554
|
|
|
28555
|
+
/**
|
|
28556
|
+
* Wrapper with Mafs transform styles for SVG elements.
|
|
28557
|
+
* Scales and translates the SVG element to match the Mafs view.
|
|
28558
|
+
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
28559
|
+
*/
|
|
28560
|
+
const MafsCssTransformWrapper = ({
|
|
28561
|
+
children
|
|
28562
|
+
}) => /*#__PURE__*/React.createElement("g", {
|
|
28563
|
+
style: {
|
|
28564
|
+
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28565
|
+
}
|
|
28566
|
+
}, children);
|
|
28567
|
+
|
|
28568
|
+
const _excluded$2 = ["children"];
|
|
28569
|
+
const TextLabel = _ref => {
|
|
28570
|
+
let {
|
|
28571
|
+
children
|
|
28572
|
+
} = _ref,
|
|
28573
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
|
|
28574
|
+
return /*#__PURE__*/React__default.createElement(Text$1, _extends({
|
|
28575
|
+
size: 16,
|
|
28576
|
+
svgTextProps: {
|
|
28577
|
+
filter: "url(#background)",
|
|
28578
|
+
fontWeight: "bold"
|
|
28579
|
+
}
|
|
28580
|
+
}, rest), children);
|
|
28581
|
+
};
|
|
28582
|
+
const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*#__PURE__*/React__default.createElement("filter", {
|
|
28583
|
+
id: "background",
|
|
28584
|
+
x: "-5%",
|
|
28585
|
+
width: "110%",
|
|
28586
|
+
y: "0%",
|
|
28587
|
+
height: "100%"
|
|
28588
|
+
}, /*#__PURE__*/React__default.createElement("feFlood", {
|
|
28589
|
+
floodColor: "#FFF",
|
|
28590
|
+
floodOpacity: "0.64"
|
|
28591
|
+
}), /*#__PURE__*/React__default.createElement("feComposite", {
|
|
28592
|
+
operator: "over",
|
|
28593
|
+
in: "SourceGraphic"
|
|
28594
|
+
})));
|
|
28595
|
+
|
|
28524
28596
|
const Angle = ({
|
|
28525
28597
|
centerPoint,
|
|
28526
28598
|
endPoints,
|
|
28527
28599
|
range,
|
|
28528
|
-
|
|
28600
|
+
polygonLines,
|
|
28529
28601
|
showAngles
|
|
28530
28602
|
}) => {
|
|
28531
28603
|
const [centerX, centerY] = centerPoint;
|
|
@@ -28547,7 +28619,7 @@ const Angle = ({
|
|
|
28547
28619
|
// Used to calculate the inside of the angle and to create square
|
|
28548
28620
|
const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
|
|
28549
28621
|
if (!showAngles) {
|
|
28550
|
-
return isRightAngle(angle) ? /*#__PURE__*/React.createElement(
|
|
28622
|
+
return isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
28551
28623
|
start: [x1, y1],
|
|
28552
28624
|
vertex: [x2, y2],
|
|
28553
28625
|
end: [x3, y3]
|
|
@@ -28555,13 +28627,13 @@ const Angle = ({
|
|
|
28555
28627
|
}
|
|
28556
28628
|
|
|
28557
28629
|
// Midpoint betwen ends of arc
|
|
28558
|
-
const
|
|
28559
|
-
const largeArcFlag =
|
|
28560
|
-
const sweepFlag =
|
|
28630
|
+
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
28631
|
+
const largeArcFlag = isOutside ? 1 : 0;
|
|
28632
|
+
const sweepFlag = isOutside ? 1 : 0;
|
|
28561
28633
|
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
28562
28634
|
let angleInDegrees = angle * (180 / Math.PI);
|
|
28563
28635
|
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
28564
|
-
if (
|
|
28636
|
+
if (isOutside) {
|
|
28565
28637
|
angleInDegrees = 360 - angleInDegrees;
|
|
28566
28638
|
}
|
|
28567
28639
|
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
|
|
@@ -28578,80 +28650,54 @@ const Angle = ({
|
|
|
28578
28650
|
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
28579
28651
|
operator: "over",
|
|
28580
28652
|
in: "SourceGraphic"
|
|
28581
|
-
}))), !
|
|
28653
|
+
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
28582
28654
|
start: [x1, y1],
|
|
28583
28655
|
vertex: [x2, y2],
|
|
28584
28656
|
end: [x3, y3]
|
|
28585
|
-
}) : /*#__PURE__*/React.createElement("
|
|
28586
|
-
style: {
|
|
28587
|
-
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28588
|
-
}
|
|
28589
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
28657
|
+
}) : /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
28590
28658
|
d: arc,
|
|
28591
28659
|
strokeWidth: 0.02,
|
|
28592
28660
|
fill: "none"
|
|
28593
|
-
})), /*#__PURE__*/React.createElement(
|
|
28661
|
+
})), /*#__PURE__*/React.createElement(TextLabel, {
|
|
28594
28662
|
x: x3,
|
|
28595
28663
|
y: y3,
|
|
28596
|
-
size: 15,
|
|
28597
|
-
svgTextProps: {
|
|
28598
|
-
filter: "url(#background)",
|
|
28599
|
-
fontWeight: "bold"
|
|
28600
|
-
}
|
|
28601
|
-
// Shift position if text is too close to movable point
|
|
28602
|
-
,
|
|
28603
28664
|
attach: y3 - centerY > 0 ? "s" : "n",
|
|
28604
28665
|
attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
28605
28666
|
}, angleLabel, "\xB0"));
|
|
28606
28667
|
};
|
|
28607
|
-
|
|
28668
|
+
|
|
28669
|
+
/**
|
|
28670
|
+
* This is broken out into its own component so that it can be used for an early return
|
|
28671
|
+
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
28672
|
+
*/
|
|
28673
|
+
const RightAngleSquare = ({
|
|
28608
28674
|
start: [x1, y1],
|
|
28609
28675
|
vertex: [x2, y2],
|
|
28610
28676
|
end: [x3, y3]
|
|
28611
|
-
}) => /*#__PURE__*/React.createElement("
|
|
28612
|
-
style: {
|
|
28613
|
-
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28614
|
-
}
|
|
28615
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
28677
|
+
}) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
28616
28678
|
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
28617
28679
|
strokeWidth: 0.02,
|
|
28618
28680
|
fill: "none"
|
|
28619
28681
|
}));
|
|
28620
28682
|
const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
|
|
28624
|
-
|
|
28625
|
-
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
|
|
28629
|
-
|
|
28630
|
-
|
|
28631
|
-
|
|
28632
|
-
|
|
28633
|
-
|
|
28634
|
-
|
|
28635
|
-
for (let i = 0; i < polygonPoints.length; i++) {
|
|
28636
|
-
if ([prev, midpointIndex, next].includes(i)) {
|
|
28637
|
-
continue;
|
|
28638
|
-
}
|
|
28639
|
-
const [x, y] = polygonPoints[i];
|
|
28640
|
-
const intersectsPoint = (y - lineAy) * (lineBx - lineAx) === (lineBy - lineAy) * (x - lineAx);
|
|
28641
|
-
if (intersectsPoint) {
|
|
28642
|
-
return false;
|
|
28643
|
-
}
|
|
28644
|
-
}
|
|
28645
|
-
return true;
|
|
28646
|
-
}
|
|
28683
|
+
|
|
28684
|
+
/**
|
|
28685
|
+
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
28686
|
+
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
28687
|
+
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
28688
|
+
* with an odd number of lines, while a ray from a point outside the polygon
|
|
28689
|
+
* will intersect with an even number of lines.
|
|
28690
|
+
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
28691
|
+
*/
|
|
28692
|
+
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
28693
|
+
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
28694
|
+
const rangeIntersectionPoint = getRayIntersectionCoords(midpoint, vertex, range);
|
|
28695
|
+
let lineIntersectionCount = 0;
|
|
28696
|
+
polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
28647
28697
|
|
|
28648
28698
|
// If the number of intersections is even, the angle is inside the polygon
|
|
28649
|
-
return isEven(
|
|
28699
|
+
return !isEven(lineIntersectionCount);
|
|
28650
28700
|
};
|
|
28651
|
-
const getLines = points => points.map((point, i) => {
|
|
28652
|
-
const next = points[(i + 1) % points.length];
|
|
28653
|
-
return [point, next];
|
|
28654
|
-
});
|
|
28655
28701
|
|
|
28656
28702
|
// https://stackoverflow.com/a/24392281/7347484
|
|
28657
28703
|
// The "intersects" function in geometry doesn't seem to work for this use case
|
|
@@ -28678,6 +28724,7 @@ const PolygonGraph = props => {
|
|
|
28678
28724
|
coords,
|
|
28679
28725
|
type,
|
|
28680
28726
|
showAngles,
|
|
28727
|
+
showSides,
|
|
28681
28728
|
range
|
|
28682
28729
|
} = props.graphState;
|
|
28683
28730
|
const points = coords != null ? coords : [[0, 0]];
|
|
@@ -28696,6 +28743,7 @@ const PolygonGraph = props => {
|
|
|
28696
28743
|
constrain: p => p
|
|
28697
28744
|
});
|
|
28698
28745
|
const active = hovered || focused || dragging;
|
|
28746
|
+
const lines = getLines(points);
|
|
28699
28747
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polygon, {
|
|
28700
28748
|
points: [...points],
|
|
28701
28749
|
color: "var(--movable-line-stroke-color)",
|
|
@@ -28714,9 +28762,17 @@ const PolygonGraph = props => {
|
|
|
28714
28762
|
endPoints: [pt1, pt2],
|
|
28715
28763
|
active: active,
|
|
28716
28764
|
range: range,
|
|
28717
|
-
|
|
28765
|
+
polygonLines: lines,
|
|
28718
28766
|
showAngles: !!showAngles
|
|
28719
28767
|
});
|
|
28768
|
+
}), showSides && lines.map(([start, end], i) => {
|
|
28769
|
+
const [x, y] = vec.midpoint(start, end);
|
|
28770
|
+
const length = parseFloat(vec.dist(start, end).toFixed(1));
|
|
28771
|
+
return /*#__PURE__*/React.createElement(TextLabel, {
|
|
28772
|
+
key: "side-" + i,
|
|
28773
|
+
x: x,
|
|
28774
|
+
y: y
|
|
28775
|
+
}, !Number.isInteger(length) && "≈ ", length);
|
|
28720
28776
|
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
28721
28777
|
points: [...points],
|
|
28722
28778
|
color: "transparent",
|
|
@@ -28736,11 +28792,73 @@ const PolygonGraph = props => {
|
|
|
28736
28792
|
}), points.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
|
|
28737
28793
|
key: "point-" + i,
|
|
28738
28794
|
point: point,
|
|
28739
|
-
onMove: destination => dispatch(
|
|
28795
|
+
onMove: destination => dispatch(movePoint(i, destination)),
|
|
28740
28796
|
"data-testid": type + i
|
|
28741
28797
|
})));
|
|
28742
28798
|
};
|
|
28743
28799
|
|
|
28800
|
+
function PointGraph(props) {
|
|
28801
|
+
const {
|
|
28802
|
+
dispatch
|
|
28803
|
+
} = props;
|
|
28804
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
|
|
28805
|
+
key: i,
|
|
28806
|
+
point: point,
|
|
28807
|
+
onMove: destination => dispatch(movePoint(i, destination))
|
|
28808
|
+
})));
|
|
28809
|
+
}
|
|
28810
|
+
|
|
28811
|
+
const GraphStateContext = /*#__PURE__*/createContext({
|
|
28812
|
+
state: {},
|
|
28813
|
+
dispatch: () => {}
|
|
28814
|
+
});
|
|
28815
|
+
function useGraphState() {
|
|
28816
|
+
return React__default.useContext(GraphStateContext);
|
|
28817
|
+
}
|
|
28818
|
+
|
|
28819
|
+
function Arrow(props) {
|
|
28820
|
+
const {
|
|
28821
|
+
userTransform,
|
|
28822
|
+
viewTransform
|
|
28823
|
+
} = useTransformContext();
|
|
28824
|
+
const point = [props.x, props.y];
|
|
28825
|
+
const userTransformedPoint = vec.transform(point, userTransform);
|
|
28826
|
+
const viewTransformedPoint = vec.transform(userTransformedPoint, viewTransform);
|
|
28827
|
+
return /*#__PURE__*/React__default.createElement("g", {
|
|
28828
|
+
transform: `translate(${viewTransformedPoint[0]} ${viewTransformedPoint[1]}) rotate(${props.rotate}) scale(1.25)`
|
|
28829
|
+
}, /*#__PURE__*/React__default.createElement("g", {
|
|
28830
|
+
transform: "translate(-1)"
|
|
28831
|
+
}, /*#__PURE__*/React__default.createElement("path", {
|
|
28832
|
+
d: "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4",
|
|
28833
|
+
fill: "none"
|
|
28834
|
+
})));
|
|
28835
|
+
}
|
|
28836
|
+
function AxisArrows() {
|
|
28837
|
+
const {
|
|
28838
|
+
state
|
|
28839
|
+
} = useGraphState();
|
|
28840
|
+
const range = state.range;
|
|
28841
|
+
const [xMin, xMax] = range[0];
|
|
28842
|
+
const [yMin, yMax] = range[1];
|
|
28843
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Arrow, {
|
|
28844
|
+
x: xMax,
|
|
28845
|
+
y: 0,
|
|
28846
|
+
rotate: 0
|
|
28847
|
+
}), /*#__PURE__*/React__default.createElement(Arrow, {
|
|
28848
|
+
x: 0,
|
|
28849
|
+
y: yMin,
|
|
28850
|
+
rotate: 90
|
|
28851
|
+
}), /*#__PURE__*/React__default.createElement(Arrow, {
|
|
28852
|
+
x: xMin,
|
|
28853
|
+
y: 0,
|
|
28854
|
+
rotate: 180
|
|
28855
|
+
}), /*#__PURE__*/React__default.createElement(Arrow, {
|
|
28856
|
+
x: 0,
|
|
28857
|
+
y: yMax,
|
|
28858
|
+
rotate: 270
|
|
28859
|
+
}));
|
|
28860
|
+
}
|
|
28861
|
+
|
|
28744
28862
|
const renderLineLabel = (n, [min, max]) => n !== -1 && n !== min && n !== max;
|
|
28745
28863
|
const axisOptions = (props, index) => ({
|
|
28746
28864
|
axis: props.markings === "graph",
|
|
@@ -28748,10 +28866,10 @@ const axisOptions = (props, index) => ({
|
|
|
28748
28866
|
subdivisions: props.step[index] / props.gridStep[index],
|
|
28749
28867
|
labels: n => renderLineLabel(n, props.range[index]) ? n : ""
|
|
28750
28868
|
});
|
|
28751
|
-
const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
|
|
28869
|
+
const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
|
|
28752
28870
|
xAxis: axisOptions(props, 0),
|
|
28753
28871
|
yAxis: axisOptions(props, 1)
|
|
28754
|
-
});
|
|
28872
|
+
}), props.markings === "graph" && /*#__PURE__*/React.createElement(AxisArrows, null));
|
|
28755
28873
|
|
|
28756
28874
|
// Displays a stylized open eye: lint warnings are visible
|
|
28757
28875
|
const VisibleIcon = () => /*#__PURE__*/React.createElement("svg", {
|
|
@@ -29448,22 +29566,32 @@ const LegacyGrid = ({
|
|
|
29448
29566
|
return null;
|
|
29449
29567
|
};
|
|
29450
29568
|
|
|
29451
|
-
/** Determine if coords is type CollinearTuple[] */
|
|
29452
|
-
const isCollinearTuples = coords => Array.isArray(coords[0][0]);
|
|
29453
|
-
|
|
29454
|
-
// Generic type makes returned state match input state
|
|
29455
29569
|
function interactiveGraphReducer(state, action) {
|
|
29570
|
+
switch (action.type) {
|
|
29571
|
+
case MOVE_CONTROL_POINT:
|
|
29572
|
+
return doMoveControlPoint(state, action);
|
|
29573
|
+
case MOVE_LINE:
|
|
29574
|
+
return doMoveLine(state, action);
|
|
29575
|
+
case MOVE_ALL:
|
|
29576
|
+
return doMoveAll(state, action);
|
|
29577
|
+
case MOVE_POINT:
|
|
29578
|
+
return doMovePoint(state, action);
|
|
29579
|
+
default:
|
|
29580
|
+
throw new UnreachableCaseError(action);
|
|
29581
|
+
}
|
|
29582
|
+
}
|
|
29583
|
+
function doMoveControlPoint(state, action) {
|
|
29456
29584
|
const {
|
|
29457
29585
|
snapStep,
|
|
29458
29586
|
range
|
|
29459
29587
|
} = state;
|
|
29460
|
-
|
|
29461
|
-
|
|
29462
|
-
|
|
29463
|
-
|
|
29464
|
-
case
|
|
29588
|
+
switch (state.type) {
|
|
29589
|
+
case "segment":
|
|
29590
|
+
case "linear":
|
|
29591
|
+
case "linear-system":
|
|
29592
|
+
case "ray":
|
|
29465
29593
|
{
|
|
29466
|
-
const newCoords =
|
|
29594
|
+
const newCoords = updateAtIndex({
|
|
29467
29595
|
array: state.coords,
|
|
29468
29596
|
index: action.itemIndex,
|
|
29469
29597
|
update: tuple => setAtIndex({
|
|
@@ -29478,23 +29606,9 @@ function interactiveGraphReducer(state, action) {
|
|
|
29478
29606
|
})
|
|
29479
29607
|
})
|
|
29480
29608
|
})
|
|
29481
|
-
}) : setAtIndex({
|
|
29482
|
-
array: state.coords,
|
|
29483
|
-
index: action.pointIndex,
|
|
29484
|
-
newValue: snap({
|
|
29485
|
-
snapStep,
|
|
29486
|
-
point: bound$1({
|
|
29487
|
-
snapStep,
|
|
29488
|
-
range,
|
|
29489
|
-
point: action.destination
|
|
29490
|
-
})
|
|
29491
|
-
})
|
|
29492
29609
|
});
|
|
29493
|
-
|
|
29494
|
-
|
|
29495
|
-
// so this may seem redundant, but it's necessary for type safety.
|
|
29496
|
-
const coordsToCheck = isCollinearTuples(newCoords) && action.itemIndex !== undefined ? newCoords[action.itemIndex] : newCoords;
|
|
29497
|
-
if (!isCollinearTuples(coordsToCheck) && coordsOverlap(coordsToCheck)) {
|
|
29610
|
+
const coordsToCheck = newCoords[action.itemIndex];
|
|
29611
|
+
if (coordsOverlap(coordsToCheck)) {
|
|
29498
29612
|
return state;
|
|
29499
29613
|
}
|
|
29500
29614
|
return _extends({}, state, {
|
|
@@ -29502,16 +29616,30 @@ function interactiveGraphReducer(state, action) {
|
|
|
29502
29616
|
coords: newCoords
|
|
29503
29617
|
});
|
|
29504
29618
|
}
|
|
29505
|
-
case
|
|
29619
|
+
case "circle":
|
|
29620
|
+
throw new Error("FIXME implement circle reducer");
|
|
29621
|
+
case "point":
|
|
29622
|
+
case "polygon":
|
|
29623
|
+
throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
|
|
29624
|
+
default:
|
|
29625
|
+
throw new UnreachableCaseError(state);
|
|
29626
|
+
}
|
|
29627
|
+
}
|
|
29628
|
+
function doMoveLine(state, action) {
|
|
29629
|
+
const {
|
|
29630
|
+
snapStep,
|
|
29631
|
+
range
|
|
29632
|
+
} = state;
|
|
29633
|
+
switch (state.type) {
|
|
29634
|
+
case "segment":
|
|
29635
|
+
case "linear":
|
|
29636
|
+
case "linear-system":
|
|
29637
|
+
case "ray":
|
|
29506
29638
|
{
|
|
29507
|
-
var _state$coords;
|
|
29508
|
-
if (!isCollinearTuples(state.coords)) {
|
|
29509
|
-
throw new Error("Cannot call this action unless coords is array of CollinearTuple.");
|
|
29510
|
-
}
|
|
29511
29639
|
if (action.itemIndex === undefined) {
|
|
29512
29640
|
throw new Error("Please provide index of line to move");
|
|
29513
29641
|
}
|
|
29514
|
-
const currentLine =
|
|
29642
|
+
const currentLine = state.coords[action.itemIndex];
|
|
29515
29643
|
if (!currentLine) {
|
|
29516
29644
|
throw new Error("No line to move");
|
|
29517
29645
|
}
|
|
@@ -29532,15 +29660,25 @@ function interactiveGraphReducer(state, action) {
|
|
|
29532
29660
|
newValue: newLine
|
|
29533
29661
|
});
|
|
29534
29662
|
return _extends({}, state, {
|
|
29663
|
+
type: state.type,
|
|
29535
29664
|
hasBeenInteractedWith: true,
|
|
29536
29665
|
coords: newCoords
|
|
29537
29666
|
});
|
|
29538
29667
|
}
|
|
29539
|
-
|
|
29668
|
+
default:
|
|
29669
|
+
// The MoveLine action doesn't make sense for other graph types;
|
|
29670
|
+
// ignore it if it somehow happens
|
|
29671
|
+
return state;
|
|
29672
|
+
}
|
|
29673
|
+
}
|
|
29674
|
+
function doMoveAll(state, action) {
|
|
29675
|
+
const {
|
|
29676
|
+
snapStep,
|
|
29677
|
+
range
|
|
29678
|
+
} = state;
|
|
29679
|
+
switch (state.type) {
|
|
29680
|
+
case "polygon":
|
|
29540
29681
|
{
|
|
29541
|
-
if (isCollinearTuples(state.coords)) {
|
|
29542
|
-
throw new Error("Cannot call this action unless coords is array of vectors.");
|
|
29543
|
-
}
|
|
29544
29682
|
const change = getChange(state.coords, action.delta, {
|
|
29545
29683
|
snapStep,
|
|
29546
29684
|
range
|
|
@@ -29555,7 +29693,33 @@ function interactiveGraphReducer(state, action) {
|
|
|
29555
29693
|
});
|
|
29556
29694
|
}
|
|
29557
29695
|
default:
|
|
29558
|
-
|
|
29696
|
+
// MoveAll is not supported for other state types; just ignore it.
|
|
29697
|
+
return state;
|
|
29698
|
+
}
|
|
29699
|
+
}
|
|
29700
|
+
function doMovePoint(state, action) {
|
|
29701
|
+
switch (state.type) {
|
|
29702
|
+
case "point":
|
|
29703
|
+
case "polygon":
|
|
29704
|
+
{
|
|
29705
|
+
return _extends({}, state, {
|
|
29706
|
+
hasBeenInteractedWith: true,
|
|
29707
|
+
coords: setAtIndex({
|
|
29708
|
+
array: state.coords,
|
|
29709
|
+
index: action.index,
|
|
29710
|
+
newValue: snap({
|
|
29711
|
+
snapStep: state.snapStep,
|
|
29712
|
+
point: bound$1({
|
|
29713
|
+
snapStep: state.snapStep,
|
|
29714
|
+
range: state.range,
|
|
29715
|
+
point: action.destination
|
|
29716
|
+
})
|
|
29717
|
+
})
|
|
29718
|
+
})
|
|
29719
|
+
});
|
|
29720
|
+
}
|
|
29721
|
+
default:
|
|
29722
|
+
throw new Error("The movePoint action is only for point and polygon graphs");
|
|
29559
29723
|
}
|
|
29560
29724
|
}
|
|
29561
29725
|
const getChange = (coords, delta, constraintOpts) => {
|
|
@@ -29636,7 +29800,7 @@ function clamp(value, min, max) {
|
|
|
29636
29800
|
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$1.equal(coord, c)));
|
|
29637
29801
|
function updateAtIndex(args) {
|
|
29638
29802
|
const {
|
|
29639
|
-
array
|
|
29803
|
+
array,
|
|
29640
29804
|
index,
|
|
29641
29805
|
update
|
|
29642
29806
|
} = args;
|
|
@@ -29649,13 +29813,12 @@ function updateAtIndex(args) {
|
|
|
29649
29813
|
}
|
|
29650
29814
|
function setAtIndex(args) {
|
|
29651
29815
|
const {
|
|
29652
|
-
array
|
|
29816
|
+
array,
|
|
29653
29817
|
index,
|
|
29654
29818
|
newValue
|
|
29655
29819
|
} = args;
|
|
29656
29820
|
const copy = [...array];
|
|
29657
29821
|
copy[index] = newValue;
|
|
29658
|
-
// restoring readonly to array
|
|
29659
29822
|
return copy;
|
|
29660
29823
|
}
|
|
29661
29824
|
|
|
@@ -29696,19 +29859,32 @@ function initializeGraphState(params) {
|
|
|
29696
29859
|
})
|
|
29697
29860
|
};
|
|
29698
29861
|
case "polygon":
|
|
29699
|
-
return
|
|
29862
|
+
return {
|
|
29863
|
+
type: "polygon",
|
|
29700
29864
|
hasBeenInteractedWith: false,
|
|
29701
29865
|
range,
|
|
29702
|
-
snapStep
|
|
29703
|
-
|
|
29866
|
+
snapStep,
|
|
29867
|
+
showAngles: Boolean(graph.showAngles),
|
|
29868
|
+
showSides: Boolean(graph.showSides),
|
|
29704
29869
|
coords: getPolygonCoords({
|
|
29705
29870
|
graph,
|
|
29706
29871
|
range,
|
|
29707
29872
|
step
|
|
29708
29873
|
})
|
|
29709
|
-
}
|
|
29710
|
-
case "angle":
|
|
29874
|
+
};
|
|
29711
29875
|
case "point":
|
|
29876
|
+
return {
|
|
29877
|
+
type: graph.type,
|
|
29878
|
+
hasBeenInteractedWith: false,
|
|
29879
|
+
range,
|
|
29880
|
+
snapStep,
|
|
29881
|
+
coords: getDefaultPoints({
|
|
29882
|
+
graph,
|
|
29883
|
+
step,
|
|
29884
|
+
range
|
|
29885
|
+
})
|
|
29886
|
+
};
|
|
29887
|
+
case "angle":
|
|
29712
29888
|
case "circle":
|
|
29713
29889
|
case "sinusoid":
|
|
29714
29890
|
case "quadratic":
|
|
@@ -29717,34 +29893,86 @@ function initializeGraphState(params) {
|
|
|
29717
29893
|
throw new UnreachableCaseError(graph);
|
|
29718
29894
|
}
|
|
29719
29895
|
}
|
|
29896
|
+
const getDefaultPoints = ({
|
|
29897
|
+
graph,
|
|
29898
|
+
range,
|
|
29899
|
+
step
|
|
29900
|
+
}) => {
|
|
29901
|
+
var _graph$coords;
|
|
29902
|
+
const numPoints = graph.numPoints || 1;
|
|
29903
|
+
let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
|
|
29904
|
+
if (coords) {
|
|
29905
|
+
return coords;
|
|
29906
|
+
}
|
|
29907
|
+
switch (numPoints) {
|
|
29908
|
+
case 1:
|
|
29909
|
+
// Back in the day, one point's coords were in graph.coord
|
|
29910
|
+
coords = [graph.coord || [0, 0]];
|
|
29911
|
+
break;
|
|
29912
|
+
case 2:
|
|
29913
|
+
coords = [[-5, 0], [5, 0]];
|
|
29914
|
+
break;
|
|
29915
|
+
case 3:
|
|
29916
|
+
coords = [[-5, 0], [0, 0], [5, 0]];
|
|
29917
|
+
break;
|
|
29918
|
+
case 4:
|
|
29919
|
+
coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
|
|
29920
|
+
break;
|
|
29921
|
+
case 5:
|
|
29922
|
+
coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
|
|
29923
|
+
break;
|
|
29924
|
+
case 6:
|
|
29925
|
+
coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
|
|
29926
|
+
break;
|
|
29927
|
+
default:
|
|
29928
|
+
coords = [];
|
|
29929
|
+
break;
|
|
29930
|
+
}
|
|
29931
|
+
// Transform coords from their -10 to 10 space to 0 to 1
|
|
29932
|
+
// because of the old graph.coord, and also it's easier.
|
|
29933
|
+
const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
|
|
29934
|
+
return normalizePoints(range, step, newCoords);
|
|
29935
|
+
};
|
|
29720
29936
|
|
|
29721
29937
|
// TS v4 doesn't narrow return types, while v5 does.
|
|
29722
29938
|
// Instead of updating to v5, using generic type to relate input and output types.
|
|
29723
29939
|
function getGradableGraph(state, initialGraph) {
|
|
29724
|
-
var _state$coords;
|
|
29725
29940
|
if (!state.hasBeenInteractedWith) {
|
|
29726
|
-
return
|
|
29941
|
+
return {
|
|
29942
|
+
type: initialGraph.type
|
|
29943
|
+
};
|
|
29727
29944
|
}
|
|
29728
|
-
|
|
29729
|
-
|
|
29730
|
-
|
|
29731
|
-
|
|
29732
|
-
|
|
29733
|
-
|
|
29734
|
-
|
|
29735
|
-
|
|
29736
|
-
|
|
29737
|
-
|
|
29738
|
-
|
|
29739
|
-
|
|
29740
|
-
|
|
29741
|
-
|
|
29742
|
-
|
|
29743
|
-
|
|
29744
|
-
|
|
29745
|
-
|
|
29746
|
-
|
|
29945
|
+
if (initialGraph.type === "linear-system" && state.type === "linear-system") {
|
|
29946
|
+
return _extends({}, initialGraph, {
|
|
29947
|
+
coords: state.coords
|
|
29948
|
+
});
|
|
29949
|
+
}
|
|
29950
|
+
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
29951
|
+
return _extends({}, initialGraph, {
|
|
29952
|
+
coords: state.coords
|
|
29953
|
+
});
|
|
29954
|
+
}
|
|
29955
|
+
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
29956
|
+
return _extends({}, initialGraph, {
|
|
29957
|
+
coords: state.coords[0]
|
|
29958
|
+
});
|
|
29959
|
+
}
|
|
29960
|
+
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
29961
|
+
return _extends({}, initialGraph, {
|
|
29962
|
+
coords: state.coords[0]
|
|
29963
|
+
});
|
|
29964
|
+
}
|
|
29965
|
+
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
29966
|
+
return _extends({}, initialGraph, {
|
|
29967
|
+
coords: state.coords
|
|
29968
|
+
});
|
|
29969
|
+
}
|
|
29970
|
+
if (state.type === "point" && initialGraph.type === "point") {
|
|
29971
|
+
return _extends({}, initialGraph, {
|
|
29972
|
+
coords: state.coords
|
|
29973
|
+
});
|
|
29747
29974
|
}
|
|
29975
|
+
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
29748
29976
|
}
|
|
29749
29977
|
const getDefaultSegments = ({
|
|
29750
29978
|
graph,
|
|
@@ -29781,10 +30009,10 @@ const getLineCoords = ({
|
|
|
29781
30009
|
range,
|
|
29782
30010
|
step
|
|
29783
30011
|
}) => {
|
|
29784
|
-
var _graph$
|
|
30012
|
+
var _graph$coords2;
|
|
29785
30013
|
return (
|
|
29786
30014
|
// Return two lines for a linear system, one for a ray or linear
|
|
29787
|
-
((_graph$
|
|
30015
|
+
((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
|
|
29788
30016
|
);
|
|
29789
30017
|
};
|
|
29790
30018
|
const getPolygonCoords = ({
|
|
@@ -29792,7 +30020,8 @@ const getPolygonCoords = ({
|
|
|
29792
30020
|
range,
|
|
29793
30021
|
step
|
|
29794
30022
|
}) => {
|
|
29795
|
-
|
|
30023
|
+
var _graph$coords3;
|
|
30024
|
+
let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
|
|
29796
30025
|
if (coords) {
|
|
29797
30026
|
return coords;
|
|
29798
30027
|
}
|
|
@@ -29846,6 +30075,13 @@ const renderGraph = props => {
|
|
|
29846
30075
|
graphState: state,
|
|
29847
30076
|
dispatch: dispatch
|
|
29848
30077
|
});
|
|
30078
|
+
case "point":
|
|
30079
|
+
return /*#__PURE__*/React.createElement(PointGraph, {
|
|
30080
|
+
graphState: state,
|
|
30081
|
+
dispatch: dispatch
|
|
30082
|
+
});
|
|
30083
|
+
case "circle":
|
|
30084
|
+
throw new Error("the circle graph type is not yet implemented");
|
|
29849
30085
|
default:
|
|
29850
30086
|
return new UnreachableCaseError(type);
|
|
29851
30087
|
}
|
|
@@ -29853,10 +30089,24 @@ const renderGraph = props => {
|
|
|
29853
30089
|
const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
29854
30090
|
const [width, height] = props.box;
|
|
29855
30091
|
const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
|
|
30092
|
+
const prevState = useRef(state);
|
|
30093
|
+
useEffect(() => {
|
|
30094
|
+
if (prevState.current !== state) {
|
|
30095
|
+
props.onChange({
|
|
30096
|
+
graph: state
|
|
30097
|
+
});
|
|
30098
|
+
}
|
|
30099
|
+
prevState.current = state;
|
|
30100
|
+
}, [props, state]);
|
|
29856
30101
|
React.useImperativeHandle(ref, () => ({
|
|
29857
30102
|
getUserInput: () => getGradableGraph(state, props.graph)
|
|
29858
30103
|
}));
|
|
29859
|
-
return /*#__PURE__*/React.createElement(
|
|
30104
|
+
return /*#__PURE__*/React.createElement(GraphStateContext.Provider, {
|
|
30105
|
+
value: {
|
|
30106
|
+
state,
|
|
30107
|
+
dispatch
|
|
30108
|
+
}
|
|
30109
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
29860
30110
|
style: {
|
|
29861
30111
|
width,
|
|
29862
30112
|
height,
|
|
@@ -29882,12 +30132,12 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
29882
30132
|
zoom: false,
|
|
29883
30133
|
width: width,
|
|
29884
30134
|
height: height
|
|
29885
|
-
}, /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
30135
|
+
}, /*#__PURE__*/React.createElement(SvgDefs, null), /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
29886
30136
|
lockedFigures: props.lockedFigures
|
|
29887
30137
|
}), renderGraph({
|
|
29888
30138
|
state,
|
|
29889
30139
|
dispatch
|
|
29890
|
-
}))));
|
|
30140
|
+
})))));
|
|
29891
30141
|
});
|
|
29892
30142
|
|
|
29893
30143
|
const {
|
|
@@ -31024,6 +31274,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
31024
31274
|
$(this.angle).on("move", () => {
|
|
31025
31275
|
var _this$angle;
|
|
31026
31276
|
this.onChange({
|
|
31277
|
+
// @ts-expect-error Type '{ coords: any; type: "angle"; showAngles?: boolean | undefined; allowReflexAngles?: boolean | undefined; angleOffsetDeg?: number | undefined; snapDegrees?: number | undefined; match?: "congruent" | undefined; }' is not assignable to type 'InteractiveGraphState | undefined'.
|
|
31027
31278
|
graph: _extends({}, graph, {
|
|
31028
31279
|
coords: (_this$angle = this.angle) == null ? void 0 : _this$angle.getClockwiseCoords()
|
|
31029
31280
|
})
|
|
@@ -31536,18 +31787,18 @@ class InteractiveGraph extends React.Component {
|
|
|
31536
31787
|
const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
|
|
31537
31788
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
31538
31789
|
}
|
|
31539
|
-
static validate(
|
|
31540
|
-
// TODO(kevinb): rename state to userInput
|
|
31541
|
-
// state === userInput
|
|
31542
|
-
state, rubric, component) {
|
|
31790
|
+
static validate(userInput, rubric, component) {
|
|
31543
31791
|
// When nothing has moved, there will neither be coords nor the
|
|
31544
31792
|
// circle's center/radius fields. When those fields are absent, skip
|
|
31545
31793
|
// all these checks; just go mark the answer as empty.
|
|
31546
|
-
|
|
31547
|
-
|
|
31548
|
-
|
|
31549
|
-
|
|
31550
|
-
|
|
31794
|
+
const hasValue = Boolean(
|
|
31795
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
31796
|
+
userInput.coords ||
|
|
31797
|
+
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
31798
|
+
userInput.center && userInput.radius);
|
|
31799
|
+
if (userInput.type === rubric.correct.type && hasValue) {
|
|
31800
|
+
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
31801
|
+
const guess = userInput.coords;
|
|
31551
31802
|
const correct = rubric.correct.coords;
|
|
31552
31803
|
|
|
31553
31804
|
// If both of the guess points are on the correct line, it's
|
|
@@ -31564,8 +31815,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31564
31815
|
message: null
|
|
31565
31816
|
};
|
|
31566
31817
|
}
|
|
31567
|
-
} else if (
|
|
31568
|
-
const guess =
|
|
31818
|
+
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
31819
|
+
const guess = userInput.coords;
|
|
31569
31820
|
const correct = rubric.correct.coords;
|
|
31570
31821
|
if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
|
|
31571
31822
|
return {
|
|
@@ -31575,9 +31826,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31575
31826
|
message: null
|
|
31576
31827
|
};
|
|
31577
31828
|
}
|
|
31578
|
-
} else if (
|
|
31829
|
+
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
31579
31830
|
// If the parabola coefficients match, it's correct.
|
|
31580
|
-
const guessCoeffs = this.getQuadraticCoefficients(
|
|
31831
|
+
const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
|
|
31581
31832
|
const correctCoeffs = this.getQuadraticCoefficients(
|
|
31582
31833
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31583
31834
|
rubric.correct.coords);
|
|
@@ -31589,8 +31840,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31589
31840
|
message: null
|
|
31590
31841
|
};
|
|
31591
31842
|
}
|
|
31592
|
-
} else if (
|
|
31593
|
-
const guessCoeffs = this.getSinusoidCoefficients(
|
|
31843
|
+
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
31844
|
+
const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
|
|
31594
31845
|
const correctCoeffs = this.getSinusoidCoefficients(
|
|
31595
31846
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31596
31847
|
rubric.correct.coords);
|
|
@@ -31605,8 +31856,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31605
31856
|
message: null
|
|
31606
31857
|
};
|
|
31607
31858
|
}
|
|
31608
|
-
} else if (
|
|
31609
|
-
if (deepEq$1(
|
|
31859
|
+
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
31860
|
+
if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
|
|
31610
31861
|
return {
|
|
31611
31862
|
type: "points",
|
|
31612
31863
|
earned: 1,
|
|
@@ -31614,10 +31865,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31614
31865
|
message: null
|
|
31615
31866
|
};
|
|
31616
31867
|
}
|
|
31617
|
-
} else if (
|
|
31618
|
-
var _state$coords;
|
|
31868
|
+
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
31619
31869
|
let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
|
|
31620
|
-
const guess =
|
|
31870
|
+
const guess = userInput.coords.slice();
|
|
31621
31871
|
correct = correct.slice();
|
|
31622
31872
|
// Everything's already rounded so we shouldn't need to do an
|
|
31623
31873
|
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
@@ -31634,9 +31884,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31634
31884
|
message: null
|
|
31635
31885
|
};
|
|
31636
31886
|
}
|
|
31637
|
-
} else if (
|
|
31638
|
-
var
|
|
31639
|
-
const guess = (
|
|
31887
|
+
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
31888
|
+
var _userInput$coords, _rubric$correct$coord;
|
|
31889
|
+
const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
|
|
31640
31890
|
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
31641
31891
|
const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
|
|
31642
31892
|
let match;
|
|
@@ -31660,9 +31910,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31660
31910
|
message: null
|
|
31661
31911
|
};
|
|
31662
31912
|
}
|
|
31663
|
-
} else if (
|
|
31913
|
+
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
31664
31914
|
var _rubric$correct$coord2;
|
|
31665
|
-
let guess =
|
|
31915
|
+
let guess = userInput.coords.slice();
|
|
31666
31916
|
let correct = (_rubric$correct$coord2 = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord2.slice();
|
|
31667
31917
|
guess = _.invoke(guess, "sort").sort();
|
|
31668
31918
|
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
@@ -31675,8 +31925,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31675
31925
|
message: null
|
|
31676
31926
|
};
|
|
31677
31927
|
}
|
|
31678
|
-
} else if (
|
|
31679
|
-
const guess =
|
|
31928
|
+
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
31929
|
+
const guess = userInput.coords;
|
|
31680
31930
|
const correct = rubric.correct.coords;
|
|
31681
31931
|
if (
|
|
31682
31932
|
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
@@ -31690,8 +31940,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31690
31940
|
message: null
|
|
31691
31941
|
};
|
|
31692
31942
|
}
|
|
31693
|
-
} else if (
|
|
31694
|
-
const guess =
|
|
31943
|
+
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
31944
|
+
const guess = userInput.coords;
|
|
31695
31945
|
const correct = rubric.correct.coords;
|
|
31696
31946
|
let match;
|
|
31697
31947
|
if (rubric.correct.match === "congruent") {
|
|
@@ -31730,7 +31980,7 @@ class InteractiveGraph extends React.Component {
|
|
|
31730
31980
|
|
|
31731
31981
|
// The input wasn't correct, so check if it's a blank input or if it's
|
|
31732
31982
|
// actually just wrong
|
|
31733
|
-
if (!hasValue || _.isEqual(
|
|
31983
|
+
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
31734
31984
|
// We're where we started.
|
|
31735
31985
|
return {
|
|
31736
31986
|
type: "invalid",
|
|
@@ -41931,7 +42181,7 @@ const init = function init(options) {
|
|
|
41931
42181
|
|
|
41932
42182
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41933
42183
|
const libName = "@khanacademy/perseus";
|
|
41934
|
-
const libVersion = "
|
|
42184
|
+
const libVersion = "21.1.0";
|
|
41935
42185
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41936
42186
|
|
|
41937
42187
|
const apiVersion = {
|
|
@@ -44183,6 +44433,140 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44183
44433
|
replaceDeprecatedWidgets();
|
|
44184
44434
|
};
|
|
44185
44435
|
|
|
44436
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44437
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44438
|
+
|
|
44439
|
+
/**
|
|
44440
|
+
* Regex for widget placeholders in a string.
|
|
44441
|
+
*
|
|
44442
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
44443
|
+
* Second capture group is the widget type (ex. "radio)
|
|
44444
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44445
|
+
*/
|
|
44446
|
+
function getWidgetRegex() {
|
|
44447
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
44448
|
+
}
|
|
44449
|
+
|
|
44450
|
+
/**
|
|
44451
|
+
* Add a widget placeholder using the widget ID.
|
|
44452
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
44453
|
+
*
|
|
44454
|
+
* @param {string} id
|
|
44455
|
+
* @returns {string}
|
|
44456
|
+
*/
|
|
44457
|
+
function addWidget(id) {
|
|
44458
|
+
return `[[☃ ${id}]]`;
|
|
44459
|
+
}
|
|
44460
|
+
|
|
44461
|
+
/**
|
|
44462
|
+
* Get a widget type by a widget's ID
|
|
44463
|
+
* (please don't derive type from ID)
|
|
44464
|
+
*
|
|
44465
|
+
* @param {string} widgetId the ID of the widget
|
|
44466
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44467
|
+
* @returns {string} the widget type (ie "radio")
|
|
44468
|
+
*/
|
|
44469
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44470
|
+
var _widget$type;
|
|
44471
|
+
const widget = widgetMap[WidgetId];
|
|
44472
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
44473
|
+
}
|
|
44474
|
+
|
|
44475
|
+
/**
|
|
44476
|
+
* Does the content have a specific type of widget?
|
|
44477
|
+
*
|
|
44478
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44479
|
+
* @param {string} content the string to search through
|
|
44480
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44481
|
+
* @returns {boolean} if the content includes the widget type
|
|
44482
|
+
*/
|
|
44483
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44484
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44485
|
+
}
|
|
44486
|
+
|
|
44487
|
+
/**
|
|
44488
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44489
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44490
|
+
*
|
|
44491
|
+
* Content should contain Perseus widget placeholders,
|
|
44492
|
+
* which look like: '[[☃ radio 1]]'.
|
|
44493
|
+
*
|
|
44494
|
+
* @param {string} content
|
|
44495
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
44496
|
+
*/
|
|
44497
|
+
function getWidgetIdsFromContent(content) {
|
|
44498
|
+
const widgets = [];
|
|
44499
|
+
const localWidgetRegex = getWidgetRegex();
|
|
44500
|
+
let match = localWidgetRegex.exec(content);
|
|
44501
|
+
while (match !== null) {
|
|
44502
|
+
widgets.push(match[1]);
|
|
44503
|
+
match = localWidgetRegex.exec(content);
|
|
44504
|
+
}
|
|
44505
|
+
return widgets;
|
|
44506
|
+
}
|
|
44507
|
+
|
|
44508
|
+
/**
|
|
44509
|
+
* Get a list of widget IDs from content,
|
|
44510
|
+
* but only for specific widget types
|
|
44511
|
+
*
|
|
44512
|
+
* @param {string} type the type of widget (ie "radio")
|
|
44513
|
+
* @param {string} content the string to parse
|
|
44514
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44515
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
44516
|
+
*/
|
|
44517
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
44518
|
+
const rv = [];
|
|
44519
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
44520
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
44521
|
+
const widget = widgetMap[widgetId];
|
|
44522
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
44523
|
+
rv.push(widgetId);
|
|
44524
|
+
}
|
|
44525
|
+
});
|
|
44526
|
+
return rv;
|
|
44527
|
+
}
|
|
44528
|
+
|
|
44529
|
+
/**
|
|
44530
|
+
* Pull the widget map out of ItemData
|
|
44531
|
+
*
|
|
44532
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44533
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44534
|
+
*/
|
|
44535
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
44536
|
+
return itemData.question.widgets;
|
|
44537
|
+
}
|
|
44538
|
+
|
|
44539
|
+
/**
|
|
44540
|
+
* Get the widget information from a WidgetMap
|
|
44541
|
+
*
|
|
44542
|
+
* @param {string} widgetId the ID of the widget
|
|
44543
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
44544
|
+
*/
|
|
44545
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
44546
|
+
var _widgetMap$widgetId;
|
|
44547
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
44548
|
+
}
|
|
44549
|
+
|
|
44550
|
+
/**
|
|
44551
|
+
* Get select widgets from a widget map.
|
|
44552
|
+
* Useful for multi-items that needs to split
|
|
44553
|
+
* one PerseusItem into several
|
|
44554
|
+
*
|
|
44555
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
44556
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
44557
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
44558
|
+
*/
|
|
44559
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
44560
|
+
const widgets = {};
|
|
44561
|
+
widgetIds.forEach(widgetId => {
|
|
44562
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
44563
|
+
if (widget) {
|
|
44564
|
+
widgets[widgetId] = widget;
|
|
44565
|
+
}
|
|
44566
|
+
});
|
|
44567
|
+
return widgets;
|
|
44568
|
+
}
|
|
44569
|
+
|
|
44186
44570
|
/**
|
|
44187
44571
|
* This function extracts the answers from the widgets.
|
|
44188
44572
|
*
|
|
@@ -44307,7 +44691,7 @@ function getAnswersFromWidgets(widgets) {
|
|
|
44307
44691
|
// E.g. 'item 1\nitem 2\nitem 3'
|
|
44308
44692
|
const orderer = widget;
|
|
44309
44693
|
if ((_orderer$options = orderer.options) != null && _orderer$options.correctOptions) {
|
|
44310
|
-
answers.push(orderer.options.correctOptions
|
|
44694
|
+
answers.push(joinOptionContents(orderer.options.correctOptions));
|
|
44311
44695
|
}
|
|
44312
44696
|
break;
|
|
44313
44697
|
case "sorter":
|
|
@@ -44366,6 +44750,28 @@ function getAnswersFromWidgets(widgets) {
|
|
|
44366
44750
|
return answers;
|
|
44367
44751
|
}
|
|
44368
44752
|
|
|
44753
|
+
/**
|
|
44754
|
+
* Join the content of the options together.
|
|
44755
|
+
* @param {Array<{content: string}>} options
|
|
44756
|
+
* @returns {string}
|
|
44757
|
+
*/
|
|
44758
|
+
const joinOptionContents = options => options.map(({
|
|
44759
|
+
content
|
|
44760
|
+
}) => content).join("\n");
|
|
44761
|
+
|
|
44762
|
+
/**
|
|
44763
|
+
* Convert an index to an option letter.
|
|
44764
|
+
* @param {number} index
|
|
44765
|
+
* @returns {string}
|
|
44766
|
+
* @example
|
|
44767
|
+
* toOptionLetter(0) // 'A'
|
|
44768
|
+
* toOptionLetter(1) // 'B'
|
|
44769
|
+
* toOptionLetter(25) // 'Z'
|
|
44770
|
+
* // Once the index goes past 25, it will start returning special characters
|
|
44771
|
+
* toOptionLetter(26) // '['
|
|
44772
|
+
*/
|
|
44773
|
+
const toOptionLetter = index => String.fromCharCode("A".charCodeAt(0) + index);
|
|
44774
|
+
|
|
44369
44775
|
/**
|
|
44370
44776
|
* Inject a string equivalent of the widgets into the content.
|
|
44371
44777
|
*
|
|
@@ -44376,7 +44782,7 @@ function getAnswersFromWidgets(widgets) {
|
|
|
44376
44782
|
* @param {PerseusRenderer["widgets"]} widgets
|
|
44377
44783
|
* @returns
|
|
44378
44784
|
*/
|
|
44379
|
-
function injectWidgets(content, widgets) {
|
|
44785
|
+
function injectWidgets(content, widgets, widgetProps) {
|
|
44380
44786
|
// The types for taskProgress.itemData are not well defined,
|
|
44381
44787
|
// so there is a chance that widgets or content could be undefined.
|
|
44382
44788
|
// In that scenario, simply exit early and return the content as is.
|
|
@@ -44396,12 +44802,20 @@ function injectWidgets(content, widgets) {
|
|
|
44396
44802
|
switch (widget.type) {
|
|
44397
44803
|
case "radio":
|
|
44398
44804
|
// Replace radio with the radio options
|
|
44399
|
-
// '[[☃ Radio 1]]' -> '
|
|
44805
|
+
// '[[☃ Radio 1]]' -> 'choice 1\nchoice 2\nchoice 3'
|
|
44806
|
+
// or if the current widget state is available with proper order,
|
|
44807
|
+
// '[[☃ Radio 1]]' ->
|
|
44808
|
+
// 'Option A: choice 1\nOption B: choice 2\nOption C: choice 3'
|
|
44400
44809
|
const radio = widget;
|
|
44810
|
+
const radioProps = widgetProps == null ? void 0 : widgetProps[widgetID];
|
|
44401
44811
|
if ((_radio$options = radio.options) != null && (_radio$options = _radio$options.choices) != null && _radio$options.length) {
|
|
44402
44812
|
var _radio$options2;
|
|
44403
|
-
let radioContext =
|
|
44404
|
-
|
|
44813
|
+
let radioContext = joinOptionContents(radioProps ? radioProps.choices.map(({
|
|
44814
|
+
content
|
|
44815
|
+
}, i) => ({
|
|
44816
|
+
content: `Option ${toOptionLetter(i)}: ${content}`
|
|
44817
|
+
})) : radio.options.choices);
|
|
44818
|
+
if (!radioProps && (_radio$options2 = radio.options) != null && _radio$options2.randomize) {
|
|
44405
44819
|
radioContext += "\nThose options are displayed in a different order to the user. If the user says the letter, number, or ordinal number, always ask them clarify which option they are referring to.\n";
|
|
44406
44820
|
}
|
|
44407
44821
|
context = context.replace(`[[☃ ${widgetID}]]`, radioContext);
|
|
@@ -44510,7 +44924,7 @@ function injectWidgets(content, widgets) {
|
|
|
44510
44924
|
// '[[☃ Orderer 1]]' -> 'option 1\noption 2\noption 3'
|
|
44511
44925
|
const orderer = widget;
|
|
44512
44926
|
if ((_orderer$options2 = orderer.options) != null && _orderer$options2.options) {
|
|
44513
|
-
context = context.replace(`[[☃ ${widgetID}]]`, orderer.options.options
|
|
44927
|
+
context = context.replace(`[[☃ ${widgetID}]]`, joinOptionContents(orderer.options.options));
|
|
44514
44928
|
}
|
|
44515
44929
|
break;
|
|
44516
44930
|
case "sorter":
|
|
@@ -44593,18 +45007,15 @@ const INDIVIDUAL_ANSWER_WIDGETS = ["interactive-graph", "categorizer", "grapher"
|
|
|
44593
45007
|
|
|
44594
45008
|
/* Widgets that are supported for automatic scoring */
|
|
44595
45009
|
const SUPPORTED_WIDGETS = ["radio", "numeric-input", "input-number", "expression", ...INDIVIDUAL_ANSWER_WIDGETS];
|
|
44596
|
-
const getWidgetTypeFromWidgetKey = widgetKey => {
|
|
44597
|
-
return widgetKey.split(" ")[0];
|
|
44598
|
-
};
|
|
44599
45010
|
|
|
44600
45011
|
/* Verify if the perseus item has supported widgets for automatic scoring */
|
|
44601
|
-
const isWrongAnswerSupported =
|
|
44602
|
-
return
|
|
45012
|
+
const isWrongAnswerSupported = (widgetIds, widgetMap) => {
|
|
45013
|
+
return widgetIds.length !== 0 && widgetIds.every(widgetId => SUPPORTED_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap)));
|
|
44603
45014
|
};
|
|
44604
45015
|
|
|
44605
|
-
/* Verify if the widget
|
|
44606
|
-
const shouldHaveIndividualAnswer =
|
|
44607
|
-
return INDIVIDUAL_ANSWER_WIDGETS.includes(
|
|
45016
|
+
/* Verify if the widget ID has an individual answer for the coach report view */
|
|
45017
|
+
const shouldHaveIndividualAnswer = (widgetId, widgetMap) => {
|
|
45018
|
+
return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap));
|
|
44608
45019
|
};
|
|
44609
45020
|
|
|
44610
45021
|
/* Returns the answer userInput submission for currently supported widgets */
|
|
@@ -44623,29 +45034,30 @@ const getAnswerFromUserInput = (widgetType, userInput) => {
|
|
|
44623
45034
|
return userInput;
|
|
44624
45035
|
};
|
|
44625
45036
|
|
|
44626
|
-
/* Returns the correct answer for a given widget
|
|
45037
|
+
/* Returns the correct answer for a given widget ID and Perseus Item */
|
|
44627
45038
|
// TODO (LEMS-1835): We should fix the resonse type from getWidget to be specific.
|
|
44628
45039
|
// TODO (LEMS-1836): We should also consider adding the getOneCorrectAnswerFromRubric method to all widgets.
|
|
44629
|
-
const
|
|
44630
|
-
const rubric = itemData.question.widgets[
|
|
44631
|
-
const
|
|
45040
|
+
const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
|
|
45041
|
+
const rubric = itemData.question.widgets[widgetId].options;
|
|
45042
|
+
const widgetMap = getWidgetsMapFromItemData(itemData);
|
|
45043
|
+
const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
|
|
44632
45044
|
const widget = getWidget(widgetType);
|
|
44633
45045
|
|
|
44634
45046
|
// @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
|
|
44635
45047
|
return widget == null || widget.getOneCorrectAnswerFromRubric == null ? void 0 : widget.getOneCorrectAnswerFromRubric(rubric);
|
|
44636
45048
|
};
|
|
44637
45049
|
|
|
44638
|
-
/* Verify if the widget
|
|
44639
|
-
const
|
|
44640
|
-
return perseusItem.question.content.indexOf(
|
|
45050
|
+
/* Verify if the widget ID exists in the content string of the Perseus Item */
|
|
45051
|
+
const isWidgetIdInContent = (perseusItem, widgetId) => {
|
|
45052
|
+
return perseusItem.question.content.indexOf(widgetId) !== -1;
|
|
44641
45053
|
};
|
|
44642
45054
|
|
|
44643
|
-
/* Return an array of all the widget
|
|
44644
|
-
const
|
|
45055
|
+
/* Return an array of all the widget IDs that exist in the content string of a Perseus Item */
|
|
45056
|
+
const getValidWidgetIds = perseusItem => {
|
|
44645
45057
|
const {
|
|
44646
45058
|
widgets
|
|
44647
45059
|
} = perseusItem.question;
|
|
44648
|
-
return keys(widgets).filter(
|
|
45060
|
+
return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
44649
45061
|
};
|
|
44650
45062
|
|
|
44651
45063
|
/**
|
|
@@ -44693,12 +45105,113 @@ const genericPerseusItemData = {
|
|
|
44693
45105
|
* uses a basic Perseus object and updates it with custom values as needed.
|
|
44694
45106
|
*
|
|
44695
45107
|
* @param {Partial<PerseusItem>} customFields
|
|
44696
|
-
* @returns PerseusItem
|
|
45108
|
+
* @returns {PerseusItem}
|
|
44697
45109
|
*/
|
|
44698
45110
|
function generateTestPerseusItem(customFields = {}) {
|
|
44699
45111
|
return _extends({}, genericPerseusItemData, customFields);
|
|
44700
45112
|
}
|
|
44701
45113
|
|
|
45114
|
+
/**
|
|
45115
|
+
* Creates an object with the bare amount
|
|
45116
|
+
* of data to be a properly typed RadioWidget
|
|
45117
|
+
*
|
|
45118
|
+
* @returns {RadioWidget} skeleton RadioWidget for testing
|
|
45119
|
+
*/
|
|
45120
|
+
function generateTestRadioWidget() {
|
|
45121
|
+
return {
|
|
45122
|
+
type: "radio",
|
|
45123
|
+
options: {
|
|
45124
|
+
choices: []
|
|
45125
|
+
}
|
|
45126
|
+
};
|
|
45127
|
+
}
|
|
45128
|
+
|
|
45129
|
+
/**
|
|
45130
|
+
* Creates an object with the bare amount
|
|
45131
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
45132
|
+
*
|
|
45133
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
45134
|
+
*/
|
|
45135
|
+
function generateTestInteractiveGraphWidget() {
|
|
45136
|
+
return {
|
|
45137
|
+
type: "interactive-graph",
|
|
45138
|
+
options: {
|
|
45139
|
+
step: [1, 1],
|
|
45140
|
+
gridStep: [1, 1],
|
|
45141
|
+
snapStep: [1, 1],
|
|
45142
|
+
markings: "graph",
|
|
45143
|
+
labels: ["x", "y"],
|
|
45144
|
+
showProtractor: false,
|
|
45145
|
+
showRuler: false,
|
|
45146
|
+
rulerLabel: "mm",
|
|
45147
|
+
rulerTicks: 2,
|
|
45148
|
+
range: [[-10, 10], [-10, 10]],
|
|
45149
|
+
graph: {
|
|
45150
|
+
type: "angle"
|
|
45151
|
+
},
|
|
45152
|
+
correct: {
|
|
45153
|
+
type: "angle"
|
|
45154
|
+
}
|
|
45155
|
+
}
|
|
45156
|
+
};
|
|
45157
|
+
}
|
|
45158
|
+
|
|
45159
|
+
/**
|
|
45160
|
+
* Creates an object with the bare amount
|
|
45161
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
45162
|
+
*
|
|
45163
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
45164
|
+
*/
|
|
45165
|
+
function generateTestCategorizerWidget() {
|
|
45166
|
+
return {
|
|
45167
|
+
type: "categorizer",
|
|
45168
|
+
options: {
|
|
45169
|
+
items: [],
|
|
45170
|
+
categories: [],
|
|
45171
|
+
randomizeItems: false,
|
|
45172
|
+
static: false,
|
|
45173
|
+
values: []
|
|
45174
|
+
}
|
|
45175
|
+
};
|
|
45176
|
+
}
|
|
45177
|
+
|
|
45178
|
+
/**
|
|
45179
|
+
* Creates an object with the bare amount
|
|
45180
|
+
* of data to be a properly typed ExpressionWidget
|
|
45181
|
+
*
|
|
45182
|
+
* @returns {ExpressionWidget} skeleton ExpressionWidget for testing
|
|
45183
|
+
*/
|
|
45184
|
+
function generateTestExpressionWidget() {
|
|
45185
|
+
return {
|
|
45186
|
+
type: "expression",
|
|
45187
|
+
options: {
|
|
45188
|
+
answerForms: [],
|
|
45189
|
+
buttonSets: ["basic"],
|
|
45190
|
+
functions: [],
|
|
45191
|
+
times: false
|
|
45192
|
+
}
|
|
45193
|
+
};
|
|
45194
|
+
}
|
|
45195
|
+
|
|
45196
|
+
/**
|
|
45197
|
+
* Creates an object with the bare amount
|
|
45198
|
+
* of data to be a properly typed NumericInputWidget
|
|
45199
|
+
*
|
|
45200
|
+
* @returns {NumericInputWidget} skeleton NumericInputWidget for testing
|
|
45201
|
+
*/
|
|
45202
|
+
function generateTestNumericInputWidget() {
|
|
45203
|
+
return {
|
|
45204
|
+
type: "numeric-input",
|
|
45205
|
+
options: {
|
|
45206
|
+
answers: [],
|
|
45207
|
+
labelText: "",
|
|
45208
|
+
size: "normal",
|
|
45209
|
+
coefficient: false,
|
|
45210
|
+
static: false
|
|
45211
|
+
}
|
|
45212
|
+
};
|
|
45213
|
+
}
|
|
45214
|
+
|
|
44702
45215
|
/**
|
|
44703
45216
|
* Some helpers for CEDAR logging. CEDAR convention dictates
|
|
44704
45217
|
* that enum values should be upper case. Ex: for the
|
|
@@ -44753,162 +45266,6 @@ function convertWidgetNameToEnum(name) {
|
|
|
44753
45266
|
}
|
|
44754
45267
|
return widgetEnum;
|
|
44755
45268
|
}
|
|
44756
|
-
function getWidgetTypeFromWidgetId(widgetId) {
|
|
44757
|
-
const [widgetType, widgetNumber] = widgetId.split(" ");
|
|
44758
|
-
if (!widgetNumber) {
|
|
44759
|
-
throw new Error(`Incorrect format for widget ID: ${widgetId}`);
|
|
44760
|
-
}
|
|
44761
|
-
if (!widgetNameToEnum[widgetType]) {
|
|
44762
|
-
throw new Error(`Unknown widget type (${widgetType}) in widget ID: ${widgetId}`);
|
|
44763
|
-
}
|
|
44764
|
-
return widgetType;
|
|
44765
|
-
}
|
|
44766
|
-
|
|
44767
|
-
let WidgetType = /*#__PURE__*/function (WidgetType) {
|
|
44768
|
-
WidgetType["Categorizer"] = "categorizer";
|
|
44769
|
-
WidgetType["CSProgram"] = "cs-program";
|
|
44770
|
-
WidgetType["Definition"] = "definition";
|
|
44771
|
-
WidgetType["Dropdown"] = "dropdown";
|
|
44772
|
-
WidgetType["Explanation"] = "explanation";
|
|
44773
|
-
WidgetType["Expression"] = "expression";
|
|
44774
|
-
WidgetType["GradedGroup"] = "graded-group";
|
|
44775
|
-
WidgetType["GradedGroupAnswerBar"] = "graded-group-answer-bar";
|
|
44776
|
-
WidgetType["GradedGroupSet"] = "graded-group-set";
|
|
44777
|
-
WidgetType["Grapher"] = "grapher";
|
|
44778
|
-
WidgetType["Group"] = "group";
|
|
44779
|
-
WidgetType["Iframe"] = "iframe";
|
|
44780
|
-
WidgetType["Image"] = "image";
|
|
44781
|
-
WidgetType["InputNumber"] = "input-number";
|
|
44782
|
-
WidgetType["Interaction"] = "interaction";
|
|
44783
|
-
WidgetType["InteractiveGraph"] = "interactive-graph";
|
|
44784
|
-
WidgetType["InteractiveNumberLine"] = "interactive-number-line";
|
|
44785
|
-
WidgetType["LabelImage"] = "label-image";
|
|
44786
|
-
WidgetType["Matcher"] = "matcher";
|
|
44787
|
-
WidgetType["Matrix"] = "matrix";
|
|
44788
|
-
WidgetType["Measurer"] = "measurer";
|
|
44789
|
-
WidgetType["Molecule"] = "molecule";
|
|
44790
|
-
WidgetType["NumberLine"] = "number-line";
|
|
44791
|
-
WidgetType["NumericInput"] = "numeric-input";
|
|
44792
|
-
WidgetType["Orderer"] = "orderer";
|
|
44793
|
-
WidgetType["Passage"] = "passage";
|
|
44794
|
-
WidgetType["PassageRef"] = "passage-ref";
|
|
44795
|
-
WidgetType["PassageRefTarget"] = "passage-ref-target";
|
|
44796
|
-
WidgetType["Plotter"] = "plotter";
|
|
44797
|
-
WidgetType["Protractor"] = "protractor";
|
|
44798
|
-
WidgetType["PythonProgram"] = "python-program";
|
|
44799
|
-
WidgetType["Radio"] = "radio";
|
|
44800
|
-
WidgetType["Sorter"] = "sorter";
|
|
44801
|
-
WidgetType["Table"] = "table";
|
|
44802
|
-
WidgetType["Unit"] = "unit";
|
|
44803
|
-
WidgetType["Video"] = "video";
|
|
44804
|
-
WidgetType["VideoTranscriptLink"] = "video-transcript-link";
|
|
44805
|
-
return WidgetType;
|
|
44806
|
-
}({});
|
|
44807
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44808
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44809
|
-
|
|
44810
|
-
/**
|
|
44811
|
-
* Regex for widget placeholders in a string.
|
|
44812
|
-
*
|
|
44813
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
44814
|
-
* Second capture group is the widget type (ex. "radio)
|
|
44815
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44816
|
-
*/
|
|
44817
|
-
function getWidgetRegex() {
|
|
44818
|
-
return /\[\[☃ (([a-z-]+) \d+)\]\]/g;
|
|
44819
|
-
}
|
|
44820
|
-
|
|
44821
|
-
/**
|
|
44822
|
-
* Add a widget placeholder using the widget ID.
|
|
44823
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
44824
|
-
*
|
|
44825
|
-
* @param {string} id
|
|
44826
|
-
* @returns {string}
|
|
44827
|
-
*/
|
|
44828
|
-
function addWidget(id) {
|
|
44829
|
-
return `[[☃ ${id}]]`;
|
|
44830
|
-
}
|
|
44831
|
-
|
|
44832
|
-
/**
|
|
44833
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44834
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44835
|
-
*
|
|
44836
|
-
* Content should contain Perseus widget placeholders,
|
|
44837
|
-
* which look like: '[[☃ radio 1]]'.
|
|
44838
|
-
*
|
|
44839
|
-
* @param {string} content
|
|
44840
|
-
* @returns {Array<WidgetId>} widgets
|
|
44841
|
-
*/
|
|
44842
|
-
function getAllWidgetIds(content) {
|
|
44843
|
-
const widgets = [];
|
|
44844
|
-
const localWidgetRegex = getWidgetRegex();
|
|
44845
|
-
let match = localWidgetRegex.exec(content);
|
|
44846
|
-
while (match !== null) {
|
|
44847
|
-
widgets.push(match[1]);
|
|
44848
|
-
match = localWidgetRegex.exec(content);
|
|
44849
|
-
}
|
|
44850
|
-
return widgets;
|
|
44851
|
-
}
|
|
44852
|
-
|
|
44853
|
-
/**
|
|
44854
|
-
* Check if a specific widget is an actual widget type using its name.
|
|
44855
|
-
* The widget name should include a widget type, without an instance number,
|
|
44856
|
-
* as a string. ex. 'radio'
|
|
44857
|
-
*
|
|
44858
|
-
* @param {string} widgetName
|
|
44859
|
-
* @returns {boolean}
|
|
44860
|
-
*/
|
|
44861
|
-
function isWidgetType(widgetName) {
|
|
44862
|
-
return Object.values(WidgetType).includes(widgetName);
|
|
44863
|
-
}
|
|
44864
|
-
|
|
44865
|
-
/**
|
|
44866
|
-
* Extract all widget types from a Perseus JSON content string.
|
|
44867
|
-
* This does not include the instance number and prevents duplicates.
|
|
44868
|
-
* example output: ['radio', 'categorizer']
|
|
44869
|
-
*
|
|
44870
|
-
* @param {string} content
|
|
44871
|
-
* @returns {Array<WidgetId>}
|
|
44872
|
-
*/
|
|
44873
|
-
function getAllWidgetTypes(content) {
|
|
44874
|
-
const widgetTypes = [];
|
|
44875
|
-
const localWidgetRegex = getWidgetRegex();
|
|
44876
|
-
let match = localWidgetRegex.exec(content);
|
|
44877
|
-
while (match !== null) {
|
|
44878
|
-
const matchType = match[2];
|
|
44879
|
-
if (!widgetTypes.includes(matchType) && isWidgetType(matchType)) {
|
|
44880
|
-
widgetTypes.push(matchType);
|
|
44881
|
-
}
|
|
44882
|
-
match = localWidgetRegex.exec(content);
|
|
44883
|
-
}
|
|
44884
|
-
return widgetTypes;
|
|
44885
|
-
}
|
|
44886
|
-
|
|
44887
|
-
/**
|
|
44888
|
-
* Check if a specific widget is a question widget type using its ID.
|
|
44889
|
-
* The widget ID includes the widget type and the instance number.
|
|
44890
|
-
*
|
|
44891
|
-
* @param {WidgetId} widgetId
|
|
44892
|
-
* @returns {boolean}
|
|
44893
|
-
*/
|
|
44894
|
-
function isQuestionWidgetType(widgetId) {
|
|
44895
|
-
return QUESTION_WIDGETS.includes(widgetId.split(" ")[0]);
|
|
44896
|
-
}
|
|
44897
|
-
|
|
44898
|
-
/**
|
|
44899
|
-
* Extract the widget IDs of all widgets considered question widgets. Widget IDs
|
|
44900
|
-
* include widget type and instance number.
|
|
44901
|
-
* example output: ['radio 1', 'categorizer 1']
|
|
44902
|
-
*
|
|
44903
|
-
* Content should contain Perseus widget placeholders,
|
|
44904
|
-
* which look like: '[[☃ radio 1]]'.
|
|
44905
|
-
*
|
|
44906
|
-
* @param {string} content
|
|
44907
|
-
* @returns Array<WidgetId>
|
|
44908
|
-
*/
|
|
44909
|
-
function getQuestionWidgetIds(content) {
|
|
44910
|
-
return getAllWidgetIds(content).filter(isQuestionWidgetType);
|
|
44911
|
-
}
|
|
44912
45269
|
|
|
44913
45270
|
const EditorJsonify = {
|
|
44914
45271
|
serialize: function () {
|
|
@@ -44918,5 +45275,5 @@ const EditorJsonify = {
|
|
|
44918
45275
|
}
|
|
44919
45276
|
};
|
|
44920
45277
|
|
|
44921
|
-
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated,
|
|
45278
|
+
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, violatingWidgets, allWidgets as widgets };
|
|
44922
45279
|
//# sourceMappingURL=index.js.map
|