@khanacademy/perseus 42.0.3 → 43.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/svg-image.d.ts +1 -1
- package/dist/es/index.css +0 -3
- package/dist/es/index.js +513 -674
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +4 -0
- package/dist/es/strings.js.map +1 -1
- package/dist/index.css +0 -3
- package/dist/index.d.ts +3 -6
- package/dist/index.js +489 -653
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +2 -3
- package/dist/renderer.d.ts +9 -6
- package/dist/server-item-renderer.d.ts +2 -33
- package/dist/strings.d.ts +2 -0
- package/dist/strings.js +4 -0
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +0 -5
- package/dist/validation.types.d.ts +31 -1
- package/dist/widgets/expression/expression.d.ts +3 -3
- package/dist/widgets/expression/validate-expression.d.ts +14 -0
- package/dist/widgets/group/group.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +13 -1
- package/dist/widgets/interactive-graphs/types.d.ts +1 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +10 -0
- package/dist/widgets/matrix/validate-matrix.d.ts +15 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +0 -7
- package/package.json +5 -5
- package/dist/util/deprecated-widgets/input-number.d.ts +0 -13
- package/dist/util/deprecated-widgets/modernize-widgets-utils.d.ts +0 -12
package/dist/es/index.js
CHANGED
|
@@ -22,9 +22,8 @@ import PropTypes from 'prop-types';
|
|
|
22
22
|
import { number as number$1, point, vector as vector$1, line } from '@khanacademy/kmath';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
24
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
-
import SimpleMarkdown
|
|
26
|
-
import
|
|
27
|
-
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
25
|
+
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
26
|
+
import { pureMarkdownRules, parse as parse$4 } from '@khanacademy/pure-markdown';
|
|
28
27
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
29
28
|
import Button from '@khanacademy/wonder-blocks-button';
|
|
30
29
|
import { Strut } from '@khanacademy/wonder-blocks-layout';
|
|
@@ -33,7 +32,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
33
32
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
34
33
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
35
34
|
import debounce$2 from 'lodash.debounce';
|
|
36
|
-
import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Mafs } from 'mafs';
|
|
35
|
+
import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Polyline, Mafs } from 'mafs';
|
|
37
36
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
38
37
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
39
38
|
import { useDrag } from '@use-gesture/react';
|
|
@@ -240,6 +239,8 @@ const mockStrings = {
|
|
|
240
239
|
addPoint: "Add Point",
|
|
241
240
|
removePoint: "Remove Point",
|
|
242
241
|
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
242
|
+
closePolygon: "Close shape",
|
|
243
|
+
openPolygon: "Re-open shape",
|
|
243
244
|
srPointAtCoordinates: ({
|
|
244
245
|
num,
|
|
245
246
|
x,
|
|
@@ -370,7 +371,10 @@ class InnerMathInput extends React.Component {
|
|
|
370
371
|
locale
|
|
371
372
|
} = this.context;
|
|
372
373
|
// Initialize MathQuill.MathField instance
|
|
373
|
-
this.__mathField = createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings,
|
|
374
|
+
this.__mathField = createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings,
|
|
375
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
376
|
+
// @ts-expect-error: Type 'EditableMathQuill' is not assignable to type 'MathFieldInterface'.
|
|
377
|
+
baseConfig => _extends({}, baseConfig, {
|
|
374
378
|
handlers: {
|
|
375
379
|
edit: debounce(mathField => {
|
|
376
380
|
// This handler is guaranteed to be called on change, but
|
|
@@ -2134,6 +2138,24 @@ const KhanAnswerTypes = {
|
|
|
2134
2138
|
}
|
|
2135
2139
|
};
|
|
2136
2140
|
|
|
2141
|
+
/**
|
|
2142
|
+
* Checks user input from the expression widget to see if it is scorable.
|
|
2143
|
+
*
|
|
2144
|
+
* Note: Most of the expression widget's validation requires the Rubric because
|
|
2145
|
+
* of its use of KhanAnswerTypes as a core part of scoring.
|
|
2146
|
+
*
|
|
2147
|
+
* @see `scoreExpression()` for more details.
|
|
2148
|
+
*/
|
|
2149
|
+
function validateExpression(userInput) {
|
|
2150
|
+
if (userInput === "") {
|
|
2151
|
+
return {
|
|
2152
|
+
type: "invalid",
|
|
2153
|
+
message: null
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
return null;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2137
2159
|
/* Content creators input a list of answers which are matched from top to
|
|
2138
2160
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2139
2161
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2153,6 +2175,10 @@ const KhanAnswerTypes = {
|
|
|
2153
2175
|
* - Otherwise, pass through the resulting points and message.
|
|
2154
2176
|
*/
|
|
2155
2177
|
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2178
|
+
const validationError = validateExpression(userInput);
|
|
2179
|
+
if (validationError) {
|
|
2180
|
+
return validationError;
|
|
2181
|
+
}
|
|
2156
2182
|
const options = _.clone(rubric);
|
|
2157
2183
|
_.extend(options, {
|
|
2158
2184
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2405,11 +2431,16 @@ class Expression extends React.Component {
|
|
|
2405
2431
|
getPromptJSON() {
|
|
2406
2432
|
return getPromptJSON$t(this.props, this.getUserInput());
|
|
2407
2433
|
}
|
|
2434
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2435
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'InputPath'.
|
|
2408
2436
|
focusInputPath(inputPath) {
|
|
2409
2437
|
// eslint-disable-next-line react/no-string-refs
|
|
2410
2438
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
2411
2439
|
this.refs.input.focus();
|
|
2412
2440
|
}
|
|
2441
|
+
|
|
2442
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2443
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'InputPath'.
|
|
2413
2444
|
blurInputPath(inputPath) {
|
|
2414
2445
|
var _this$refs$input;
|
|
2415
2446
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -2592,7 +2623,7 @@ const keypadConfigurationForProps = widgetOptions => {
|
|
|
2592
2623
|
times: widgetOptions.times
|
|
2593
2624
|
};
|
|
2594
2625
|
};
|
|
2595
|
-
const propUpgrades$
|
|
2626
|
+
const propUpgrades$2 = {
|
|
2596
2627
|
/* c8 ignore next */
|
|
2597
2628
|
"1": v0props => ({
|
|
2598
2629
|
times: v0props.times,
|
|
@@ -2652,10 +2683,14 @@ var Expression$1 = {
|
|
|
2652
2683
|
major: 1,
|
|
2653
2684
|
minor: 0
|
|
2654
2685
|
},
|
|
2655
|
-
propUpgrades: propUpgrades$
|
|
2686
|
+
propUpgrades: propUpgrades$2,
|
|
2656
2687
|
// For use by the editor
|
|
2657
2688
|
isLintable: true,
|
|
2689
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2690
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2658
2691
|
scorer: scoreExpression,
|
|
2692
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2693
|
+
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
2659
2694
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2660
2695
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2661
2696
|
if (correctAnswers.length === 0) {
|
|
@@ -2738,6 +2773,8 @@ const nestedMap$2 = function nestedMap(children, func, context) {
|
|
|
2738
2773
|
return nestedMap(child, func);
|
|
2739
2774
|
});
|
|
2740
2775
|
}
|
|
2776
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2777
|
+
// @ts-expect-error: T | ReadonlyArray<T> is not assignable to T
|
|
2741
2778
|
return func.call(context, children);
|
|
2742
2779
|
};
|
|
2743
2780
|
|
|
@@ -14489,7 +14526,9 @@ const constraints$2 = {
|
|
|
14489
14526
|
});
|
|
14490
14527
|
|
|
14491
14528
|
// bound the delta by the calculated bounds
|
|
14492
|
-
const boundedDelta = _.reduce(deltaBounds,
|
|
14529
|
+
const boundedDelta = _.reduce(deltaBounds,
|
|
14530
|
+
// @ts-expect-error: Type 'number[]' is not assignable to type 'Coord'.
|
|
14531
|
+
function (delta, bound) {
|
|
14493
14532
|
const lower = bound[0];
|
|
14494
14533
|
const upper = bound[1];
|
|
14495
14534
|
const deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
|
|
@@ -15020,6 +15059,7 @@ const constraints$1 = {
|
|
|
15020
15059
|
};
|
|
15021
15060
|
},
|
|
15022
15061
|
snap: function (snap) {
|
|
15062
|
+
// @ts-expect-error: Type 'boolean | Coord' is not assignable to type 'Coord'.
|
|
15023
15063
|
return function (coord) {
|
|
15024
15064
|
if (snap === null) {
|
|
15025
15065
|
// TODO(benchristel), NOTE(kevinb): this should probably return
|
|
@@ -15042,6 +15082,8 @@ const constraints$1 = {
|
|
|
15042
15082
|
return optionalPaddingPx;
|
|
15043
15083
|
}
|
|
15044
15084
|
})();
|
|
15085
|
+
|
|
15086
|
+
// @ts-expect-error: Type 'boolean | Coord' is not assignable to type 'Coord'.
|
|
15045
15087
|
return function (coord, prev, options) {
|
|
15046
15088
|
const graphie = this.graphie;
|
|
15047
15089
|
range = range || graphie.range;
|
|
@@ -15932,15 +15974,15 @@ const constraints = {
|
|
|
15932
15974
|
});
|
|
15933
15975
|
|
|
15934
15976
|
// bound the delta by the calculated bounds
|
|
15935
|
-
const boundedDelta = _.reduce(deltaBounds,
|
|
15977
|
+
const boundedDelta = _.reduce(deltaBounds,
|
|
15978
|
+
// @ts-expect-error - TS2345 - Type 'number[]' is not assignable to type 'Coord'.
|
|
15979
|
+
function (delta, bound) {
|
|
15936
15980
|
const lower = bound[0];
|
|
15937
15981
|
const upper = bound[1];
|
|
15938
15982
|
const deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
|
|
15939
15983
|
const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
|
|
15940
15984
|
return [deltaX, deltaY];
|
|
15941
15985
|
}, delta);
|
|
15942
|
-
|
|
15943
|
-
// @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'Coord'.
|
|
15944
15986
|
return vector$1.add(prevCoord, boundedDelta);
|
|
15945
15987
|
};
|
|
15946
15988
|
}
|
|
@@ -16826,6 +16868,7 @@ class ImageLoader extends React.Component {
|
|
|
16826
16868
|
|
|
16827
16869
|
this.img = new Image();
|
|
16828
16870
|
this.img.onload = this.handleLoad;
|
|
16871
|
+
// @ts-expect-error: Type 'string | Event' is not assignable to type 'Event'.
|
|
16829
16872
|
this.img.onerror = this.handleError;
|
|
16830
16873
|
this.img.src = this.props.src;
|
|
16831
16874
|
};
|
|
@@ -17916,6 +17959,7 @@ class InteractionTracker {
|
|
|
17916
17959
|
this.widgetType = widgetType;
|
|
17917
17960
|
this.widgetID = widgetID;
|
|
17918
17961
|
this.setting = setting;
|
|
17962
|
+
// @ts-expect-error: Type 'T | undefined' is not assignable to type 'T'.
|
|
17919
17963
|
this.track = this._track;
|
|
17920
17964
|
}
|
|
17921
17965
|
}
|
|
@@ -17926,7 +17970,7 @@ class InteractionTracker {
|
|
|
17926
17970
|
*/
|
|
17927
17971
|
const arrayRules = {
|
|
17928
17972
|
fence: {
|
|
17929
|
-
match: SimpleMarkdown
|
|
17973
|
+
match: SimpleMarkdown.defaultRules.fence.match,
|
|
17930
17974
|
order: 1,
|
|
17931
17975
|
parse: (capture, state, parse) => ({
|
|
17932
17976
|
type: "codeBlock",
|
|
@@ -17935,14 +17979,14 @@ const arrayRules = {
|
|
|
17935
17979
|
})
|
|
17936
17980
|
},
|
|
17937
17981
|
paragraph: {
|
|
17938
|
-
match: SimpleMarkdown
|
|
17982
|
+
match: SimpleMarkdown.defaultRules.paragraph.match,
|
|
17939
17983
|
order: 2,
|
|
17940
17984
|
parse: (capture, state, parse) => ({
|
|
17941
17985
|
content: capture[1]
|
|
17942
17986
|
})
|
|
17943
17987
|
}
|
|
17944
17988
|
};
|
|
17945
|
-
const builtArrayParser = SimpleMarkdown
|
|
17989
|
+
const builtArrayParser = SimpleMarkdown.parserFor(arrayRules);
|
|
17946
17990
|
|
|
17947
17991
|
// This should just return an array of strings! magick!
|
|
17948
17992
|
const parseToArray = source => {
|
|
@@ -18367,7 +18411,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18367
18411
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18368
18412
|
table: _extends({}, pureMarkdownRules.table, {
|
|
18369
18413
|
react: function (node, output, state) {
|
|
18370
|
-
const table = SimpleMarkdown
|
|
18414
|
+
const table = SimpleMarkdown.defaultRules.table.react(node, output, state);
|
|
18371
18415
|
if (!state.isMobile) {
|
|
18372
18416
|
return table;
|
|
18373
18417
|
}
|
|
@@ -18416,7 +18460,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18416
18460
|
contents = rules$1.lint.react(node.table, output, state);
|
|
18417
18461
|
}
|
|
18418
18462
|
} else {
|
|
18419
|
-
const tableOutput = SimpleMarkdown
|
|
18463
|
+
const tableOutput = SimpleMarkdown.defaultRules.table.react(node.table, output, state);
|
|
18420
18464
|
const caption = /*#__PURE__*/React.createElement("caption", {
|
|
18421
18465
|
key: "caption",
|
|
18422
18466
|
className: "perseus-table-title"
|
|
@@ -18486,7 +18530,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18486
18530
|
// window and handle reverse tabnapping phishing attacks
|
|
18487
18531
|
link: _extends({}, pureMarkdownRules.link, {
|
|
18488
18532
|
react: function (node, output, state) {
|
|
18489
|
-
const link = SimpleMarkdown
|
|
18533
|
+
const link = SimpleMarkdown.defaultRules.link.react(node, output, state);
|
|
18490
18534
|
const href = link.props.href;
|
|
18491
18535
|
let rel = null;
|
|
18492
18536
|
// The href prop should always be here, but just in case somehow the article's
|
|
@@ -18519,7 +18563,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18519
18563
|
className: "perseus-markdown-alt perseus-sr-only"
|
|
18520
18564
|
}, output(node.content, state));
|
|
18521
18565
|
}
|
|
18522
|
-
return SimpleMarkdown
|
|
18566
|
+
return SimpleMarkdown.defaultRules.codeBlock.react(node, output, state);
|
|
18523
18567
|
}
|
|
18524
18568
|
}),
|
|
18525
18569
|
// The lint rule never actually matches anything.
|
|
@@ -18561,7 +18605,7 @@ const inlineNodeTypes = {
|
|
|
18561
18605
|
};
|
|
18562
18606
|
|
|
18563
18607
|
// @ts-expect-error - TS2345 - Argument of type '{ readonly columns: { readonly react: (node: any, output: any, state: any) => Element; readonly order: -2; readonly match: any; readonly parse: (capture: any, parse: any, state: any) => any; }; ... 35 more ...; readonly text: TextInOutRule; }' is not assignable to parameter of type 'ParserRules'.
|
|
18564
|
-
const builtParser$1 = SimpleMarkdown
|
|
18608
|
+
const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
|
|
18565
18609
|
const parse$3 = (source, state) => {
|
|
18566
18610
|
const paragraphedSource = source + "\n\n";
|
|
18567
18611
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
@@ -18654,10 +18698,10 @@ var PerseusMarkdown = {
|
|
|
18654
18698
|
traverseContent: traverseContent,
|
|
18655
18699
|
parse: parse$3,
|
|
18656
18700
|
parseInline: inlineParser,
|
|
18657
|
-
reactFor: SimpleMarkdown
|
|
18658
|
-
ruleOutput: SimpleMarkdown
|
|
18659
|
-
basicOutput: SimpleMarkdown
|
|
18660
|
-
sanitizeUrl: SimpleMarkdown
|
|
18701
|
+
reactFor: SimpleMarkdown.reactFor,
|
|
18702
|
+
ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
|
|
18703
|
+
basicOutput: SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules$1, "react")),
|
|
18704
|
+
sanitizeUrl: SimpleMarkdown.sanitizeUrl
|
|
18661
18705
|
};
|
|
18662
18706
|
|
|
18663
18707
|
class QuestionParagraph extends React.Component {
|
|
@@ -18674,337 +18718,6 @@ class QuestionParagraph extends React.Component {
|
|
|
18674
18718
|
}
|
|
18675
18719
|
}
|
|
18676
18720
|
|
|
18677
|
-
// Methods to convert input-number widgets to numeric-input widgets
|
|
18678
|
-
|
|
18679
|
-
const inputNumberToNumericInput = json => {
|
|
18680
|
-
// First we need to create a map of the old input-number keys to the new numeric-input keys
|
|
18681
|
-
// so that we can ensure we update the content and widgets accordingly
|
|
18682
|
-
const renameMap = getInputNumberRenameMap(json);
|
|
18683
|
-
|
|
18684
|
-
// Then we can use this to update the JSON
|
|
18685
|
-
return convertInputNumberJson(json, renameMap);
|
|
18686
|
-
};
|
|
18687
|
-
|
|
18688
|
-
// We need to be able to run this code recursively in order to convert nested input-number widgets
|
|
18689
|
-
const convertInputNumberJson = (json, renameMap) => {
|
|
18690
|
-
const updatedContent = convertDeprecatedWidgetsInContent(json, renameMap);
|
|
18691
|
-
const updatedWidgets = convertInputNumberWidgetOptions(json, renameMap);
|
|
18692
|
-
const modernizedJson = _extends({}, json, {
|
|
18693
|
-
content: updatedContent,
|
|
18694
|
-
widgets: updatedWidgets
|
|
18695
|
-
});
|
|
18696
|
-
return modernizedJson;
|
|
18697
|
-
};
|
|
18698
|
-
|
|
18699
|
-
// Convert the input-number json in the widgets of a PerseusRenderer object
|
|
18700
|
-
const convertInputNumberWidgetOptions = (json, renameMap) => {
|
|
18701
|
-
const widgets = _extends({}, json.widgets);
|
|
18702
|
-
// The question.widgets is a dictionary map of widgets, so we need to loop through in order to convert input-number to numeric-input
|
|
18703
|
-
// which can exist as both the key or as a value on widget.type.
|
|
18704
|
-
for (const key of Object.keys(widgets)) {
|
|
18705
|
-
// Loop through the keys of the widgets dictionary
|
|
18706
|
-
if (widgets[key].options.widgets) {
|
|
18707
|
-
widgets[key].options = _extends({}, inputNumberToNumericInput(widgets[key].options));
|
|
18708
|
-
}
|
|
18709
|
-
// Check if the widget is an input-number
|
|
18710
|
-
if (widgets[key].type === "input-number") {
|
|
18711
|
-
// If the answerType is not number or percent, we need to provide
|
|
18712
|
-
// the answer form for the numeric-input widget
|
|
18713
|
-
const provideAnswerForm = widgets[key].options.answerType !== "number" && widgets[key].options.answerType !== "percent";
|
|
18714
|
-
// We need to determine the mathFormat for the numeric-input widget
|
|
18715
|
-
const mathFormat = widgets[key].options.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
18716
|
-
: widgets[key].options.answerType; // Otherwise, we can use the answerType directly
|
|
18717
|
-
|
|
18718
|
-
// We need to update the answers prop to match the numeric-input widget format
|
|
18719
|
-
const answers = [{
|
|
18720
|
-
value: widgets[key].options.value,
|
|
18721
|
-
simplify: widgets[key].options.simplify,
|
|
18722
|
-
// Input Number doesn't have a strict prop, so we default to false
|
|
18723
|
-
strict: false,
|
|
18724
|
-
// We only want to set maxError if the inexact prop is true
|
|
18725
|
-
maxError: widgets[key].options.inexact ? widgets[key].options.maxError : 0,
|
|
18726
|
-
status: "correct",
|
|
18727
|
-
// Input-number only allows correct answers
|
|
18728
|
-
message: ""
|
|
18729
|
-
}];
|
|
18730
|
-
|
|
18731
|
-
// Add the required answerForms if provided/applicable
|
|
18732
|
-
if (provideAnswerForm) {
|
|
18733
|
-
answers[0].answerForms = [mathFormat];
|
|
18734
|
-
}
|
|
18735
|
-
|
|
18736
|
-
// Update the options prop to match the numeric-input widget format
|
|
18737
|
-
const upgradedWidgetOptions = {
|
|
18738
|
-
answers,
|
|
18739
|
-
size: widgets[key].options.size,
|
|
18740
|
-
coefficient: false,
|
|
18741
|
-
// input-number doesn't have a coefficient prop
|
|
18742
|
-
labelText: "",
|
|
18743
|
-
// input-number doesn't have a labelText prop
|
|
18744
|
-
static: false,
|
|
18745
|
-
// static is always false for numeric-input
|
|
18746
|
-
rightAlign: widgets[key].options.rightAlign || false
|
|
18747
|
-
};
|
|
18748
|
-
const upgradedWidget = {
|
|
18749
|
-
options: upgradedWidgetOptions,
|
|
18750
|
-
type: "numeric-input"
|
|
18751
|
-
};
|
|
18752
|
-
const newWidgetName = renameMap[key];
|
|
18753
|
-
// Create the new key entry
|
|
18754
|
-
widgets[newWidgetName] = upgradedWidget;
|
|
18755
|
-
|
|
18756
|
-
// We need to delete the old widget key
|
|
18757
|
-
delete widgets[key];
|
|
18758
|
-
}
|
|
18759
|
-
}
|
|
18760
|
-
return widgets;
|
|
18761
|
-
};
|
|
18762
|
-
|
|
18763
|
-
// Convert the deprecated widget refs in the content string
|
|
18764
|
-
// of a PerseusRenderer object to their renamed equivalents
|
|
18765
|
-
const convertDeprecatedWidgetsInContent = (json, renameMap) => {
|
|
18766
|
-
return Object.keys(renameMap).reduce((newContent, oldKey) => {
|
|
18767
|
-
const newKey = renameMap[oldKey];
|
|
18768
|
-
return newKey ? newContent.replace(oldKey, newKey) : newContent;
|
|
18769
|
-
}, json.content);
|
|
18770
|
-
};
|
|
18771
|
-
|
|
18772
|
-
// Create a map of the old input-number keys to the new numeric-input keys
|
|
18773
|
-
const getInputNumberRenameMap = json => {
|
|
18774
|
-
const numericRegex = /(?<=\[\[\u2603 )(numeric-input \d+)(?=\]\])/g;
|
|
18775
|
-
const inputNumberRegex = /(?<=\[\[\u2603 )(input-number \d+)(?=\]\])/g;
|
|
18776
|
-
|
|
18777
|
-
// Get all the content strings within the json, which might be nested within widgets
|
|
18778
|
-
const allContentStrings = json.content;
|
|
18779
|
-
|
|
18780
|
-
// Loop through the content strings to get all the input-number widgets
|
|
18781
|
-
const renameMap = {};
|
|
18782
|
-
const inputNumberMatches = [...(allContentStrings.match(inputNumberRegex) || [])];
|
|
18783
|
-
|
|
18784
|
-
// We want to count any pre-existing numeric-input widgets
|
|
18785
|
-
// so that we can start the new ids at the next number
|
|
18786
|
-
const numericMatches = [...(allContentStrings.match(numericRegex) || [])];
|
|
18787
|
-
let currentNumericCount = numericMatches.reduce((count, match) => {
|
|
18788
|
-
const id = parseInt(match.split(" ")[1], 10);
|
|
18789
|
-
return id >= count ? id + 1 : count; // We want to start at the next highest number
|
|
18790
|
-
}, 1);
|
|
18791
|
-
|
|
18792
|
-
// Now that we have all the necessary information, we can create the renameMap
|
|
18793
|
-
for (const match of inputNumberMatches) {
|
|
18794
|
-
const oldKey = match;
|
|
18795
|
-
const newKey = `numeric-input ${currentNumericCount}`;
|
|
18796
|
-
renameMap[oldKey] = newKey;
|
|
18797
|
-
currentNumericCount++;
|
|
18798
|
-
}
|
|
18799
|
-
return renameMap;
|
|
18800
|
-
};
|
|
18801
|
-
|
|
18802
|
-
// Convert the user input data keys from input-number to numeric-input
|
|
18803
|
-
const convertUserInputNumberData = (userInputMap, renameMap) => {
|
|
18804
|
-
const updatedUserInputMap = _extends({}, userInputMap);
|
|
18805
|
-
for (const key of Object.keys(userInputMap)) {
|
|
18806
|
-
if (key.includes("input-number")) {
|
|
18807
|
-
const updatedKey = renameMap[key];
|
|
18808
|
-
updatedUserInputMap[updatedKey] = userInputMap[key];
|
|
18809
|
-
delete updatedUserInputMap[key];
|
|
18810
|
-
}
|
|
18811
|
-
}
|
|
18812
|
-
return updatedUserInputMap;
|
|
18813
|
-
};
|
|
18814
|
-
|
|
18815
|
-
// Used to convert InputNumber widgets to NumericInput widgets for scoring
|
|
18816
|
-
const convertInputNumberForScoring = (rubric, userInputMap) => {
|
|
18817
|
-
// First we need to create a map of the old input-number keys to the new numeric-input keys
|
|
18818
|
-
// so that we can ensure we update the content, widgets, AND userInput accordingly
|
|
18819
|
-
const renameMap = getInputNumberRenameMap(rubric);
|
|
18820
|
-
const convertedRubric = convertInputNumberJson(rubric, renameMap);
|
|
18821
|
-
const convertedUserData = convertUserInputNumberData(userInputMap, renameMap);
|
|
18822
|
-
return {
|
|
18823
|
-
convertedRubric,
|
|
18824
|
-
convertedUserData
|
|
18825
|
-
};
|
|
18826
|
-
};
|
|
18827
|
-
|
|
18828
|
-
const widgetRegExes = [/input-number \d+/]; // We can add more regexes here in the future
|
|
18829
|
-
|
|
18830
|
-
// This utils in this file are used to modernize our Perseus JSON structure,
|
|
18831
|
-
// so that we can convert deprecated widgets to their modern equivalents when
|
|
18832
|
-
// content creators use the Editor Page to update content containing these widgets.
|
|
18833
|
-
// Currently, we're only converting input-number to numeric-input,
|
|
18834
|
-
// but we can add more conversions here in the future.
|
|
18835
|
-
|
|
18836
|
-
// Modernize the json content of a PerseusRenderer object
|
|
18837
|
-
// by converting deprecated widgets to their modern equivalents
|
|
18838
|
-
const convertDeprecatedWidgets = json => {
|
|
18839
|
-
// Currently we're only converting input-number to numeric-input,
|
|
18840
|
-
// But we can add more conversions here in the future
|
|
18841
|
-
return inputNumberToNumericInput(json);
|
|
18842
|
-
};
|
|
18843
|
-
const conversionRequired = json => {
|
|
18844
|
-
// If there's no content, then there's no conversion required
|
|
18845
|
-
if (!json.content) {
|
|
18846
|
-
return false;
|
|
18847
|
-
}
|
|
18848
|
-
// Check the content string for any top-level input-number widgets
|
|
18849
|
-
if (widgetRegExes.some(regex => regex.test(json.content))) {
|
|
18850
|
-
return true;
|
|
18851
|
-
}
|
|
18852
|
-
|
|
18853
|
-
// If there's no deprecated widget in the top-level, then check for any nested widgets
|
|
18854
|
-
for (const key of Object.keys(json.widgets)) {
|
|
18855
|
-
if (json.widgets[key].options.widgets) {
|
|
18856
|
-
const nestedJson = json.widgets[key].options;
|
|
18857
|
-
if (conversionRequired(nestedJson)) {
|
|
18858
|
-
return true;
|
|
18859
|
-
}
|
|
18860
|
-
}
|
|
18861
|
-
}
|
|
18862
|
-
return false;
|
|
18863
|
-
};
|
|
18864
|
-
|
|
18865
|
-
/**
|
|
18866
|
-
* Convert the deprecated widgets in the rubric and user data to their
|
|
18867
|
-
* modern equivalents for scoring. These need to be updated together
|
|
18868
|
-
* in order to ensure that the widgetKeys match between the rubric and user data.
|
|
18869
|
-
*/
|
|
18870
|
-
const convertDeprecatedWidgetsForScoring = (rubric, userInputMap) => {
|
|
18871
|
-
return convertInputNumberForScoring(rubric, userInputMap);
|
|
18872
|
-
};
|
|
18873
|
-
|
|
18874
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
18875
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
18876
|
-
|
|
18877
|
-
/**
|
|
18878
|
-
* Regex for widget placeholders in a string.
|
|
18879
|
-
*
|
|
18880
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
18881
|
-
* Second capture group is the widget type (ex. "radio)
|
|
18882
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
18883
|
-
*/
|
|
18884
|
-
function getWidgetRegex() {
|
|
18885
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
18886
|
-
}
|
|
18887
|
-
|
|
18888
|
-
/**
|
|
18889
|
-
* Add a widget placeholder using the widget ID.
|
|
18890
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
18891
|
-
*
|
|
18892
|
-
* @param {string} id
|
|
18893
|
-
* @returns {string}
|
|
18894
|
-
*/
|
|
18895
|
-
function addWidget(id) {
|
|
18896
|
-
return `[[☃ ${id}]]`;
|
|
18897
|
-
}
|
|
18898
|
-
|
|
18899
|
-
/**
|
|
18900
|
-
* Get a widget type by a widget's ID
|
|
18901
|
-
* (please don't derive type from ID)
|
|
18902
|
-
*
|
|
18903
|
-
* @param {string} widgetId the ID of the widget
|
|
18904
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18905
|
-
* @returns {string} the widget type (ie "radio")
|
|
18906
|
-
*/
|
|
18907
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
18908
|
-
var _widget$type;
|
|
18909
|
-
const widget = widgetMap[WidgetId];
|
|
18910
|
-
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
18911
|
-
}
|
|
18912
|
-
|
|
18913
|
-
/**
|
|
18914
|
-
* Does the content have a specific type of widget?
|
|
18915
|
-
*
|
|
18916
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
18917
|
-
* @param {string} content the string to search through
|
|
18918
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18919
|
-
* @returns {boolean} if the content includes the widget type
|
|
18920
|
-
*/
|
|
18921
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
18922
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
18923
|
-
}
|
|
18924
|
-
|
|
18925
|
-
/**
|
|
18926
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
18927
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
18928
|
-
*
|
|
18929
|
-
* Content should contain Perseus widget placeholders,
|
|
18930
|
-
* which look like: '[[☃ radio 1]]'.
|
|
18931
|
-
*
|
|
18932
|
-
* @param {string} content
|
|
18933
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
18934
|
-
*/
|
|
18935
|
-
function getWidgetIdsFromContent(content) {
|
|
18936
|
-
const widgets = [];
|
|
18937
|
-
const localWidgetRegex = getWidgetRegex();
|
|
18938
|
-
let match = localWidgetRegex.exec(content);
|
|
18939
|
-
while (match !== null) {
|
|
18940
|
-
widgets.push(match[1]);
|
|
18941
|
-
match = localWidgetRegex.exec(content);
|
|
18942
|
-
}
|
|
18943
|
-
return widgets;
|
|
18944
|
-
}
|
|
18945
|
-
|
|
18946
|
-
/**
|
|
18947
|
-
* Get a list of widget IDs from content,
|
|
18948
|
-
* but only for specific widget types
|
|
18949
|
-
*
|
|
18950
|
-
* @param {string} type the type of widget (ie "radio")
|
|
18951
|
-
* @param {string} content the string to parse
|
|
18952
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18953
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
18954
|
-
*/
|
|
18955
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
18956
|
-
const rv = [];
|
|
18957
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
18958
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
18959
|
-
const widget = widgetMap[widgetId];
|
|
18960
|
-
if ((widget == null ? void 0 : widget.type) === type) {
|
|
18961
|
-
rv.push(widgetId);
|
|
18962
|
-
}
|
|
18963
|
-
});
|
|
18964
|
-
return rv;
|
|
18965
|
-
}
|
|
18966
|
-
|
|
18967
|
-
/**
|
|
18968
|
-
* Pull the widget map out of ItemData
|
|
18969
|
-
*
|
|
18970
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
18971
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
18972
|
-
*/
|
|
18973
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
18974
|
-
return itemData.question.widgets;
|
|
18975
|
-
}
|
|
18976
|
-
|
|
18977
|
-
/**
|
|
18978
|
-
* Get the widget information from a WidgetMap
|
|
18979
|
-
*
|
|
18980
|
-
* @param {string} widgetId the ID of the widget
|
|
18981
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
18982
|
-
*/
|
|
18983
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
18984
|
-
var _widgetMap$widgetId;
|
|
18985
|
-
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
18986
|
-
}
|
|
18987
|
-
|
|
18988
|
-
/**
|
|
18989
|
-
* Get select widgets from a widget map.
|
|
18990
|
-
* Useful for multi-items that needs to split
|
|
18991
|
-
* one PerseusItem into several
|
|
18992
|
-
*
|
|
18993
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
18994
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
18995
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
18996
|
-
*/
|
|
18997
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
18998
|
-
const widgets = {};
|
|
18999
|
-
widgetIds.forEach(widgetId => {
|
|
19000
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
19001
|
-
if (widget) {
|
|
19002
|
-
widgets[widgetId] = widget;
|
|
19003
|
-
}
|
|
19004
|
-
});
|
|
19005
|
-
return widgets;
|
|
19006
|
-
}
|
|
19007
|
-
|
|
19008
18721
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
19009
18722
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
19010
18723
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -19055,9 +18768,6 @@ const replaceDeprecatedWidgets = () => {
|
|
|
19055
18768
|
replaceWidget("sequence", "deprecated-standin");
|
|
19056
18769
|
replaceWidget("simulator", "deprecated-standin");
|
|
19057
18770
|
replaceWidget("unit-input", "deprecated-standin");
|
|
19058
|
-
|
|
19059
|
-
// Input-Number is a special case as it is being replaced by Numeric-Input
|
|
19060
|
-
replaceWidget("input-number", "numeric-input");
|
|
19061
18771
|
};
|
|
19062
18772
|
const registerEditors = editorsToRegister => {
|
|
19063
18773
|
editorsToRegister.forEach(editor => {
|
|
@@ -19092,11 +18802,6 @@ const replaceDeprecatedEditors = () => {
|
|
|
19092
18802
|
replaceEditor("sequence", "deprecated-standin");
|
|
19093
18803
|
replaceEditor("simulator", "deprecated-standin");
|
|
19094
18804
|
replaceEditor("unit-input", "deprecated-standin");
|
|
19095
|
-
|
|
19096
|
-
// We're replacing the input-number editor with the numeric-input editor here,
|
|
19097
|
-
// but we're also modifying the JSON content to convert input-number into the
|
|
19098
|
-
// numeric-input format over in editor-page.tsx on line 92.
|
|
19099
|
-
replaceEditor("input-number", "numeric-input");
|
|
19100
18805
|
};
|
|
19101
18806
|
const getWidget = name => {
|
|
19102
18807
|
var _widgets$name;
|
|
@@ -19147,8 +18852,10 @@ const getVersionVector = () => {
|
|
|
19147
18852
|
const getPublicWidgets = () => {
|
|
19148
18853
|
// TODO(alex): Update underscore.js so that _.pick can take a function.
|
|
19149
18854
|
// @ts-expect-error - TS2740 - Type 'Pick<{ [key: string]: Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>; }, string>' is missing the following properties from type 'readonly Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>[]': length, concat, join, slice, and 18 more.
|
|
19150
|
-
return _.pick(widgets,
|
|
19151
|
-
|
|
18855
|
+
return _.pick(widgets,
|
|
18856
|
+
// @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
|
|
18857
|
+
_.reject(_.keys(widgets), function (name) {
|
|
18858
|
+
return widgets[name].hidden;
|
|
19152
18859
|
}));
|
|
19153
18860
|
};
|
|
19154
18861
|
const isAccessible = widgetInfo => {
|
|
@@ -19466,34 +19173,6 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19466
19173
|
}
|
|
19467
19174
|
});
|
|
19468
19175
|
}
|
|
19469
|
-
|
|
19470
|
-
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
19471
|
-
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
19472
|
-
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
19473
|
-
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
19474
|
-
// need to be removed before we move this to the server
|
|
19475
|
-
strings, locale) {
|
|
19476
|
-
let convertedRenderData = perseusRenderData;
|
|
19477
|
-
let convertedUserInputMap = userInputMap;
|
|
19478
|
-
|
|
19479
|
-
// Check if the PerseusRenderer object contains any deprecated widgets that need to be converted
|
|
19480
|
-
const mustConvertData = conversionRequired(perseusRenderData);
|
|
19481
|
-
if (mustConvertData) {
|
|
19482
|
-
const {
|
|
19483
|
-
convertedRubric,
|
|
19484
|
-
convertedUserData
|
|
19485
|
-
} = convertDeprecatedWidgetsForScoring(perseusRenderData, userInputMap);
|
|
19486
|
-
convertedRenderData = convertedRubric;
|
|
19487
|
-
convertedUserInputMap = convertedUserData;
|
|
19488
|
-
}
|
|
19489
|
-
|
|
19490
|
-
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
19491
|
-
// widget data for widgets that are not in PerseusRenderer.content,
|
|
19492
|
-
// so this checks that the widgets are being used before scoring them
|
|
19493
|
-
const usedWidgetIds = getWidgetIdsFromContent(convertedRenderData.content);
|
|
19494
|
-
const scores = scoreWidgetsFunctional(convertedRenderData.widgets, usedWidgetIds, convertedUserInputMap, strings, locale);
|
|
19495
|
-
return Util.flattenScores(scores);
|
|
19496
|
-
}
|
|
19497
19176
|
function scoreWidgetsFunctional(widgets,
|
|
19498
19177
|
// This is a port of old code, I'm not sure why
|
|
19499
19178
|
// we need widgetIds vs the keys of the widgets object
|
|
@@ -19514,7 +19193,7 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19514
19193
|
const userInput = userInputMap[id];
|
|
19515
19194
|
const scorer = getWidgetScorer(widget.type);
|
|
19516
19195
|
if (widget.type === "group") {
|
|
19517
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets,
|
|
19196
|
+
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19518
19197
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19519
19198
|
} else if (scorer) {
|
|
19520
19199
|
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
@@ -20346,7 +20025,7 @@ class Renderer extends React.Component {
|
|
|
20346
20025
|
// Instead, we center them with css magic in styles.less
|
|
20347
20026
|
// /cry(aria)
|
|
20348
20027
|
this._foundTextNodes = true;
|
|
20349
|
-
if (this.widgetIds
|
|
20028
|
+
if (_.contains(this.widgetIds, node.id)) {
|
|
20350
20029
|
// We don't want to render a duplicate widget key/ref,
|
|
20351
20030
|
// as this causes problems with react (for obvious
|
|
20352
20031
|
// reasons). Instead we just notify the
|
|
@@ -20650,7 +20329,7 @@ class Renderer extends React.Component {
|
|
|
20650
20329
|
};
|
|
20651
20330
|
this.getInputPaths = () => {
|
|
20652
20331
|
const inputPaths = [];
|
|
20653
|
-
this.widgetIds
|
|
20332
|
+
_.each(this.widgetIds, widgetId => {
|
|
20654
20333
|
const widget = this.getWidgetInstance(widgetId);
|
|
20655
20334
|
if (widget && widget.getInputPaths) {
|
|
20656
20335
|
// Grab all input paths and add widgetID to the front
|
|
@@ -20658,7 +20337,7 @@ class Renderer extends React.Component {
|
|
|
20658
20337
|
// Prefix paths with their widgetID and add to collective
|
|
20659
20338
|
// list of paths.
|
|
20660
20339
|
// @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
|
|
20661
|
-
|
|
20340
|
+
_.each(widgetInputPaths, inputPath => {
|
|
20662
20341
|
const relativeInputPath = [widgetId].concat(inputPath);
|
|
20663
20342
|
inputPaths.push(relativeInputPath);
|
|
20664
20343
|
});
|
|
@@ -20787,20 +20466,12 @@ class Renderer extends React.Component {
|
|
|
20787
20466
|
this.getWidgetIds = () => {
|
|
20788
20467
|
return this.widgetIds;
|
|
20789
20468
|
};
|
|
20790
|
-
/**
|
|
20791
|
-
* @deprecated use scorePerseusItem
|
|
20792
|
-
*/
|
|
20793
|
-
this.guessAndScore = () => {
|
|
20794
|
-
const totalGuess = this.getUserInput();
|
|
20795
|
-
const totalScore = this.score();
|
|
20796
|
-
return [totalGuess, totalScore];
|
|
20797
|
-
};
|
|
20798
20469
|
this.examples = () => {
|
|
20799
20470
|
const widgetIds = this.widgetIds;
|
|
20800
|
-
const examples =
|
|
20471
|
+
const examples = _.compact(_.map(widgetIds, widgetId => {
|
|
20801
20472
|
const widget = this.getWidgetInstance(widgetId);
|
|
20802
20473
|
return widget != null && widget.examples ? widget.examples() : null;
|
|
20803
|
-
})
|
|
20474
|
+
}));
|
|
20804
20475
|
|
|
20805
20476
|
// no widgets with examples
|
|
20806
20477
|
if (!examples.length) {
|
|
@@ -21055,15 +20726,27 @@ class Renderer extends React.Component {
|
|
|
21055
20726
|
}
|
|
21056
20727
|
|
|
21057
20728
|
/**
|
|
21058
|
-
*
|
|
21059
|
-
*
|
|
21060
|
-
*
|
|
20729
|
+
* Returns an object mapping from widget ID to perseus-style score.
|
|
20730
|
+
* The keys of this object are the values of the array returned
|
|
20731
|
+
* from `getWidgetIds`.
|
|
20732
|
+
*/
|
|
20733
|
+
scoreWidgets() {
|
|
20734
|
+
return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20735
|
+
}
|
|
20736
|
+
|
|
20737
|
+
/**
|
|
20738
|
+
* Grades the content.
|
|
21061
20739
|
*/
|
|
21062
20740
|
score() {
|
|
21063
|
-
const scores =
|
|
20741
|
+
const scores = this.scoreWidgets();
|
|
21064
20742
|
const combinedScore = Util.flattenScores(scores);
|
|
21065
20743
|
return combinedScore;
|
|
21066
20744
|
}
|
|
20745
|
+
guessAndScore() {
|
|
20746
|
+
const totalGuess = this.getUserInput();
|
|
20747
|
+
const totalScore = this.score();
|
|
20748
|
+
return [totalGuess, totalScore];
|
|
20749
|
+
}
|
|
21067
20750
|
render() {
|
|
21068
20751
|
const apiOptions = this.getApiOptions();
|
|
21069
20752
|
if (this.reuseMarkdown) {
|
|
@@ -22073,11 +21756,15 @@ class InputNumber extends React.Component {
|
|
|
22073
21756
|
this.refs.input.focus();
|
|
22074
21757
|
return true;
|
|
22075
21758
|
};
|
|
21759
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21760
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22076
21761
|
this.focusInputPath = inputPath => {
|
|
22077
21762
|
// eslint-disable-next-line react/no-string-refs
|
|
22078
21763
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
22079
21764
|
this.refs.input.focus();
|
|
22080
21765
|
};
|
|
21766
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21767
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22081
21768
|
this.blurInputPath = inputPath => {
|
|
22082
21769
|
var _this$refs$input;
|
|
22083
21770
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -22095,6 +21782,8 @@ class InputNumber extends React.Component {
|
|
|
22095
21782
|
/* c8 ignore next */
|
|
22096
21783
|
return [[]];
|
|
22097
21784
|
};
|
|
21785
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21786
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22098
21787
|
this.setInputValue = (path, newValue, cb) => {
|
|
22099
21788
|
this.props.onChange({
|
|
22100
21789
|
currentValue: newValue
|
|
@@ -22222,6 +21911,8 @@ var InputNumber$1 = {
|
|
|
22222
21911
|
widget: InputNumber,
|
|
22223
21912
|
transform: propTransform$3,
|
|
22224
21913
|
isLintable: true,
|
|
21914
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21915
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInputNumberUserInput'.
|
|
22225
21916
|
scorer: scoreInputNumber,
|
|
22226
21917
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22227
21918
|
if (rubric.value == null) {
|
|
@@ -22512,7 +22203,7 @@ class NumericInput extends React.Component {
|
|
|
22512
22203
|
height: "auto"
|
|
22513
22204
|
}
|
|
22514
22205
|
});
|
|
22515
|
-
return /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
22206
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
22516
22207
|
ref: ref => this.inputRef = ref,
|
|
22517
22208
|
value: this.props.currentValue,
|
|
22518
22209
|
onChange: this.handleChange,
|
|
@@ -22524,7 +22215,7 @@ class NumericInput extends React.Component {
|
|
|
22524
22215
|
id: this.props.widgetId,
|
|
22525
22216
|
disabled: this.props.apiOptions.readOnly,
|
|
22526
22217
|
style: styles.input
|
|
22527
|
-
});
|
|
22218
|
+
}));
|
|
22528
22219
|
}
|
|
22529
22220
|
}
|
|
22530
22221
|
|
|
@@ -22593,69 +22284,19 @@ const propsTransform = function propsTransform(widgetOptions) {
|
|
|
22593
22284
|
});
|
|
22594
22285
|
return rendererProps;
|
|
22595
22286
|
};
|
|
22596
|
-
|
|
22597
|
-
// This function is being used to replace the input-number widget
|
|
22598
|
-
// with the numeric-input widget
|
|
22599
|
-
const propUpgrades$2 = {
|
|
22600
|
-
/* c8 ignore next */
|
|
22601
|
-
"1": initialProps => {
|
|
22602
|
-
// If the initialProps has simplify, it means we're upgrading from
|
|
22603
|
-
// input-number to numeric-input. In this case, we need to upgrade
|
|
22604
|
-
// the widget options accordingly.
|
|
22605
|
-
if (initialProps.simplify !== undefined) {
|
|
22606
|
-
// If the answerType is not number or percent, we need to provide
|
|
22607
|
-
// the answer form for the numeric-input widget
|
|
22608
|
-
const provideAnswerForm = initialProps.answerType !== "number" && initialProps.answerType !== "percent";
|
|
22609
|
-
|
|
22610
|
-
// We need to determine the mathFormat for the numeric-input widget
|
|
22611
|
-
const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
22612
|
-
: initialProps.answerType; // Otherwise, we can use the answerType directly
|
|
22613
|
-
|
|
22614
|
-
// If adjusting this logic, also adjust the logic in the convertInputNumberWidgetOptions
|
|
22615
|
-
// function in input-number.ts in the Perseus Editor package's util folder
|
|
22616
|
-
const answers = [{
|
|
22617
|
-
value: initialProps.value,
|
|
22618
|
-
simplify: initialProps.simplify,
|
|
22619
|
-
answerForms: provideAnswerForm ? [mathFormat] : undefined,
|
|
22620
|
-
strict: initialProps.inexact,
|
|
22621
|
-
// We only want to set maxError if the inexact prop is true
|
|
22622
|
-
maxError: initialProps.inexact ? initialProps.maxError : 0,
|
|
22623
|
-
status: "correct",
|
|
22624
|
-
// Input-number only allows correct answers
|
|
22625
|
-
message: ""
|
|
22626
|
-
}];
|
|
22627
|
-
return {
|
|
22628
|
-
answers,
|
|
22629
|
-
size: initialProps.size,
|
|
22630
|
-
coefficient: false,
|
|
22631
|
-
// input-number doesn't have a coefficient prop
|
|
22632
|
-
labelText: "",
|
|
22633
|
-
// input-number doesn't have a labelText prop
|
|
22634
|
-
static: false,
|
|
22635
|
-
// static is always false for numeric-input
|
|
22636
|
-
rightAlign: initialProps.rightAlign || false
|
|
22637
|
-
};
|
|
22638
|
-
} else {
|
|
22639
|
-
// Otherwise simply return the initialProps as there's no differences
|
|
22640
|
-
// between v0 and v1 for numeric-input
|
|
22641
|
-
return initialProps;
|
|
22642
|
-
}
|
|
22643
|
-
}
|
|
22644
|
-
};
|
|
22645
22287
|
var NumericInput$1 = {
|
|
22646
22288
|
name: "numeric-input",
|
|
22647
22289
|
displayName: "Numeric input",
|
|
22648
22290
|
defaultAlignment: "inline-block",
|
|
22649
22291
|
accessible: true,
|
|
22650
22292
|
widget: NumericInput,
|
|
22651
|
-
version: {
|
|
22652
|
-
major: 1,
|
|
22653
|
-
minor: 0
|
|
22654
|
-
},
|
|
22655
22293
|
transform: propsTransform,
|
|
22656
|
-
propUpgrades: propUpgrades$2,
|
|
22657
22294
|
isLintable: true,
|
|
22295
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22296
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumericInputUserInput'.
|
|
22658
22297
|
scorer: scoreNumericInput,
|
|
22298
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22299
|
+
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
|
|
22659
22300
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22660
22301
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22661
22302
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -24166,12 +23807,12 @@ const AltText = ({
|
|
|
24166
23807
|
}));
|
|
24167
23808
|
};
|
|
24168
23809
|
const rules = {
|
|
24169
|
-
newline: SimpleMarkdown
|
|
24170
|
-
paragraph: SimpleMarkdown
|
|
24171
|
-
escape: SimpleMarkdown
|
|
23810
|
+
newline: SimpleMarkdown.defaultRules.newline,
|
|
23811
|
+
paragraph: SimpleMarkdown.defaultRules.paragraph,
|
|
23812
|
+
escape: SimpleMarkdown.defaultRules.escape,
|
|
24172
23813
|
passageFootnote: {
|
|
24173
|
-
order: SimpleMarkdown
|
|
24174
|
-
match: SimpleMarkdown
|
|
23814
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.1,
|
|
23815
|
+
match: SimpleMarkdown.inlineRegex(/^\^/),
|
|
24175
23816
|
parse: (capture, parse, state) => {
|
|
24176
23817
|
// if no footnotes have been seen, we're id 1. otherwise,
|
|
24177
23818
|
// we're the next subsequent id
|
|
@@ -24200,7 +23841,7 @@ const rules = {
|
|
|
24200
23841
|
}
|
|
24201
23842
|
},
|
|
24202
23843
|
refStart: {
|
|
24203
|
-
order: SimpleMarkdown
|
|
23844
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.2,
|
|
24204
23845
|
match: function (source) {
|
|
24205
23846
|
const capture = /^\{\{/.exec(source);
|
|
24206
23847
|
if (capture) {
|
|
@@ -24286,8 +23927,8 @@ const rules = {
|
|
|
24286
23927
|
}
|
|
24287
23928
|
},
|
|
24288
23929
|
refEnd: {
|
|
24289
|
-
order: SimpleMarkdown
|
|
24290
|
-
match: SimpleMarkdown
|
|
23930
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.3,
|
|
23931
|
+
match: SimpleMarkdown.inlineRegex(/^\}\}/),
|
|
24291
23932
|
parse: (capture, parse, state) => {
|
|
24292
23933
|
if (!state.useRefs) {
|
|
24293
23934
|
return {
|
|
@@ -24315,8 +23956,8 @@ const rules = {
|
|
|
24315
23956
|
}
|
|
24316
23957
|
},
|
|
24317
23958
|
squareLabel: {
|
|
24318
|
-
order: SimpleMarkdown
|
|
24319
|
-
match: SimpleMarkdown
|
|
23959
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.4,
|
|
23960
|
+
match: SimpleMarkdown.inlineRegex(/^\[\[(\w+)\]\]( *)/),
|
|
24320
23961
|
parse: (capture, parse, state) => {
|
|
24321
23962
|
if (!state.firstQuestionRef) {
|
|
24322
23963
|
state.firstQuestionRef = capture[1];
|
|
@@ -24342,8 +23983,8 @@ const rules = {
|
|
|
24342
23983
|
}
|
|
24343
23984
|
},
|
|
24344
23985
|
circleLabel: {
|
|
24345
|
-
order: SimpleMarkdown
|
|
24346
|
-
match: SimpleMarkdown
|
|
23986
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.5,
|
|
23987
|
+
match: SimpleMarkdown.inlineRegex(/^\(\((\w+)\)\)( *)/),
|
|
24347
23988
|
parse: (capture, parse, state) => {
|
|
24348
23989
|
return {
|
|
24349
23990
|
content: capture[1],
|
|
@@ -24367,8 +24008,8 @@ const rules = {
|
|
|
24367
24008
|
}
|
|
24368
24009
|
},
|
|
24369
24010
|
squareBracketRef: {
|
|
24370
|
-
order: SimpleMarkdown
|
|
24371
|
-
match: SimpleMarkdown
|
|
24011
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.6,
|
|
24012
|
+
match: SimpleMarkdown.inlineRegex(/^\[(\d+)\]( *)/),
|
|
24372
24013
|
parse: (capture, parse, state) => {
|
|
24373
24014
|
if (!state.firstSentenceRef) {
|
|
24374
24015
|
state.firstSentenceRef = capture[1];
|
|
@@ -24391,8 +24032,8 @@ const rules = {
|
|
|
24391
24032
|
}
|
|
24392
24033
|
},
|
|
24393
24034
|
highlight: {
|
|
24394
|
-
order: SimpleMarkdown
|
|
24395
|
-
match: SimpleMarkdown
|
|
24035
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
24036
|
+
match: SimpleMarkdown.inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
|
|
24396
24037
|
parse: (capture, parse, state) => {
|
|
24397
24038
|
return {
|
|
24398
24039
|
content: capture[1]
|
|
@@ -24406,8 +24047,8 @@ const rules = {
|
|
|
24406
24047
|
}
|
|
24407
24048
|
},
|
|
24408
24049
|
reviewHighlight: {
|
|
24409
|
-
order: SimpleMarkdown
|
|
24410
|
-
match: SimpleMarkdown
|
|
24050
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
24051
|
+
match: SimpleMarkdown.inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
|
|
24411
24052
|
parse: (capture, parse, state) => {
|
|
24412
24053
|
return {
|
|
24413
24054
|
content: capture[1]
|
|
@@ -24420,11 +24061,11 @@ const rules = {
|
|
|
24420
24061
|
}, node.content)];
|
|
24421
24062
|
}
|
|
24422
24063
|
},
|
|
24423
|
-
strong: SimpleMarkdown
|
|
24424
|
-
u: SimpleMarkdown
|
|
24425
|
-
em: SimpleMarkdown
|
|
24426
|
-
del: SimpleMarkdown
|
|
24427
|
-
text: _extends({}, SimpleMarkdown
|
|
24064
|
+
strong: SimpleMarkdown.defaultRules.strong,
|
|
24065
|
+
u: SimpleMarkdown.defaultRules.u,
|
|
24066
|
+
em: SimpleMarkdown.defaultRules.em,
|
|
24067
|
+
del: SimpleMarkdown.defaultRules.del,
|
|
24068
|
+
text: _extends({}, SimpleMarkdown.defaultRules.text, {
|
|
24428
24069
|
react(node, output, state) {
|
|
24429
24070
|
return /*#__PURE__*/React.createElement("span", {
|
|
24430
24071
|
key: state.key
|
|
@@ -24465,7 +24106,10 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
24465
24106
|
borderRadius: "50%",
|
|
24466
24107
|
textAlign: "center"
|
|
24467
24108
|
};
|
|
24468
|
-
|
|
24109
|
+
|
|
24110
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
24111
|
+
// @ts-expect-error: State is not assignable to ParseState
|
|
24112
|
+
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
24469
24113
|
const parse$2 = (source, state) => {
|
|
24470
24114
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
24471
24115
|
state = state || {};
|
|
@@ -24474,7 +24118,7 @@ const parse$2 = (source, state) => {
|
|
|
24474
24118
|
};
|
|
24475
24119
|
|
|
24476
24120
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
24477
|
-
const output = SimpleMarkdown
|
|
24121
|
+
const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
|
|
24478
24122
|
var PassageMarkdown = {
|
|
24479
24123
|
parse: parse$2,
|
|
24480
24124
|
output,
|
|
@@ -26897,6 +26541,8 @@ var Radio = {
|
|
|
26897
26541
|
},
|
|
26898
26542
|
propUpgrades: propUpgrades$1,
|
|
26899
26543
|
isLintable: true,
|
|
26544
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
26545
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
26900
26546
|
scorer: scoreRadio
|
|
26901
26547
|
};
|
|
26902
26548
|
|
|
@@ -27117,6 +26763,8 @@ var Categorizer$1 = {
|
|
|
27117
26763
|
return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
27118
26764
|
},
|
|
27119
26765
|
isLintable: true,
|
|
26766
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
26767
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
27120
26768
|
scorer: scoreCategorizer
|
|
27121
26769
|
};
|
|
27122
26770
|
|
|
@@ -27350,6 +26998,8 @@ var CSProgram$1 = {
|
|
|
27350
26998
|
supportedAlignments: ["block", "full-width"],
|
|
27351
26999
|
widget: CSProgram,
|
|
27352
27000
|
hidden: true,
|
|
27001
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27002
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
27353
27003
|
scorer: scoreCSProgram
|
|
27354
27004
|
};
|
|
27355
27005
|
|
|
@@ -27567,6 +27217,8 @@ var Dropdown$1 = {
|
|
|
27567
27217
|
accessible: true,
|
|
27568
27218
|
widget: Dropdown,
|
|
27569
27219
|
transform: optionsTransform,
|
|
27220
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27221
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
27570
27222
|
scorer: scoreDropdown
|
|
27571
27223
|
};
|
|
27572
27224
|
|
|
@@ -29129,6 +28781,8 @@ class FunctionGrapher extends React.Component {
|
|
|
29129
28781
|
return this.props.asymptote;
|
|
29130
28782
|
};
|
|
29131
28783
|
this.change = (...args) => {
|
|
28784
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
28785
|
+
// @ts-expect-error: Argument of type 'any[]' is not assignable to parameter of type '[newPropsOrSinglePropName: string | { [key: string]: any; }, propValue?: any, callback?: (() => unknown) | undefined]'. Target requires 1 element(s) but source may have fewer.
|
|
29132
28786
|
return change.apply(this, args);
|
|
29133
28787
|
};
|
|
29134
28788
|
this.renderPlot = () => {
|
|
@@ -29492,6 +29146,8 @@ var Grapher$1 = {
|
|
|
29492
29146
|
widget: Grapher,
|
|
29493
29147
|
transform: propTransform$2,
|
|
29494
29148
|
staticTransform: staticTransform$4,
|
|
29149
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29150
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGrapherUserInput'.
|
|
29495
29151
|
scorer: scoreGrapher
|
|
29496
29152
|
};
|
|
29497
29153
|
|
|
@@ -29613,9 +29269,6 @@ class Group extends React.Component {
|
|
|
29613
29269
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
29614
29270
|
}
|
|
29615
29271
|
};
|
|
29616
|
-
|
|
29617
|
-
// TODO(LEMS-2391): replace this when there's a separate check
|
|
29618
|
-
// for valid/invalid state
|
|
29619
29272
|
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
29620
29273
|
const isValid = score && score.type !== "invalid";
|
|
29621
29274
|
const isInvalid = score && score.type === "invalid";
|
|
@@ -29823,6 +29476,8 @@ var Iframe$1 = {
|
|
|
29823
29476
|
widget: Iframe,
|
|
29824
29477
|
// Let's not expose it to all content creators yet
|
|
29825
29478
|
hidden: true,
|
|
29479
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29480
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusIframeUserInput'.
|
|
29826
29481
|
scorer: scoreIframe
|
|
29827
29482
|
};
|
|
29828
29483
|
|
|
@@ -31482,7 +31137,7 @@ function isUnlimitedGraphState(state) {
|
|
|
31482
31137
|
function replaceOutsideTeX(mathString) {
|
|
31483
31138
|
// All the information we need is in the first section,
|
|
31484
31139
|
// whether it's typed as "blockmath" or "paragraph"
|
|
31485
|
-
const firstSection =
|
|
31140
|
+
const firstSection = parse$4(mathString)[0];
|
|
31486
31141
|
|
|
31487
31142
|
// If it's blockMath, the outer level has the full math content.
|
|
31488
31143
|
if (firstSection.type === "blockMath") {
|
|
@@ -31535,6 +31190,37 @@ function escapeSpecialChars(str) {
|
|
|
31535
31190
|
return str.replace(/([$\\{}])/g, "\\$1");
|
|
31536
31191
|
}
|
|
31537
31192
|
|
|
31193
|
+
/**
|
|
31194
|
+
* Parse a string of text and math into a list of objects with type and content
|
|
31195
|
+
*
|
|
31196
|
+
* Example: "Pi is about $\frac{22}{7}$" ==>
|
|
31197
|
+
* [
|
|
31198
|
+
* {type: "text", content: "Pi is about "},
|
|
31199
|
+
* {type: "math", content: "\\frac{22}{7}"},
|
|
31200
|
+
* ]
|
|
31201
|
+
*/
|
|
31202
|
+
const mathOnlyParser = SimpleMarkdown.parserFor({
|
|
31203
|
+
math: _extends({}, pureMarkdownRules.math, {
|
|
31204
|
+
order: 0
|
|
31205
|
+
}),
|
|
31206
|
+
text: {
|
|
31207
|
+
order: 1,
|
|
31208
|
+
match: SimpleMarkdown.anyScopeRegex(/^([^$\\{}]+)/),
|
|
31209
|
+
parse: capture => ({
|
|
31210
|
+
content: capture[0]
|
|
31211
|
+
})
|
|
31212
|
+
},
|
|
31213
|
+
specialCharacter: {
|
|
31214
|
+
order: 2,
|
|
31215
|
+
match: SimpleMarkdown.anyScopeRegex(/^(\\[\S\s]|\$|\\$|{|})/),
|
|
31216
|
+
parse: capture => ({
|
|
31217
|
+
content: capture[0]
|
|
31218
|
+
})
|
|
31219
|
+
}
|
|
31220
|
+
}, {
|
|
31221
|
+
inline: true
|
|
31222
|
+
});
|
|
31223
|
+
|
|
31538
31224
|
function AxisLabels() {
|
|
31539
31225
|
const {
|
|
31540
31226
|
range,
|
|
@@ -32477,12 +32163,17 @@ const ItemExtras = [
|
|
|
32477
32163
|
// prettier-ignore
|
|
32478
32164
|
|
|
32479
32165
|
// prettier-ignore
|
|
32480
|
-
|
|
32166
|
+
|
|
32481
32167
|
// prettier-ignore
|
|
32168
|
+
|
|
32482
32169
|
// prettier-ignore
|
|
32170
|
+
|
|
32483
32171
|
// prettier-ignore
|
|
32172
|
+
|
|
32484
32173
|
//prettier-ignore
|
|
32174
|
+
|
|
32485
32175
|
// A background image applied to various widgets.
|
|
32176
|
+
|
|
32486
32177
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32487
32178
|
|
|
32488
32179
|
// 2D range: xMin, xMax, yMin, yMax
|
|
@@ -32992,7 +32683,9 @@ const actions = {
|
|
|
32992
32683
|
removePoint,
|
|
32993
32684
|
focusPoint,
|
|
32994
32685
|
blurPoint,
|
|
32995
|
-
clickPoint
|
|
32686
|
+
clickPoint,
|
|
32687
|
+
closePolygon,
|
|
32688
|
+
openPolygon
|
|
32996
32689
|
},
|
|
32997
32690
|
quadratic: {
|
|
32998
32691
|
movePoint
|
|
@@ -33071,6 +32764,18 @@ function changeKeyboardInvitationVisibility(shouldShow) {
|
|
|
33071
32764
|
shouldShow
|
|
33072
32765
|
};
|
|
33073
32766
|
}
|
|
32767
|
+
const CLOSE_POLYGON = "close-polygon";
|
|
32768
|
+
function closePolygon() {
|
|
32769
|
+
return {
|
|
32770
|
+
type: CLOSE_POLYGON
|
|
32771
|
+
};
|
|
32772
|
+
}
|
|
32773
|
+
const OPEN_POLYGON = "open-polygon";
|
|
32774
|
+
function openPolygon() {
|
|
32775
|
+
return {
|
|
32776
|
+
type: OPEN_POLYGON
|
|
32777
|
+
};
|
|
32778
|
+
}
|
|
33074
32779
|
const MOVE_ALL = "move-all";
|
|
33075
32780
|
function moveAll(delta) {
|
|
33076
32781
|
return {
|
|
@@ -34584,33 +34289,17 @@ const LimitedPolygonGraph = props => {
|
|
|
34584
34289
|
}
|
|
34585
34290
|
})));
|
|
34586
34291
|
};
|
|
34587
|
-
|
|
34588
|
-
// TODO(catjohnson): reduce redundancy between LimitedPolygonGraph and UnlimitedPolygonGraph
|
|
34589
|
-
// both components are vary similar, however more implementation is needed to be added before
|
|
34590
|
-
// it is clear what can and can't be shared between components.
|
|
34591
34292
|
const UnlimitedPolygonGraph = props => {
|
|
34592
|
-
const [hovered, setHovered] = React.useState(false);
|
|
34593
|
-
// This is more so required for the re-rendering that occurs when state
|
|
34594
|
-
// updates; specifically with regard to line weighting and polygon focus.
|
|
34595
|
-
const [focusVisible, setFocusVisible] = React.useState(false);
|
|
34596
34293
|
const {
|
|
34597
34294
|
dispatch
|
|
34598
34295
|
} = props;
|
|
34599
34296
|
const {
|
|
34600
34297
|
coords,
|
|
34601
|
-
|
|
34602
|
-
showSides,
|
|
34603
|
-
range,
|
|
34604
|
-
snapStep,
|
|
34605
|
-
snapTo = "grid"
|
|
34298
|
+
closedPolygon
|
|
34606
34299
|
} = props.graphState;
|
|
34607
34300
|
const graphConfig = useGraphConfig();
|
|
34608
|
-
|
|
34609
|
-
// TODO(catjohnson): Explore abstracting this code as it is similar to point.tsx
|
|
34610
|
-
// and hopefully we can cut down ont the unlimited graph redundancy.
|
|
34611
34301
|
const {
|
|
34612
34302
|
range: [x, y],
|
|
34613
|
-
disableKeyboardInteraction,
|
|
34614
34303
|
graphDimensionsInPixels
|
|
34615
34304
|
} = graphConfig;
|
|
34616
34305
|
const widthPx = graphDimensionsInPixels[0];
|
|
@@ -34621,21 +34310,6 @@ const UnlimitedPolygonGraph = props => {
|
|
|
34621
34310
|
// TODO(benchristel): can the default set of points be removed here? I don't
|
|
34622
34311
|
// think coords can be null.
|
|
34623
34312
|
const points = coords != null ? coords : [[0, 0]];
|
|
34624
|
-
const polygonRef = React.useRef(null);
|
|
34625
|
-
const dragReferencePoint = points[0];
|
|
34626
|
-
const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
|
|
34627
|
-
const {
|
|
34628
|
-
dragging
|
|
34629
|
-
} = useDraggable({
|
|
34630
|
-
gestureTarget: polygonRef,
|
|
34631
|
-
point: dragReferencePoint,
|
|
34632
|
-
onMove: newPoint => {
|
|
34633
|
-
const delta = vec.sub(newPoint, dragReferencePoint);
|
|
34634
|
-
dispatch(actions.polygon.moveAll(delta));
|
|
34635
|
-
},
|
|
34636
|
-
constrainKeyboardMovement: constrain
|
|
34637
|
-
});
|
|
34638
|
-
const lines = getLines(points);
|
|
34639
34313
|
React.useEffect(() => {
|
|
34640
34314
|
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34641
34315
|
if (focusedIndex != null) {
|
|
@@ -34643,94 +34317,59 @@ const UnlimitedPolygonGraph = props => {
|
|
|
34643
34317
|
(_pointRef$current$foc = pointRef.current[focusedIndex]) == null || _pointRef$current$foc.focus();
|
|
34644
34318
|
}
|
|
34645
34319
|
}, [props.graphState.focusedPointIndex, pointRef]);
|
|
34646
|
-
|
|
34647
|
-
|
|
34648
|
-
|
|
34649
|
-
cursor: "crosshair"
|
|
34650
|
-
},
|
|
34651
|
-
width: widthPx,
|
|
34652
|
-
height: heightPx,
|
|
34653
|
-
x: left,
|
|
34654
|
-
y: top,
|
|
34655
|
-
onClick: event => {
|
|
34656
|
-
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34657
|
-
const x = event.clientX - elementRect.x;
|
|
34658
|
-
const y = event.clientY - elementRect.y;
|
|
34659
|
-
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34660
|
-
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34661
|
-
}
|
|
34662
|
-
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
34663
|
-
points: [...points],
|
|
34664
|
-
color: "var(--movable-line-stroke-color)",
|
|
34665
|
-
svgPolygonProps: {
|
|
34666
|
-
strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
|
|
34667
|
-
style: {
|
|
34668
|
-
fill: "transparent"
|
|
34669
|
-
}
|
|
34670
|
-
}
|
|
34671
|
-
}), props.graphState.coords.map((point, i) => {
|
|
34672
|
-
const pt1 = points.at(i - 1);
|
|
34673
|
-
const pt2 = points[(i + 1) % points.length];
|
|
34674
|
-
if (!pt1 || !pt2) {
|
|
34675
|
-
return null;
|
|
34676
|
-
}
|
|
34677
|
-
return /*#__PURE__*/React.createElement(PolygonAngle, {
|
|
34678
|
-
key: "angle-" + i,
|
|
34679
|
-
centerPoint: point,
|
|
34680
|
-
endPoints: [pt1, pt2],
|
|
34681
|
-
range: range,
|
|
34682
|
-
polygonLines: lines,
|
|
34683
|
-
showAngles: !!showAngles,
|
|
34684
|
-
snapTo: snapTo
|
|
34320
|
+
if (closedPolygon) {
|
|
34321
|
+
const closedPolygonProps = _extends({}, props, {
|
|
34322
|
+
numSides: coords.length
|
|
34685
34323
|
});
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
return /*#__PURE__*/React.createElement(TextLabel, {
|
|
34690
|
-
key: "side-" + i,
|
|
34691
|
-
x: x,
|
|
34692
|
-
y: y
|
|
34693
|
-
}, !Number.isInteger(length) && "≈ ", length);
|
|
34694
|
-
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
34695
|
-
points: [...points],
|
|
34696
|
-
color: "transparent",
|
|
34697
|
-
svgPolygonProps: {
|
|
34698
|
-
ref: polygonRef,
|
|
34699
|
-
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
34700
|
-
strokeWidth: TARGET_SIZE,
|
|
34324
|
+
return /*#__PURE__*/React.createElement(LimitedPolygonGraph, closedPolygonProps);
|
|
34325
|
+
} else {
|
|
34326
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
|
|
34701
34327
|
style: {
|
|
34702
|
-
|
|
34703
|
-
|
|
34328
|
+
fill: "rgba(0,0,0,0)",
|
|
34329
|
+
cursor: "crosshair"
|
|
34704
34330
|
},
|
|
34705
|
-
|
|
34706
|
-
|
|
34707
|
-
|
|
34708
|
-
|
|
34709
|
-
|
|
34710
|
-
|
|
34331
|
+
width: widthPx,
|
|
34332
|
+
height: heightPx,
|
|
34333
|
+
x: left,
|
|
34334
|
+
y: top,
|
|
34335
|
+
onClick: event => {
|
|
34336
|
+
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34337
|
+
const x = event.clientX - elementRect.x;
|
|
34338
|
+
const y = event.clientY - elementRect.y;
|
|
34339
|
+
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34340
|
+
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34341
|
+
}
|
|
34342
|
+
}), /*#__PURE__*/React.createElement(Polyline, {
|
|
34343
|
+
points: [...points],
|
|
34344
|
+
color: "var(--movable-line-stroke-color)",
|
|
34345
|
+
svgPolylineProps: {
|
|
34346
|
+
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
34347
|
+
style: {
|
|
34348
|
+
fill: "transparent"
|
|
34349
|
+
}
|
|
34350
|
+
}
|
|
34351
|
+
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34352
|
+
key: i,
|
|
34353
|
+
point: point,
|
|
34354
|
+
sequenceNumber: i + 1,
|
|
34355
|
+
onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
|
|
34356
|
+
ref: ref => {
|
|
34357
|
+
pointRef.current[i] = ref;
|
|
34711
34358
|
},
|
|
34712
|
-
|
|
34713
|
-
|
|
34714
|
-
|
|
34715
|
-
|
|
34716
|
-
|
|
34717
|
-
|
|
34718
|
-
|
|
34719
|
-
|
|
34720
|
-
|
|
34721
|
-
|
|
34722
|
-
|
|
34723
|
-
|
|
34724
|
-
|
|
34725
|
-
|
|
34726
|
-
},
|
|
34727
|
-
onFocus: () => {
|
|
34728
|
-
dispatch(actions.pointGraph.focusPoint(i));
|
|
34729
|
-
},
|
|
34730
|
-
onClick: () => {
|
|
34731
|
-
dispatch(actions.pointGraph.clickPoint(i));
|
|
34732
|
-
}
|
|
34733
|
-
})));
|
|
34359
|
+
onFocus: () => {
|
|
34360
|
+
dispatch(actions.polygon.focusPoint(i));
|
|
34361
|
+
},
|
|
34362
|
+
onClick: () => {
|
|
34363
|
+
// If the point being clicked is the first point and
|
|
34364
|
+
// there's enough points to form a polygon (3 or more)
|
|
34365
|
+
// Close the shape before setting focus.
|
|
34366
|
+
if (i === 0 && props.graphState.coords.length >= 3) {
|
|
34367
|
+
dispatch(actions.polygon.closePolygon());
|
|
34368
|
+
}
|
|
34369
|
+
dispatch(actions.polygon.clickPoint(i));
|
|
34370
|
+
}
|
|
34371
|
+
})));
|
|
34372
|
+
}
|
|
34734
34373
|
};
|
|
34735
34374
|
function getLines(points) {
|
|
34736
34375
|
return points.map((point, i) => {
|
|
@@ -35333,23 +34972,47 @@ const renderPointGraphControls = props => {
|
|
|
35333
34972
|
}
|
|
35334
34973
|
}, perseusStrings.removePoint));
|
|
35335
34974
|
};
|
|
35336
|
-
|
|
35337
|
-
/**
|
|
35338
|
-
* TODO(catjohnson): The polygon controls will be vastly different from the point controls.
|
|
35339
|
-
* Will be keeping this function as is for the time being, even if it's a copy of the point graph
|
|
35340
|
-
* controls.
|
|
35341
|
-
*/
|
|
35342
34975
|
const renderPolygonGraphControls = props => {
|
|
35343
34976
|
const {
|
|
35344
34977
|
interactionMode,
|
|
35345
34978
|
showRemovePointButton,
|
|
35346
|
-
focusedPointIndex
|
|
34979
|
+
focusedPointIndex,
|
|
34980
|
+
closedPolygon,
|
|
34981
|
+
coords
|
|
35347
34982
|
} = props.state;
|
|
35348
34983
|
const {
|
|
35349
34984
|
perseusStrings
|
|
35350
34985
|
} = props;
|
|
35351
34986
|
const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
|
|
35352
|
-
|
|
34987
|
+
|
|
34988
|
+
// If polygon is closed, show open button.
|
|
34989
|
+
// If polygon is open, show close button.
|
|
34990
|
+
const polygonButton = closedPolygon ? /*#__PURE__*/React.createElement(Button, {
|
|
34991
|
+
kind: "secondary",
|
|
34992
|
+
style: {
|
|
34993
|
+
width: "100%",
|
|
34994
|
+
marginLeft: "20px"
|
|
34995
|
+
},
|
|
34996
|
+
tabIndex: 0,
|
|
34997
|
+
onClick: () => {
|
|
34998
|
+
props.dispatch(actions.polygon.openPolygon());
|
|
34999
|
+
}
|
|
35000
|
+
}, perseusStrings.openPolygon) : /*#__PURE__*/React.createElement(Button, {
|
|
35001
|
+
kind: "secondary"
|
|
35002
|
+
// Conditional disable when there are less than 3 points in
|
|
35003
|
+
// the graph
|
|
35004
|
+
,
|
|
35005
|
+
disabled: coords.length < 3,
|
|
35006
|
+
style: {
|
|
35007
|
+
width: "100%",
|
|
35008
|
+
marginLeft: "20px"
|
|
35009
|
+
},
|
|
35010
|
+
tabIndex: 0,
|
|
35011
|
+
onClick: () => {
|
|
35012
|
+
props.dispatch(actions.polygon.closePolygon());
|
|
35013
|
+
}
|
|
35014
|
+
}, perseusStrings.closePolygon);
|
|
35015
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
|
|
35353
35016
|
style: {
|
|
35354
35017
|
flexDirection: "row",
|
|
35355
35018
|
width: props.width
|
|
@@ -35359,8 +35022,13 @@ const renderPolygonGraphControls = props => {
|
|
|
35359
35022
|
style: {
|
|
35360
35023
|
width: "100%",
|
|
35361
35024
|
marginLeft: "20px"
|
|
35362
|
-
}
|
|
35363
|
-
|
|
35025
|
+
}
|
|
35026
|
+
// Disable button when polygon is closed.
|
|
35027
|
+
,
|
|
35028
|
+
disabled: closedPolygon
|
|
35029
|
+
// Do not make the button tabbable when it is disabled.
|
|
35030
|
+
,
|
|
35031
|
+
tabIndex: closedPolygon ? -1 : 0,
|
|
35364
35032
|
onClick: () => {
|
|
35365
35033
|
props.dispatch(actions.polygon.addPoint([0, 0]));
|
|
35366
35034
|
}
|
|
@@ -35368,19 +35036,21 @@ const renderPolygonGraphControls = props => {
|
|
|
35368
35036
|
id: REMOVE_BUTTON_ID,
|
|
35369
35037
|
kind: "secondary",
|
|
35370
35038
|
color: "destructive"
|
|
35039
|
+
// Disable button when polygon is closed.
|
|
35040
|
+
,
|
|
35041
|
+
disabled: closedPolygon || !shouldShowRemoveButton
|
|
35371
35042
|
// This button is meant to be interacted with by the mouse only
|
|
35372
35043
|
// Never allow learners to tab to this button
|
|
35373
35044
|
,
|
|
35374
35045
|
tabIndex: -1,
|
|
35375
35046
|
style: {
|
|
35376
35047
|
width: "100%",
|
|
35377
|
-
marginLeft: "20px"
|
|
35378
|
-
visibility: shouldShowRemoveButton ? "visible" : "hidden"
|
|
35048
|
+
marginLeft: "20px"
|
|
35379
35049
|
},
|
|
35380
35050
|
onClick: _event => {
|
|
35381
35051
|
props.dispatch(actions.polygon.removePoint(props.state.focusedPointIndex));
|
|
35382
35052
|
}
|
|
35383
|
-
}, perseusStrings.removePoint));
|
|
35053
|
+
}, perseusStrings.removePoint), polygonButton));
|
|
35384
35054
|
};
|
|
35385
35055
|
const renderGraphControls = props => {
|
|
35386
35056
|
const {
|
|
@@ -35431,7 +35101,7 @@ function handleBlurEvent(_event, state, dispatch) {
|
|
|
35431
35101
|
}
|
|
35432
35102
|
function handleKeyboardEvent(event, state, dispatch) {
|
|
35433
35103
|
if (isUnlimitedGraphState(state)) {
|
|
35434
|
-
if (event.key === "Backspace") {
|
|
35104
|
+
if (event.key === "Backspace" || event.key === "Delete") {
|
|
35435
35105
|
var _document$activeEleme;
|
|
35436
35106
|
// TODO(benchristel): Checking classList here is a hack to prevent
|
|
35437
35107
|
// points from being deleted if the user presses the backspace key
|
|
@@ -35439,7 +35109,10 @@ function handleKeyboardEvent(event, state, dispatch) {
|
|
|
35439
35109
|
// should move the keyboard event handler to the movable point
|
|
35440
35110
|
// handle element.
|
|
35441
35111
|
if ((_document$activeEleme = document.activeElement) != null && _document$activeEleme.classList.contains("movable-point__focusable-handle")) {
|
|
35442
|
-
|
|
35112
|
+
// Only allow delete if type is point or a polygon that is open.
|
|
35113
|
+
if (state.type === "point" || state.type === "polygon" && !state.closedPolygon) {
|
|
35114
|
+
dispatch(actions.global.deleteIntent());
|
|
35115
|
+
}
|
|
35443
35116
|
}
|
|
35444
35117
|
|
|
35445
35118
|
// After removing a point blur
|
|
@@ -35670,7 +35343,8 @@ function initializeGraphState(params) {
|
|
|
35670
35343
|
focusedPointIndex: null,
|
|
35671
35344
|
showRemovePointButton: false,
|
|
35672
35345
|
interactionMode: "mouse",
|
|
35673
|
-
showKeyboardInteractionInvitation: false
|
|
35346
|
+
showKeyboardInteractionInvitation: false,
|
|
35347
|
+
closedPolygon: false
|
|
35674
35348
|
});
|
|
35675
35349
|
case "point":
|
|
35676
35350
|
return _extends({}, shared, {
|
|
@@ -35950,6 +35624,10 @@ function interactiveGraphReducer(state, action) {
|
|
|
35950
35624
|
return doDeleteIntent(state);
|
|
35951
35625
|
case CLICK_POINT:
|
|
35952
35626
|
return doClickPoint(state, action);
|
|
35627
|
+
case CLOSE_POLYGON:
|
|
35628
|
+
return doClosePolygon(state);
|
|
35629
|
+
case OPEN_POLYGON:
|
|
35630
|
+
return doOpenPolygon(state);
|
|
35953
35631
|
case CHANGE_INTERACTION_MODE:
|
|
35954
35632
|
return doChangeInteractionMode(state, action);
|
|
35955
35633
|
case CHANGE_KEYBOARD_INVITATION_VISIBILITY:
|
|
@@ -36003,6 +35681,22 @@ function doClickPoint(state, action) {
|
|
|
36003
35681
|
}
|
|
36004
35682
|
return state;
|
|
36005
35683
|
}
|
|
35684
|
+
function doClosePolygon(state) {
|
|
35685
|
+
if (isUnlimitedGraphState(state) && state.type === "polygon") {
|
|
35686
|
+
return _extends({}, state, {
|
|
35687
|
+
closedPolygon: true
|
|
35688
|
+
});
|
|
35689
|
+
}
|
|
35690
|
+
return state;
|
|
35691
|
+
}
|
|
35692
|
+
function doOpenPolygon(state) {
|
|
35693
|
+
if (isUnlimitedGraphState(state) && state.type === "polygon") {
|
|
35694
|
+
return _extends({}, state, {
|
|
35695
|
+
closedPolygon: false
|
|
35696
|
+
});
|
|
35697
|
+
}
|
|
35698
|
+
return state;
|
|
35699
|
+
}
|
|
36006
35700
|
function doChangeInteractionMode(state, action) {
|
|
36007
35701
|
if (isUnlimitedGraphState(state)) {
|
|
36008
35702
|
const nextKeyboardInvitation = action.mode === "keyboard" ? false : state.showKeyboardInteractionInvitation;
|
|
@@ -36359,13 +36053,14 @@ function doAddPoint(state, action) {
|
|
|
36359
36053
|
return state;
|
|
36360
36054
|
}
|
|
36361
36055
|
}
|
|
36056
|
+
const newCoords = [...state.coords, snappedPoint];
|
|
36362
36057
|
|
|
36363
36058
|
// If there's no point in spot where we want the new point to go we add it there
|
|
36364
36059
|
return _extends({}, state, {
|
|
36365
36060
|
hasBeenInteractedWith: true,
|
|
36366
|
-
coords:
|
|
36061
|
+
coords: newCoords,
|
|
36367
36062
|
showRemovePointButton: false,
|
|
36368
|
-
focusedPointIndex:
|
|
36063
|
+
focusedPointIndex: newCoords.length - 1
|
|
36369
36064
|
});
|
|
36370
36065
|
}
|
|
36371
36066
|
function doRemovePoint(state, action) {
|
|
@@ -38676,6 +38371,8 @@ var InteractiveGraph$1 = {
|
|
|
38676
38371
|
displayName: "Interactive graph (Assessments only)",
|
|
38677
38372
|
widget: InteractiveGraph,
|
|
38678
38373
|
staticTransform: staticTransform$3,
|
|
38374
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
38375
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInteractiveGraphUserInput'.
|
|
38679
38376
|
scorer: scoreInteractiveGraph
|
|
38680
38377
|
};
|
|
38681
38378
|
|
|
@@ -39669,6 +39366,8 @@ var LabelImage$1 = {
|
|
|
39669
39366
|
widget: LabelImageWithDependencies,
|
|
39670
39367
|
accessible: true,
|
|
39671
39368
|
isLintable: true,
|
|
39369
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
39370
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusLabelImageUserInput'.
|
|
39672
39371
|
scorer: scoreLabelImage
|
|
39673
39372
|
};
|
|
39674
39373
|
|
|
@@ -40611,6 +40310,8 @@ var Matcher$1 = {
|
|
|
40611
40310
|
displayName: "Matcher (two column)",
|
|
40612
40311
|
widget: Matcher,
|
|
40613
40312
|
isLintable: true,
|
|
40313
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
40314
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatcherUserInput'.
|
|
40614
40315
|
scorer: scoreMatcher
|
|
40615
40316
|
};
|
|
40616
40317
|
|
|
@@ -40627,7 +40328,35 @@ const getPromptJSON$8 = (renderProps, userInput) => {
|
|
|
40627
40328
|
};
|
|
40628
40329
|
};
|
|
40629
40330
|
|
|
40331
|
+
/**
|
|
40332
|
+
* Checks user input from the matrix widget to see if it is scorable.
|
|
40333
|
+
*
|
|
40334
|
+
* Note: The matrix widget cannot do much validation without the Scoring
|
|
40335
|
+
* Data because of its use of KhanAnswerTypes as a core part of scoring.
|
|
40336
|
+
*
|
|
40337
|
+
* @see `scoreMatrix()` for more details.
|
|
40338
|
+
*/
|
|
40339
|
+
function validateMatrix(userInput, validationData, strings) {
|
|
40340
|
+
const supplied = userInput.answers;
|
|
40341
|
+
const suppliedSize = getMatrixSize(supplied);
|
|
40342
|
+
for (let row = 0; row < suppliedSize[0]; row++) {
|
|
40343
|
+
for (let col = 0; col < suppliedSize[1]; col++) {
|
|
40344
|
+
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40345
|
+
return {
|
|
40346
|
+
type: "invalid",
|
|
40347
|
+
message: strings.fillAllCells
|
|
40348
|
+
};
|
|
40349
|
+
}
|
|
40350
|
+
}
|
|
40351
|
+
}
|
|
40352
|
+
return null;
|
|
40353
|
+
}
|
|
40354
|
+
|
|
40630
40355
|
function scoreMatrix(userInput, rubric, strings) {
|
|
40356
|
+
const validationResult = validateMatrix(userInput, rubric, strings);
|
|
40357
|
+
if (validationResult != null) {
|
|
40358
|
+
return validationResult;
|
|
40359
|
+
}
|
|
40631
40360
|
const solution = rubric.answers;
|
|
40632
40361
|
const supplied = userInput.answers;
|
|
40633
40362
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -40635,13 +40364,9 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
40635
40364
|
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
40636
40365
|
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
40637
40366
|
let message = null;
|
|
40638
|
-
let hasEmptyCell = false;
|
|
40639
40367
|
let incorrect = false;
|
|
40640
40368
|
_(suppliedSize[0]).times(row => {
|
|
40641
40369
|
_(suppliedSize[1]).times(col => {
|
|
40642
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40643
|
-
hasEmptyCell = true;
|
|
40644
|
-
}
|
|
40645
40370
|
if (!incorrectSize) {
|
|
40646
40371
|
const validator = createValidator(
|
|
40647
40372
|
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
@@ -40659,12 +40384,6 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
40659
40384
|
}
|
|
40660
40385
|
});
|
|
40661
40386
|
});
|
|
40662
|
-
if (hasEmptyCell) {
|
|
40663
|
-
return {
|
|
40664
|
-
type: "invalid",
|
|
40665
|
-
message: strings.fillAllCells
|
|
40666
|
-
};
|
|
40667
|
-
}
|
|
40668
40387
|
if (incorrectSize) {
|
|
40669
40388
|
return {
|
|
40670
40389
|
type: "points",
|
|
@@ -41093,6 +40812,8 @@ var Matrix$1 = {
|
|
|
41093
40812
|
transform: propTransform$1,
|
|
41094
40813
|
staticTransform: staticTransform$2,
|
|
41095
40814
|
isLintable: true,
|
|
40815
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
40816
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
41096
40817
|
scorer: scoreMatrix
|
|
41097
40818
|
};
|
|
41098
40819
|
|
|
@@ -42724,6 +42445,8 @@ var NumberLine$1 = {
|
|
|
42724
42445
|
widget: NumberLine,
|
|
42725
42446
|
transform: numberLineTransform,
|
|
42726
42447
|
staticTransform: staticTransform$1,
|
|
42448
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42449
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
42727
42450
|
scorer: scoreNumberLine
|
|
42728
42451
|
};
|
|
42729
42452
|
|
|
@@ -43304,6 +43027,8 @@ var Orderer$1 = {
|
|
|
43304
43027
|
hidden: true,
|
|
43305
43028
|
widget: Orderer,
|
|
43306
43029
|
isLintable: true,
|
|
43030
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
43031
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
43307
43032
|
scorer: scoreOrderer
|
|
43308
43033
|
};
|
|
43309
43034
|
|
|
@@ -44390,6 +44115,8 @@ var Plotter$1 = {
|
|
|
44390
44115
|
hidden: true,
|
|
44391
44116
|
widget: Plotter,
|
|
44392
44117
|
staticTransform: staticTransform,
|
|
44118
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44119
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
44393
44120
|
scorer: scorePlotter
|
|
44394
44121
|
};
|
|
44395
44122
|
|
|
@@ -44575,6 +44302,8 @@ var Sorter$1 = {
|
|
|
44575
44302
|
displayName: "Sorter",
|
|
44576
44303
|
widget: Sorter,
|
|
44577
44304
|
isLintable: true,
|
|
44305
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44306
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
44578
44307
|
scorer: scoreSorter
|
|
44579
44308
|
};
|
|
44580
44309
|
|
|
@@ -44870,6 +44599,8 @@ var Table$1 = {
|
|
|
44870
44599
|
transform: propTransform,
|
|
44871
44600
|
hidden: true,
|
|
44872
44601
|
isLintable: true,
|
|
44602
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44603
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
44873
44604
|
scorer: scoreTable
|
|
44874
44605
|
};
|
|
44875
44606
|
|
|
@@ -45053,7 +44784,7 @@ const init = function init() {
|
|
|
45053
44784
|
|
|
45054
44785
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45055
44786
|
const libName = "@khanacademy/perseus";
|
|
45056
|
-
const libVersion = "
|
|
44787
|
+
const libVersion = "43.0.0";
|
|
45057
44788
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45058
44789
|
|
|
45059
44790
|
const apiVersion = {
|
|
@@ -45677,13 +45408,6 @@ class ServerItemRenderer extends React.Component {
|
|
|
45677
45408
|
this.props.onRendered(true);
|
|
45678
45409
|
}
|
|
45679
45410
|
}
|
|
45680
|
-
if (this.props.score && this.props.score !== prevProps.score) {
|
|
45681
|
-
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45682
|
-
this.setState({
|
|
45683
|
-
questionCompleted: this.props.score.correct,
|
|
45684
|
-
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
45685
|
-
});
|
|
45686
|
-
}
|
|
45687
45411
|
}
|
|
45688
45412
|
componentWillUnmount() {
|
|
45689
45413
|
if (this.blurTimeoutID != null) {
|
|
@@ -45764,7 +45488,7 @@ class ServerItemRenderer extends React.Component {
|
|
|
45764
45488
|
|
|
45765
45489
|
/**
|
|
45766
45490
|
* Accepts a question area widgetId, or an answer area widgetId of
|
|
45767
|
-
* the form "answer-
|
|
45491
|
+
* the form "answer-input-number 1", or the string "answer-area"
|
|
45768
45492
|
* for the whole answer area (if the answer area is a single widget).
|
|
45769
45493
|
*/
|
|
45770
45494
|
_setWidgetProps(widgetId, newProps, callback) {
|
|
@@ -45801,37 +45525,18 @@ class ServerItemRenderer extends React.Component {
|
|
|
45801
45525
|
return this.questionRenderer.getPromptJSON();
|
|
45802
45526
|
}
|
|
45803
45527
|
|
|
45804
|
-
/**
|
|
45805
|
-
* Returns an array of the widget `.getUserInput()` results
|
|
45806
|
-
*
|
|
45807
|
-
* TODO: can we remove this? Seems to be just for backwards
|
|
45808
|
-
* compatibility with old Perseus Chrome logging
|
|
45809
|
-
* @deprecated use getUserInput
|
|
45810
|
-
*/
|
|
45811
|
-
getUserInputLegacy() {
|
|
45812
|
-
return this.questionRenderer.getUserInput();
|
|
45813
|
-
}
|
|
45814
|
-
|
|
45815
|
-
/**
|
|
45816
|
-
* Returns an object of the widget `.getUserInput()` results
|
|
45817
|
-
*/
|
|
45818
|
-
getUserInput() {
|
|
45819
|
-
return this.questionRenderer.getUserInputMap();
|
|
45820
|
-
}
|
|
45821
|
-
|
|
45822
45528
|
/**
|
|
45823
45529
|
* Grades the item.
|
|
45824
|
-
*
|
|
45825
|
-
* @deprecated use scorePerseusItem
|
|
45826
45530
|
*/
|
|
45827
45531
|
scoreInput() {
|
|
45828
|
-
const
|
|
45829
|
-
const
|
|
45532
|
+
const guessAndScore = this.questionRenderer.guessAndScore();
|
|
45533
|
+
const guess = guessAndScore[0];
|
|
45534
|
+
const score = guessAndScore[1];
|
|
45830
45535
|
|
|
45831
45536
|
// Continue to include an empty guess for the now defunct answer area.
|
|
45832
45537
|
// TODO(alex): Check whether we rely on the format here for
|
|
45833
45538
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
45834
|
-
const maxCompatGuess = [
|
|
45539
|
+
const maxCompatGuess = [guess, []];
|
|
45835
45540
|
const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
45836
45541
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45837
45542
|
this.setState({
|
|
@@ -47679,6 +47384,140 @@ const registerAllWidgetsForTesting = () => {
|
|
|
47679
47384
|
replaceDeprecatedWidgets();
|
|
47680
47385
|
};
|
|
47681
47386
|
|
|
47387
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
47388
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
47389
|
+
|
|
47390
|
+
/**
|
|
47391
|
+
* Regex for widget placeholders in a string.
|
|
47392
|
+
*
|
|
47393
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
47394
|
+
* Second capture group is the widget type (ex. "radio)
|
|
47395
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
47396
|
+
*/
|
|
47397
|
+
function getWidgetRegex() {
|
|
47398
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
47399
|
+
}
|
|
47400
|
+
|
|
47401
|
+
/**
|
|
47402
|
+
* Add a widget placeholder using the widget ID.
|
|
47403
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
47404
|
+
*
|
|
47405
|
+
* @param {string} id
|
|
47406
|
+
* @returns {string}
|
|
47407
|
+
*/
|
|
47408
|
+
function addWidget(id) {
|
|
47409
|
+
return `[[☃ ${id}]]`;
|
|
47410
|
+
}
|
|
47411
|
+
|
|
47412
|
+
/**
|
|
47413
|
+
* Get a widget type by a widget's ID
|
|
47414
|
+
* (please don't derive type from ID)
|
|
47415
|
+
*
|
|
47416
|
+
* @param {string} widgetId the ID of the widget
|
|
47417
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47418
|
+
* @returns {string} the widget type (ie "radio")
|
|
47419
|
+
*/
|
|
47420
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
47421
|
+
var _widget$type;
|
|
47422
|
+
const widget = widgetMap[WidgetId];
|
|
47423
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
47424
|
+
}
|
|
47425
|
+
|
|
47426
|
+
/**
|
|
47427
|
+
* Does the content have a specific type of widget?
|
|
47428
|
+
*
|
|
47429
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
47430
|
+
* @param {string} content the string to search through
|
|
47431
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47432
|
+
* @returns {boolean} if the content includes the widget type
|
|
47433
|
+
*/
|
|
47434
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
47435
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
47436
|
+
}
|
|
47437
|
+
|
|
47438
|
+
/**
|
|
47439
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
47440
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
47441
|
+
*
|
|
47442
|
+
* Content should contain Perseus widget placeholders,
|
|
47443
|
+
* which look like: '[[☃ radio 1]]'.
|
|
47444
|
+
*
|
|
47445
|
+
* @param {string} content
|
|
47446
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
47447
|
+
*/
|
|
47448
|
+
function getWidgetIdsFromContent(content) {
|
|
47449
|
+
const widgets = [];
|
|
47450
|
+
const localWidgetRegex = getWidgetRegex();
|
|
47451
|
+
let match = localWidgetRegex.exec(content);
|
|
47452
|
+
while (match !== null) {
|
|
47453
|
+
widgets.push(match[1]);
|
|
47454
|
+
match = localWidgetRegex.exec(content);
|
|
47455
|
+
}
|
|
47456
|
+
return widgets;
|
|
47457
|
+
}
|
|
47458
|
+
|
|
47459
|
+
/**
|
|
47460
|
+
* Get a list of widget IDs from content,
|
|
47461
|
+
* but only for specific widget types
|
|
47462
|
+
*
|
|
47463
|
+
* @param {string} type the type of widget (ie "radio")
|
|
47464
|
+
* @param {string} content the string to parse
|
|
47465
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47466
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
47467
|
+
*/
|
|
47468
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
47469
|
+
const rv = [];
|
|
47470
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
47471
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
47472
|
+
const widget = widgetMap[widgetId];
|
|
47473
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
47474
|
+
rv.push(widgetId);
|
|
47475
|
+
}
|
|
47476
|
+
});
|
|
47477
|
+
return rv;
|
|
47478
|
+
}
|
|
47479
|
+
|
|
47480
|
+
/**
|
|
47481
|
+
* Pull the widget map out of ItemData
|
|
47482
|
+
*
|
|
47483
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
47484
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
47485
|
+
*/
|
|
47486
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
47487
|
+
return itemData.question.widgets;
|
|
47488
|
+
}
|
|
47489
|
+
|
|
47490
|
+
/**
|
|
47491
|
+
* Get the widget information from a WidgetMap
|
|
47492
|
+
*
|
|
47493
|
+
* @param {string} widgetId the ID of the widget
|
|
47494
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
47495
|
+
*/
|
|
47496
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
47497
|
+
var _widgetMap$widgetId;
|
|
47498
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
47499
|
+
}
|
|
47500
|
+
|
|
47501
|
+
/**
|
|
47502
|
+
* Get select widgets from a widget map.
|
|
47503
|
+
* Useful for multi-items that needs to split
|
|
47504
|
+
* one PerseusItem into several
|
|
47505
|
+
*
|
|
47506
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
47507
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
47508
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
47509
|
+
*/
|
|
47510
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
47511
|
+
const widgets = {};
|
|
47512
|
+
widgetIds.forEach(widgetId => {
|
|
47513
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
47514
|
+
if (widget) {
|
|
47515
|
+
widgets[widgetId] = widget;
|
|
47516
|
+
}
|
|
47517
|
+
});
|
|
47518
|
+
return widgets;
|
|
47519
|
+
}
|
|
47520
|
+
|
|
47682
47521
|
/**
|
|
47683
47522
|
* This function extracts the answers from the widgets.
|
|
47684
47523
|
*
|
|
@@ -49623,5 +49462,5 @@ const GrapherUtil = {
|
|
|
49623
49462
|
typeToButton
|
|
49624
49463
|
};
|
|
49625
49464
|
|
|
49626
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType,
|
|
49465
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
49627
49466
|
//# sourceMappingURL=index.js.map
|