@khanacademy/perseus 47.0.0 → 48.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/es/index.js +259 -187
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +249 -187
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +33 -4
- package/dist/renderer.d.ts +0 -4
- package/dist/server-item-renderer.d.ts +0 -6
- package/dist/util/test-utils.d.ts +7 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -2412,9 +2412,6 @@ function scoreExpression(userInput, rubric, strings, locale) {
|
|
|
2412
2412
|
// We matched a graded answer form, so we can now tell the user
|
|
2413
2413
|
// whether their input was correct or incorrect, and hand out
|
|
2414
2414
|
// points accordingly
|
|
2415
|
-
// TODO(eater): Seems silly to translate result to this
|
|
2416
|
-
// invalid/points thing and immediately translate it back in
|
|
2417
|
-
// ItemRenderer.scoreInput()
|
|
2418
2415
|
return {
|
|
2419
2416
|
type: "points",
|
|
2420
2417
|
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
@@ -3363,7 +3360,7 @@ const supportsPassiveEvents = () => {
|
|
|
3363
3360
|
window.addEventListener("testPassive", null, opts);
|
|
3364
3361
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
3365
3362
|
window.removeEventListener("testPassive", null, opts);
|
|
3366
|
-
} catch
|
|
3363
|
+
} catch {
|
|
3367
3364
|
// Intentionally left empty!
|
|
3368
3365
|
}
|
|
3369
3366
|
return supportsPassive;
|
|
@@ -3604,7 +3601,9 @@ function changeViewportTag(contentString, callback) {
|
|
|
3604
3601
|
// finished resetting.
|
|
3605
3602
|
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
3606
3603
|
// eslint-disable-next-line no-restricted-syntax
|
|
3607
|
-
|
|
3604
|
+
if (callback != null) {
|
|
3605
|
+
setTimeout(callback, 0);
|
|
3606
|
+
}
|
|
3608
3607
|
}, 0);
|
|
3609
3608
|
}
|
|
3610
3609
|
|
|
@@ -16964,7 +16963,7 @@ class ImageLoader extends React__namespace.Component {
|
|
|
16964
16963
|
onKeyUp = e => {
|
|
16965
16964
|
// 13 is enter key, 32 is space key
|
|
16966
16965
|
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
16967
|
-
imgProps.onClick
|
|
16966
|
+
imgProps.onClick?.(e);
|
|
16968
16967
|
}
|
|
16969
16968
|
};
|
|
16970
16969
|
onKeyDown = e => {
|
|
@@ -17033,7 +17032,7 @@ const doJSONP = function (url, options) {
|
|
|
17033
17032
|
|
|
17034
17033
|
// A cleanup function to run when we're done.
|
|
17035
17034
|
function cleanup() {
|
|
17036
|
-
document.head
|
|
17035
|
+
document.head?.removeChild(script);
|
|
17037
17036
|
delete window[options.callbackName];
|
|
17038
17037
|
}
|
|
17039
17038
|
|
|
@@ -17057,7 +17056,7 @@ const doJSONP = function (url, options) {
|
|
|
17057
17056
|
});
|
|
17058
17057
|
|
|
17059
17058
|
// Insert the script to start the download.
|
|
17060
|
-
document.head
|
|
17059
|
+
document.head?.appendChild(script);
|
|
17061
17060
|
};
|
|
17062
17061
|
|
|
17063
17062
|
// For offline exercises in the mobile app, we download the graphie data
|
|
@@ -17396,7 +17395,7 @@ class SvgImage extends React__namespace.Component {
|
|
|
17396
17395
|
$image.width() < this.props.width || this.props.zoomToFullSizeOnMobile) {
|
|
17397
17396
|
ZoomService.handleZoomClick(e, this.props.zoomToFullSizeOnMobile);
|
|
17398
17397
|
}
|
|
17399
|
-
this.props.trackInteraction
|
|
17398
|
+
this.props.trackInteraction?.();
|
|
17400
17399
|
};
|
|
17401
17400
|
handleUpdate = status => {
|
|
17402
17401
|
this.props.onUpdate();
|
|
@@ -19803,8 +19802,8 @@ class WidgetContainer extends React__namespace.Component {
|
|
|
19803
19802
|
};
|
|
19804
19803
|
|
|
19805
19804
|
// Note: if you add more props here, please consider whether or not
|
|
19806
|
-
// it should be auto-serialized
|
|
19807
|
-
// widget-jsonify-deprecated.jsx and widget-prop-denylist.jsx
|
|
19805
|
+
// it should be auto-serialized.
|
|
19806
|
+
// See widget-jsonify-deprecated.jsx and widget-prop-denylist.jsx
|
|
19808
19807
|
|
|
19809
19808
|
// We default to an empty object for style instead of null
|
|
19810
19809
|
// because of a strange bug where the static styles aren't applied
|
|
@@ -21100,15 +21099,6 @@ class Renderer extends React__namespace.Component {
|
|
|
21100
21099
|
return combinedScore;
|
|
21101
21100
|
}
|
|
21102
21101
|
|
|
21103
|
-
/**
|
|
21104
|
-
* @deprecated use scorePerseusItem
|
|
21105
|
-
*/
|
|
21106
|
-
guessAndScore = () => {
|
|
21107
|
-
const totalGuess = this.getUserInput();
|
|
21108
|
-
const totalScore = this.score();
|
|
21109
|
-
return [totalGuess, totalScore];
|
|
21110
|
-
};
|
|
21111
|
-
|
|
21112
21102
|
// TranslationLinter callback
|
|
21113
21103
|
handletranslationLintErrors = lintErrors => {
|
|
21114
21104
|
if (!this._isMounted) {
|
|
@@ -21786,11 +21776,11 @@ class SimpleKeypadInput extends React__namespace.Component {
|
|
|
21786
21776
|
keypadType: mathInput.KeypadType.FRACTION
|
|
21787
21777
|
}, () => {
|
|
21788
21778
|
if (_this._isMounted) {
|
|
21789
|
-
onFocus
|
|
21779
|
+
onFocus?.();
|
|
21790
21780
|
}
|
|
21791
21781
|
});
|
|
21792
21782
|
} else {
|
|
21793
|
-
onFocus
|
|
21783
|
+
onFocus?.();
|
|
21794
21784
|
}
|
|
21795
21785
|
},
|
|
21796
21786
|
value: value == null ? "" : "" + value
|
|
@@ -22029,9 +22019,6 @@ function scoreInputNumber(userInput, rubric, strings) {
|
|
|
22029
22019
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
22030
22020
|
const currentValue = ParseTex$1(userInput.currentValue);
|
|
22031
22021
|
const result = val(currentValue);
|
|
22032
|
-
|
|
22033
|
-
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
22034
|
-
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
22035
22022
|
if (result.empty) {
|
|
22036
22023
|
return {
|
|
22037
22024
|
type: "invalid",
|
|
@@ -22395,10 +22382,6 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22395
22382
|
message: matchedAnswer?.message ?? null,
|
|
22396
22383
|
guess: localValue
|
|
22397
22384
|
};
|
|
22398
|
-
|
|
22399
|
-
// TODO(eater): Seems silly to translate result to this
|
|
22400
|
-
// invalid/points thing and immediately translate it
|
|
22401
|
-
// back in ItemRenderer.scoreInput()
|
|
22402
22385
|
if (result.empty) {
|
|
22403
22386
|
return {
|
|
22404
22387
|
type: "invalid",
|
|
@@ -25255,7 +25238,7 @@ const scrollElementToOffset = function (elementOrWindow, offset) {
|
|
|
25255
25238
|
// TODO(jeff, WEB-1378): Use Wonder Blocks Timing API
|
|
25256
25239
|
// eslint-disable-next-line no-restricted-syntax
|
|
25257
25240
|
clearInterval(scrollInterval);
|
|
25258
|
-
callback
|
|
25241
|
+
callback?.();
|
|
25259
25242
|
}
|
|
25260
25243
|
}, tickLength);
|
|
25261
25244
|
};
|
|
@@ -27317,7 +27300,7 @@ class CSProgram extends React__namespace.Component {
|
|
|
27317
27300
|
let data = {};
|
|
27318
27301
|
try {
|
|
27319
27302
|
data = JSON.parse(e.originalEvent.data);
|
|
27320
|
-
} catch
|
|
27303
|
+
} catch {
|
|
27321
27304
|
return;
|
|
27322
27305
|
}
|
|
27323
27306
|
if (___default["default"].isUndefined(data.testsPassed)) {
|
|
@@ -27616,7 +27599,7 @@ class Dropdown extends React__namespace.Component {
|
|
|
27616
27599
|
key: "placeholder",
|
|
27617
27600
|
value: "0",
|
|
27618
27601
|
disabled: true,
|
|
27619
|
-
label: this.props.placeholder
|
|
27602
|
+
label: this.props.placeholder || " "
|
|
27620
27603
|
}), ...this.props.choices.map((choice, i) => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
27621
27604
|
key: String(i + 1),
|
|
27622
27605
|
value: String(i + 1),
|
|
@@ -27637,15 +27620,14 @@ class Dropdown extends React__namespace.Component {
|
|
|
27637
27620
|
}
|
|
27638
27621
|
}, this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
|
|
27639
27622
|
tag: "label",
|
|
27640
|
-
|
|
27623
|
+
htmlFor: ids.get("dropdown")
|
|
27641
27624
|
}, this.props.visibleLabel), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
|
|
27642
27625
|
id: ids.get("dropdown"),
|
|
27643
27626
|
placeholder: "",
|
|
27644
27627
|
onChange: value => this._handleChange(parseInt(value)),
|
|
27645
27628
|
selectedValue: String(this.props.selected),
|
|
27646
27629
|
disabled: this.props.apiOptions.readOnly,
|
|
27647
|
-
"aria-label": this.props.ariaLabel || this.context.strings.selectAnAnswer
|
|
27648
|
-
"aria-labelledby": ids.get("dropdown-label")
|
|
27630
|
+
"aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
|
|
27649
27631
|
// This is currently necessary for SRs to read the labels properly.
|
|
27650
27632
|
// However, WB is working on a change to add the "combobox" role to
|
|
27651
27633
|
// all dropdowns.
|
|
@@ -28345,7 +28327,6 @@ class GradedGroupSet extends React__namespace.Component {
|
|
|
28345
28327
|
currentGroup: 0
|
|
28346
28328
|
};
|
|
28347
28329
|
shouldComponentUpdate(nextProps, nextState) {
|
|
28348
|
-
nextProps.gradedGroups;
|
|
28349
28330
|
return nextProps !== this.props || nextState !== this.state;
|
|
28350
28331
|
}
|
|
28351
28332
|
change = (() => {
|
|
@@ -29869,7 +29850,7 @@ class Iframe extends React__namespace.Component {
|
|
|
29869
29850
|
let data = {};
|
|
29870
29851
|
try {
|
|
29871
29852
|
data = JSON.parse(e.originalEvent.data);
|
|
29872
|
-
} catch
|
|
29853
|
+
} catch {
|
|
29873
29854
|
return;
|
|
29874
29855
|
}
|
|
29875
29856
|
if (___default["default"].isUndefined(data.testsPassed)) {
|
|
@@ -30964,10 +30945,10 @@ class Graph extends React__namespace.Component {
|
|
|
30964
30945
|
this.props.onMouseDown(coord);
|
|
30965
30946
|
}, this) : undefined;
|
|
30966
30947
|
const onMouseOver = $instructionsWrapper ? function () {
|
|
30967
|
-
$instructionsWrapper
|
|
30948
|
+
$instructionsWrapper?.css("opacity", invisible);
|
|
30968
30949
|
} : undefined;
|
|
30969
30950
|
const onMouseOut = $instructionsWrapper ? function () {
|
|
30970
|
-
$instructionsWrapper
|
|
30951
|
+
$instructionsWrapper?.css("opacity", visible);
|
|
30971
30952
|
} : undefined;
|
|
30972
30953
|
/* eslint-enable indent */
|
|
30973
30954
|
|
|
@@ -31680,6 +31661,7 @@ function replaceOutsideTeX(mathString) {
|
|
|
31680
31661
|
const parsed = mathOnlyParser(mathString);
|
|
31681
31662
|
let result = "";
|
|
31682
31663
|
for (const piece of parsed) {
|
|
31664
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
31683
31665
|
piece.type === "math" ? result += "$" + piece.content + "$" : piece.type === "specialCharacter" ? result += escapeIfUnescaped(piece.content) : result += piece.content;
|
|
31684
31666
|
}
|
|
31685
31667
|
return `\\text{${result}}`;
|
|
@@ -32532,11 +32514,39 @@ const LegacyGrid = _ref => {
|
|
|
32532
32514
|
return null;
|
|
32533
32515
|
};
|
|
32534
32516
|
|
|
32517
|
+
/**
|
|
32518
|
+
* The Perseus "data schema" file.
|
|
32519
|
+
*
|
|
32520
|
+
* This file, and the types in it, represents the "data schema" that Perseus
|
|
32521
|
+
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
32522
|
+
* that conform to the types in this file. Similarly, the top-level renderers
|
|
32523
|
+
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
32524
|
+
*
|
|
32525
|
+
* WARNING: This file should not import any types from elsewhere so that it is
|
|
32526
|
+
* easy to reason about changes that alter the Perseus schema. This helps
|
|
32527
|
+
* ensure that it is not changed accidentally when upgrading a dependant
|
|
32528
|
+
* package or other part of Perseus code. Note that TypeScript does type
|
|
32529
|
+
* checking via something called "structural typing". This means that as long
|
|
32530
|
+
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
32531
|
+
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
32532
|
+
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
32533
|
+
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
32534
|
+
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
32535
|
+
* labels are for humans, not TypeScript. :)
|
|
32536
|
+
*
|
|
32537
|
+
* If you make changes to types in this file, be very sure that:
|
|
32538
|
+
*
|
|
32539
|
+
* a) the changes are backwards compatible. If they are not, old data from
|
|
32540
|
+
* previous versions of the "schema" could become unrenderable, or worse,
|
|
32541
|
+
* introduce hard-to-diagnose bugs.
|
|
32542
|
+
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
32543
|
+
* the new format _as well as_ the old format.
|
|
32544
|
+
*/
|
|
32545
|
+
|
|
32535
32546
|
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
32536
|
-
// TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
|
|
32537
32547
|
|
|
32538
|
-
//
|
|
32539
|
-
|
|
32548
|
+
// Same name as Mafs
|
|
32549
|
+
|
|
32540
32550
|
/**
|
|
32541
32551
|
* Our core set of Perseus widgets.
|
|
32542
32552
|
*
|
|
@@ -32580,6 +32590,7 @@ const LegacyGrid = _ref => {
|
|
|
32580
32590
|
*
|
|
32581
32591
|
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
32582
32592
|
*/
|
|
32593
|
+
|
|
32583
32594
|
/**
|
|
32584
32595
|
* A map of widget IDs to widget options. This is most often used as the type
|
|
32585
32596
|
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
@@ -32592,6 +32603,7 @@ const LegacyGrid = _ref => {
|
|
|
32592
32603
|
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
32593
32604
|
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
32594
32605
|
*/
|
|
32606
|
+
|
|
32595
32607
|
/**
|
|
32596
32608
|
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
32597
32609
|
* `ServerItemRenderer` and the layout is pre-set.
|
|
@@ -32599,10 +32611,12 @@ const LegacyGrid = _ref => {
|
|
|
32599
32611
|
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
32600
32612
|
* area.
|
|
32601
32613
|
*/
|
|
32614
|
+
|
|
32602
32615
|
/**
|
|
32603
32616
|
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
32604
32617
|
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
32605
32618
|
*/
|
|
32619
|
+
|
|
32606
32620
|
const ItemExtras = [
|
|
32607
32621
|
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
32608
32622
|
"calculator",
|
|
@@ -34619,42 +34633,64 @@ function renderPointGraph(state, dispatch) {
|
|
|
34619
34633
|
};
|
|
34620
34634
|
}
|
|
34621
34635
|
function PointGraph(props) {
|
|
34622
|
-
const
|
|
34636
|
+
const {
|
|
34637
|
+
numPoints
|
|
34638
|
+
} = props.graphState;
|
|
34639
|
+
const graphConfig = useGraphConfig();
|
|
34640
|
+
const pointsRef = React__namespace.useRef([]);
|
|
34641
|
+
|
|
34642
|
+
// Dimensions to build the graph overlay for Unlimited Point.
|
|
34643
|
+
const {
|
|
34644
|
+
range: [x, y]
|
|
34645
|
+
} = graphConfig;
|
|
34646
|
+
const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
|
|
34647
|
+
|
|
34648
|
+
// This useEffect is to handle the focus snapping for Unlimited Point.
|
|
34649
|
+
React__namespace.useEffect(() => {
|
|
34650
|
+
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34651
|
+
if (focusedIndex != null) {
|
|
34652
|
+
pointsRef.current[focusedIndex]?.focus();
|
|
34653
|
+
}
|
|
34654
|
+
}, [props.graphState.focusedPointIndex, pointsRef]);
|
|
34655
|
+
const statefulProps = {
|
|
34656
|
+
...props,
|
|
34657
|
+
graphConfig,
|
|
34658
|
+
pointsRef,
|
|
34659
|
+
top,
|
|
34660
|
+
left
|
|
34661
|
+
};
|
|
34623
34662
|
if (numPoints === "unlimited") {
|
|
34624
|
-
return UnlimitedPointGraph(
|
|
34663
|
+
return UnlimitedPointGraph(statefulProps);
|
|
34625
34664
|
}
|
|
34626
|
-
return LimitedPointGraph(
|
|
34665
|
+
return LimitedPointGraph(statefulProps);
|
|
34627
34666
|
}
|
|
34628
|
-
function LimitedPointGraph(
|
|
34667
|
+
function LimitedPointGraph(statefulProps) {
|
|
34629
34668
|
const {
|
|
34630
34669
|
dispatch
|
|
34631
|
-
} =
|
|
34632
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null,
|
|
34670
|
+
} = statefulProps;
|
|
34671
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, statefulProps.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34633
34672
|
key: i,
|
|
34634
34673
|
point: point,
|
|
34635
34674
|
sequenceNumber: i + 1,
|
|
34636
34675
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
34637
34676
|
})));
|
|
34638
34677
|
}
|
|
34639
|
-
function UnlimitedPointGraph(
|
|
34678
|
+
function UnlimitedPointGraph(statefulProps) {
|
|
34640
34679
|
const {
|
|
34641
|
-
dispatch
|
|
34642
|
-
|
|
34643
|
-
|
|
34680
|
+
dispatch,
|
|
34681
|
+
graphConfig,
|
|
34682
|
+
pointsRef,
|
|
34683
|
+
top,
|
|
34684
|
+
left
|
|
34685
|
+
} = statefulProps;
|
|
34686
|
+
const {
|
|
34687
|
+
coords
|
|
34688
|
+
} = statefulProps.graphState;
|
|
34644
34689
|
const {
|
|
34645
|
-
|
|
34690
|
+
graphDimensionsInPixels
|
|
34646
34691
|
} = graphConfig;
|
|
34647
|
-
const
|
|
34648
|
-
const
|
|
34649
|
-
const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
|
|
34650
|
-
const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
|
|
34651
|
-
const itemsRef = React__namespace.useRef([]);
|
|
34652
|
-
React__namespace.useEffect(() => {
|
|
34653
|
-
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34654
|
-
if (focusedIndex != null) {
|
|
34655
|
-
itemsRef.current[focusedIndex]?.focus();
|
|
34656
|
-
}
|
|
34657
|
-
}, [props.graphState.focusedPointIndex, itemsRef]);
|
|
34692
|
+
const widthPx = graphDimensionsInPixels[0];
|
|
34693
|
+
const heightPx = graphDimensionsInPixels[1];
|
|
34658
34694
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
34659
34695
|
style: {
|
|
34660
34696
|
fill: "rgba(0,0,0,0)",
|
|
@@ -34671,13 +34707,13 @@ function UnlimitedPointGraph(props) {
|
|
|
34671
34707
|
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34672
34708
|
dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
|
|
34673
34709
|
}
|
|
34674
|
-
}),
|
|
34710
|
+
}), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34675
34711
|
key: i,
|
|
34676
34712
|
point: point,
|
|
34677
34713
|
sequenceNumber: i + 1,
|
|
34678
34714
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
34679
34715
|
ref: ref => {
|
|
34680
|
-
|
|
34716
|
+
pointsRef.current[i] = ref;
|
|
34681
34717
|
},
|
|
34682
34718
|
onFocus: () => {
|
|
34683
34719
|
dispatch(actions.pointGraph.focusPoint(i));
|
|
@@ -34728,36 +34764,42 @@ function renderPolygonGraph(state, dispatch) {
|
|
|
34728
34764
|
interactiveElementsDescription: null
|
|
34729
34765
|
};
|
|
34730
34766
|
}
|
|
34731
|
-
const
|
|
34732
|
-
const [hovered, setHovered] = React__namespace.useState(false);
|
|
34733
|
-
// This is more so required for the re-rendering that occurs when state
|
|
34734
|
-
// updates; specifically with regard to line weighting and polygon focus.
|
|
34735
|
-
const [focusVisible, setFocusVisible] = React__namespace.useState(false);
|
|
34767
|
+
const PolygonGraph = props => {
|
|
34736
34768
|
const {
|
|
34737
34769
|
dispatch
|
|
34738
34770
|
} = props;
|
|
34739
34771
|
const {
|
|
34772
|
+
numSides,
|
|
34740
34773
|
coords,
|
|
34741
|
-
showAngles,
|
|
34742
|
-
showSides,
|
|
34743
|
-
range,
|
|
34744
34774
|
snapStep,
|
|
34745
34775
|
snapTo = "grid"
|
|
34746
34776
|
} = props.graphState;
|
|
34777
|
+
const graphConfig = useGraphConfig();
|
|
34778
|
+
|
|
34779
|
+
// Ref to implement the dragging behavior on a Limited/Closed Polygon.
|
|
34780
|
+
const polygonRef = React__namespace.useRef(null);
|
|
34781
|
+
// Ref to manage dynamic focus for the points in Unlimited Polygon
|
|
34782
|
+
const pointsRef = React__namespace.useRef([]);
|
|
34783
|
+
// Ref to manage the last move time for a Limited Polygon.
|
|
34784
|
+
const lastMoveTimeRef = React__namespace.useRef(0);
|
|
34785
|
+
|
|
34786
|
+
// Dimensions to build the graph overlay for Unlimited Polgyon.
|
|
34747
34787
|
const {
|
|
34748
|
-
|
|
34749
|
-
} =
|
|
34788
|
+
range: [x, y]
|
|
34789
|
+
} = graphConfig;
|
|
34790
|
+
const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
|
|
34750
34791
|
|
|
34751
34792
|
// TODO(benchristel): can the default set of points be removed here? I don't
|
|
34752
34793
|
// think coords can be null.
|
|
34753
34794
|
const points = coords ?? [[0, 0]];
|
|
34754
|
-
|
|
34795
|
+
|
|
34796
|
+
// Logic to build the dragging experience. Primarily used by Limited Polygon.
|
|
34755
34797
|
const dragReferencePoint = points[0];
|
|
34756
34798
|
const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
|
|
34757
34799
|
const {
|
|
34758
34800
|
dragging
|
|
34759
34801
|
} = useDraggable({
|
|
34760
|
-
gestureTarget:
|
|
34802
|
+
gestureTarget: polygonRef,
|
|
34761
34803
|
point: dragReferencePoint,
|
|
34762
34804
|
onMove: newPoint => {
|
|
34763
34805
|
const delta = mafs.vec.sub(newPoint, dragReferencePoint);
|
|
@@ -34765,7 +34807,61 @@ const LimitedPolygonGraph = props => {
|
|
|
34765
34807
|
},
|
|
34766
34808
|
constrainKeyboardMovement: constrain
|
|
34767
34809
|
});
|
|
34768
|
-
|
|
34810
|
+
|
|
34811
|
+
// Hover/focus state for Limited Polgyon effects.
|
|
34812
|
+
const [hovered, setHovered] = React__namespace.useState(false);
|
|
34813
|
+
// This is more so required for the re-rendering that occurs when state
|
|
34814
|
+
// updates; specifically with regard to line weighting and polygon focus.
|
|
34815
|
+
const [focusVisible, setFocusVisible] = React__namespace.useState(false);
|
|
34816
|
+
|
|
34817
|
+
// This useEffect is to handle the focus snapping for Unlimited Polygon.
|
|
34818
|
+
React__namespace.useEffect(() => {
|
|
34819
|
+
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34820
|
+
if (focusedIndex != null) {
|
|
34821
|
+
pointsRef.current[focusedIndex]?.focus();
|
|
34822
|
+
}
|
|
34823
|
+
}, [props.graphState.focusedPointIndex, pointsRef]);
|
|
34824
|
+
const statefulProps = {
|
|
34825
|
+
...props,
|
|
34826
|
+
graphConfig,
|
|
34827
|
+
polygonRef,
|
|
34828
|
+
pointsRef,
|
|
34829
|
+
lastMoveTimeRef,
|
|
34830
|
+
left,
|
|
34831
|
+
top,
|
|
34832
|
+
dragging,
|
|
34833
|
+
points,
|
|
34834
|
+
constrain,
|
|
34835
|
+
hovered,
|
|
34836
|
+
setHovered,
|
|
34837
|
+
focusVisible,
|
|
34838
|
+
setFocusVisible
|
|
34839
|
+
};
|
|
34840
|
+
return numSides === "unlimited" ? UnlimitedPolygonGraph(statefulProps) : LimitedPolygonGraph(statefulProps);
|
|
34841
|
+
};
|
|
34842
|
+
const LimitedPolygonGraph = statefulProps => {
|
|
34843
|
+
const {
|
|
34844
|
+
dispatch,
|
|
34845
|
+
hovered,
|
|
34846
|
+
setHovered,
|
|
34847
|
+
focusVisible,
|
|
34848
|
+
setFocusVisible,
|
|
34849
|
+
graphConfig,
|
|
34850
|
+
polygonRef,
|
|
34851
|
+
lastMoveTimeRef,
|
|
34852
|
+
dragging,
|
|
34853
|
+
points,
|
|
34854
|
+
constrain
|
|
34855
|
+
} = statefulProps;
|
|
34856
|
+
const {
|
|
34857
|
+
showAngles,
|
|
34858
|
+
showSides,
|
|
34859
|
+
range,
|
|
34860
|
+
snapTo = "grid"
|
|
34861
|
+
} = statefulProps.graphState;
|
|
34862
|
+
const {
|
|
34863
|
+
disableKeyboardInteraction
|
|
34864
|
+
} = graphConfig;
|
|
34769
34865
|
const lines = getLines(points);
|
|
34770
34866
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
34771
34867
|
points: [...points],
|
|
@@ -34804,7 +34900,7 @@ const LimitedPolygonGraph = props => {
|
|
|
34804
34900
|
points: [...points],
|
|
34805
34901
|
color: "transparent",
|
|
34806
34902
|
svgPolygonProps: {
|
|
34807
|
-
ref,
|
|
34903
|
+
ref: polygonRef,
|
|
34808
34904
|
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
34809
34905
|
strokeWidth: TARGET_SIZE,
|
|
34810
34906
|
style: {
|
|
@@ -34816,13 +34912,13 @@ const LimitedPolygonGraph = props => {
|
|
|
34816
34912
|
// Required to remove line weighting when user clicks away
|
|
34817
34913
|
// from the focused polygon
|
|
34818
34914
|
onKeyDownCapture: () => {
|
|
34819
|
-
setFocusVisible(hasFocusVisible(
|
|
34915
|
+
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
34820
34916
|
},
|
|
34821
34917
|
// Required for lines to darken on focus
|
|
34822
|
-
onFocus: () => setFocusVisible(hasFocusVisible(
|
|
34918
|
+
onFocus: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
|
|
34823
34919
|
// Required for line weighting to update on blur. Without this,
|
|
34824
34920
|
// the user has to hover over the shape for it to update
|
|
34825
|
-
onBlur: () => setFocusVisible(hasFocusVisible(
|
|
34921
|
+
onBlur: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
|
|
34826
34922
|
className: "movable-polygon"
|
|
34827
34923
|
}
|
|
34828
34924
|
}), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
@@ -34834,94 +34930,86 @@ const LimitedPolygonGraph = props => {
|
|
|
34834
34930
|
const now = Date.now();
|
|
34835
34931
|
const targetFPS = 40;
|
|
34836
34932
|
const moveThresholdTime = 1000 / targetFPS;
|
|
34837
|
-
if (now -
|
|
34933
|
+
if (now - lastMoveTimeRef.current > moveThresholdTime) {
|
|
34838
34934
|
dispatch(actions.polygon.movePoint(i, destination));
|
|
34839
|
-
|
|
34935
|
+
lastMoveTimeRef.current = now;
|
|
34840
34936
|
}
|
|
34841
34937
|
}
|
|
34842
34938
|
})));
|
|
34843
34939
|
};
|
|
34844
|
-
const UnlimitedPolygonGraph =
|
|
34940
|
+
const UnlimitedPolygonGraph = statefulProps => {
|
|
34845
34941
|
const {
|
|
34846
|
-
dispatch
|
|
34847
|
-
|
|
34942
|
+
dispatch,
|
|
34943
|
+
graphConfig,
|
|
34944
|
+
left,
|
|
34945
|
+
top,
|
|
34946
|
+
pointsRef,
|
|
34947
|
+
points
|
|
34948
|
+
} = statefulProps;
|
|
34848
34949
|
const {
|
|
34849
34950
|
coords,
|
|
34850
34951
|
closedPolygon
|
|
34851
|
-
} =
|
|
34852
|
-
const graphConfig = useGraphConfig();
|
|
34853
|
-
const {
|
|
34854
|
-
range: [x, y],
|
|
34855
|
-
graphDimensionsInPixels
|
|
34856
|
-
} = graphConfig;
|
|
34857
|
-
const widthPx = graphDimensionsInPixels[0];
|
|
34858
|
-
const heightPx = graphDimensionsInPixels[1];
|
|
34859
|
-
const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
|
|
34860
|
-
const pointRef = React__namespace.useRef([]);
|
|
34952
|
+
} = statefulProps.graphState;
|
|
34861
34953
|
|
|
34862
|
-
//
|
|
34863
|
-
// think coords can be null.
|
|
34864
|
-
const points = coords ?? [[0, 0]];
|
|
34865
|
-
React__namespace.useEffect(() => {
|
|
34866
|
-
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34867
|
-
if (focusedIndex != null) {
|
|
34868
|
-
pointRef.current[focusedIndex]?.focus();
|
|
34869
|
-
}
|
|
34870
|
-
}, [props.graphState.focusedPointIndex, pointRef]);
|
|
34954
|
+
// If the polygon is closed, return a LimitedPolygon component.
|
|
34871
34955
|
if (closedPolygon) {
|
|
34872
34956
|
const closedPolygonProps = {
|
|
34873
|
-
...
|
|
34957
|
+
...statefulProps,
|
|
34874
34958
|
numSides: coords.length
|
|
34875
34959
|
};
|
|
34876
34960
|
return /*#__PURE__*/React__namespace.createElement(LimitedPolygonGraph, closedPolygonProps);
|
|
34877
|
-
}
|
|
34878
|
-
|
|
34961
|
+
}
|
|
34962
|
+
const {
|
|
34963
|
+
graphDimensionsInPixels
|
|
34964
|
+
} = graphConfig;
|
|
34965
|
+
const widthPx = graphDimensionsInPixels[0];
|
|
34966
|
+
const heightPx = graphDimensionsInPixels[1];
|
|
34967
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
34968
|
+
style: {
|
|
34969
|
+
fill: "rgba(0,0,0,0)",
|
|
34970
|
+
cursor: "crosshair"
|
|
34971
|
+
},
|
|
34972
|
+
width: widthPx,
|
|
34973
|
+
height: heightPx,
|
|
34974
|
+
x: left,
|
|
34975
|
+
y: top,
|
|
34976
|
+
onClick: event => {
|
|
34977
|
+
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34978
|
+
const x = event.clientX - elementRect.x;
|
|
34979
|
+
const y = event.clientY - elementRect.y;
|
|
34980
|
+
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34981
|
+
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34982
|
+
}
|
|
34983
|
+
}), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
|
|
34984
|
+
points: [...points],
|
|
34985
|
+
color: "var(--movable-line-stroke-color)",
|
|
34986
|
+
svgPolylineProps: {
|
|
34987
|
+
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
34879
34988
|
style: {
|
|
34880
|
-
fill: "
|
|
34881
|
-
cursor: "crosshair"
|
|
34882
|
-
},
|
|
34883
|
-
width: widthPx,
|
|
34884
|
-
height: heightPx,
|
|
34885
|
-
x: left,
|
|
34886
|
-
y: top,
|
|
34887
|
-
onClick: event => {
|
|
34888
|
-
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34889
|
-
const x = event.clientX - elementRect.x;
|
|
34890
|
-
const y = event.clientY - elementRect.y;
|
|
34891
|
-
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34892
|
-
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34893
|
-
}
|
|
34894
|
-
}), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
|
|
34895
|
-
points: [...points],
|
|
34896
|
-
color: "var(--movable-line-stroke-color)",
|
|
34897
|
-
svgPolylineProps: {
|
|
34898
|
-
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
34899
|
-
style: {
|
|
34900
|
-
fill: "transparent"
|
|
34901
|
-
}
|
|
34989
|
+
fill: "transparent"
|
|
34902
34990
|
}
|
|
34903
|
-
}
|
|
34904
|
-
|
|
34905
|
-
|
|
34906
|
-
|
|
34907
|
-
|
|
34908
|
-
|
|
34909
|
-
|
|
34910
|
-
|
|
34911
|
-
|
|
34912
|
-
|
|
34913
|
-
|
|
34914
|
-
|
|
34915
|
-
|
|
34916
|
-
|
|
34917
|
-
|
|
34918
|
-
|
|
34919
|
-
|
|
34920
|
-
|
|
34921
|
-
dispatch(actions.polygon.clickPoint(i));
|
|
34991
|
+
}
|
|
34992
|
+
}), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34993
|
+
key: i,
|
|
34994
|
+
point: point,
|
|
34995
|
+
sequenceNumber: i + 1,
|
|
34996
|
+
onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
|
|
34997
|
+
ref: ref => {
|
|
34998
|
+
pointsRef.current[i] = ref;
|
|
34999
|
+
},
|
|
35000
|
+
onFocus: () => {
|
|
35001
|
+
dispatch(actions.polygon.focusPoint(i));
|
|
35002
|
+
},
|
|
35003
|
+
onClick: () => {
|
|
35004
|
+
// If the point being clicked is the first point and
|
|
35005
|
+
// there's enough points to form a polygon (3 or more)
|
|
35006
|
+
// Close the shape before setting focus.
|
|
35007
|
+
if (i === 0 && coords.length >= 3) {
|
|
35008
|
+
dispatch(actions.polygon.closePolygon());
|
|
34922
35009
|
}
|
|
34923
|
-
|
|
34924
|
-
|
|
35010
|
+
dispatch(actions.polygon.clickPoint(i));
|
|
35011
|
+
}
|
|
35012
|
+
})));
|
|
34925
35013
|
};
|
|
34926
35014
|
function getLines(points) {
|
|
34927
35015
|
return points.map((point, i) => {
|
|
@@ -34933,17 +35021,13 @@ const hasFocusVisible = element => {
|
|
|
34933
35021
|
const matches = selector => element?.matches(selector) ?? false;
|
|
34934
35022
|
try {
|
|
34935
35023
|
return matches(":focus-visible");
|
|
34936
|
-
} catch
|
|
35024
|
+
} catch {
|
|
34937
35025
|
// jsdom doesn't support :focus-visible
|
|
34938
35026
|
// (see https://github.com/jsdom/jsdom/issues/3426),
|
|
34939
35027
|
// so the call to matches(":focus-visible") will fail in tests.
|
|
34940
35028
|
return matches(":focus");
|
|
34941
35029
|
}
|
|
34942
35030
|
};
|
|
34943
|
-
const PolygonGraph = props => {
|
|
34944
|
-
const numSides = props.graphState.numSides;
|
|
34945
|
-
return numSides === "unlimited" ? UnlimitedPolygonGraph(props) : LimitedPolygonGraph(props);
|
|
34946
|
-
};
|
|
34947
35031
|
|
|
34948
35032
|
function renderQuadraticGraph(state, dispatch) {
|
|
34949
35033
|
return {
|
|
@@ -40364,7 +40448,7 @@ class Sortable extends React__namespace.Component {
|
|
|
40364
40448
|
this.setState({
|
|
40365
40449
|
items
|
|
40366
40450
|
}, () => {
|
|
40367
|
-
this.props.onMeasure
|
|
40451
|
+
this.props.onMeasure?.({
|
|
40368
40452
|
widths: widths,
|
|
40369
40453
|
heights: heights
|
|
40370
40454
|
});
|
|
@@ -40414,7 +40498,7 @@ class Sortable extends React__namespace.Component {
|
|
|
40414
40498
|
this.setState({
|
|
40415
40499
|
items: nextItems
|
|
40416
40500
|
});
|
|
40417
|
-
this.props.onChange
|
|
40501
|
+
this.props.onChange?.({});
|
|
40418
40502
|
}
|
|
40419
40503
|
onMouseMove(key) {
|
|
40420
40504
|
// Dragging: Rearrange items based on draggable's position
|
|
@@ -40494,7 +40578,7 @@ class Sortable extends React__namespace.Component {
|
|
|
40494
40578
|
// HACK: We need to know *that* the widget changed, but currently it's
|
|
40495
40579
|
// not set up in a nice way to tell us *how* it changed, since the
|
|
40496
40580
|
// permutation of the items is stored in state.
|
|
40497
|
-
this.props.onChange
|
|
40581
|
+
this.props.onChange?.({});
|
|
40498
40582
|
});
|
|
40499
40583
|
|
|
40500
40584
|
// @ts-expect-error - TS2339 - Property 'animationFrameRequest' does not exist on type 'Sortable'.
|
|
@@ -43219,7 +43303,7 @@ class Card extends React__namespace.Component {
|
|
|
43219
43303
|
dragging: true
|
|
43220
43304
|
});
|
|
43221
43305
|
this.bindMouseMoveUp();
|
|
43222
|
-
this.props.onMouseDown
|
|
43306
|
+
this.props.onMouseDown?.(loc, this);
|
|
43223
43307
|
}
|
|
43224
43308
|
};
|
|
43225
43309
|
onMouseMove = event => {
|
|
@@ -43229,7 +43313,7 @@ class Card extends React__namespace.Component {
|
|
|
43229
43313
|
event.preventDefault();
|
|
43230
43314
|
const loc = Util.extractPointerLocation(event);
|
|
43231
43315
|
if (loc) {
|
|
43232
|
-
this.props.onMouseMove
|
|
43316
|
+
this.props.onMouseMove?.(loc);
|
|
43233
43317
|
}
|
|
43234
43318
|
};
|
|
43235
43319
|
onMouseUp = event => {
|
|
@@ -43240,7 +43324,7 @@ class Card extends React__namespace.Component {
|
|
|
43240
43324
|
dragging: false
|
|
43241
43325
|
});
|
|
43242
43326
|
this.unbindMouseMoveUp();
|
|
43243
|
-
this.props.onMouseUp
|
|
43327
|
+
this.props.onMouseUp?.(loc);
|
|
43244
43328
|
}
|
|
43245
43329
|
};
|
|
43246
43330
|
render() {
|
|
@@ -45446,7 +45530,7 @@ const init = function () {
|
|
|
45446
45530
|
|
|
45447
45531
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45448
45532
|
const libName = "@khanacademy/perseus";
|
|
45449
|
-
const libVersion = "
|
|
45533
|
+
const libVersion = "48.0.0";
|
|
45450
45534
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45451
45535
|
|
|
45452
45536
|
const apiVersion = {
|
|
@@ -46206,28 +46290,6 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
46206
46290
|
return this.questionRenderer.getUserInputMap();
|
|
46207
46291
|
}
|
|
46208
46292
|
|
|
46209
|
-
/**
|
|
46210
|
-
* Grades the item.
|
|
46211
|
-
*
|
|
46212
|
-
* @deprecated use scorePerseusItem
|
|
46213
|
-
*/
|
|
46214
|
-
scoreInput() {
|
|
46215
|
-
const guess = this.getUserInput();
|
|
46216
|
-
const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
|
|
46217
|
-
|
|
46218
|
-
// Continue to include an empty guess for the now defunct answer area.
|
|
46219
|
-
// TODO(alex): Check whether we rely on the format here for
|
|
46220
|
-
// analyzing ProblemLogs. If not, remove this layer.
|
|
46221
|
-
const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
|
|
46222
|
-
const keScore = keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
46223
|
-
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
46224
|
-
this.setState({
|
|
46225
|
-
questionCompleted: keScore.correct,
|
|
46226
|
-
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
46227
|
-
});
|
|
46228
|
-
return keScore;
|
|
46229
|
-
}
|
|
46230
|
-
|
|
46231
46293
|
/**
|
|
46232
46294
|
* Returns an array of all widget IDs in the order they occur in
|
|
46233
46295
|
* the question content.
|