@khanacademy/perseus 8.0.0 → 10.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/CHANGELOG.md +33 -0
- package/dist/article-renderer.d.ts +2 -1
- package/dist/components/info-tip.d.ts +2 -1
- package/dist/components/input-with-examples.d.ts +1 -1
- package/dist/components/sortable.d.ts +1 -1
- package/dist/dependencies.d.ts +7 -1
- package/dist/es/index.css +3 -5
- package/dist/es/index.js +72 -45
- package/dist/es/index.js.map +1 -1
- package/dist/hint-renderer.d.ts +2 -1
- package/dist/hints-renderer.d.ts +2 -1
- package/dist/index.css +3 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +72 -46
- package/dist/index.js.map +1 -1
- package/dist/interaction-tracker.d.ts +3 -3
- package/dist/item-renderer.d.ts +3 -1
- package/dist/multi-items/multi-renderer.d.ts +4 -2
- package/dist/perseus-api.d.ts +1 -1
- package/dist/renderer.d.ts +4 -9
- package/dist/server-item-renderer.d.ts +3 -1
- package/dist/types.d.ts +30 -15
- package/dist/widget-container.d.ts +2 -1
- package/dist/widgets/expression.d.ts +409 -12
- package/dist/widgets/graded-group.d.ts +2 -2
- package/dist/widgets/grapher.d.ts +2 -1
- package/dist/widgets/passage-ref.d.ts +2 -1
- package/dist/widgets/sequence.d.ts +2 -2
- package/package.json +3 -3
- package/src/__stories__/article-renderer.stories.tsx +35 -2
- package/src/__testdata__/article-renderer.testdata.ts +71 -0
- package/src/__tests__/item-renderer.test.tsx +12 -1
- package/src/__tests__/renderer.test.tsx +0 -2
- package/src/__tests__/server-item-renderer.test.tsx +11 -5
- package/src/article-renderer.tsx +38 -30
- package/src/components/info-tip.tsx +3 -4
- package/src/components/input-with-examples.tsx +1 -1
- package/src/components/sortable.tsx +1 -1
- package/src/dependencies.ts +16 -1
- package/src/hint-renderer.tsx +2 -1
- package/src/hints-renderer.tsx +2 -9
- package/src/index.ts +0 -1
- package/src/interaction-tracker.ts +3 -3
- package/src/item-renderer.tsx +57 -44
- package/src/multi-items/__tests__/multi-renderer.test.tsx +6 -1
- package/src/multi-items/multi-renderer.tsx +18 -11
- package/src/renderer.tsx +4 -4
- package/src/server-item-renderer.tsx +21 -17
- package/src/types.ts +50 -21
- package/src/widget-container.tsx +2 -1
- package/src/widgets/__stories__/definition.stories.tsx +8 -1
- package/src/widgets/__stories__/expression.stories.tsx +2 -2
- package/src/widgets/__tests__/{expression.test.ts → expression.test.tsx} +73 -8
- package/src/widgets/__tests__/renderQuestion.tsx +29 -29
- package/src/widgets/expression.tsx +77 -48
- package/src/widgets/graded-group.tsx +11 -2
- package/src/widgets/grapher.tsx +4 -12
- package/src/widgets/passage-ref.tsx +1 -1
- package/src/widgets/sequence.tsx +10 -2
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -12,7 +12,7 @@ import Tooltip, {
|
|
|
12
12
|
HorizontalDirection,
|
|
13
13
|
VerticalDirection,
|
|
14
14
|
} from "../components/tooltip";
|
|
15
|
-
import {
|
|
15
|
+
import {useDependencies} from "../dependencies";
|
|
16
16
|
import {iconExclamationSign} from "../icon-paths";
|
|
17
17
|
import {Errors as PerseusErrors, Log} from "../logging/log";
|
|
18
18
|
import * as Changeable from "../mixins/changeable";
|
|
@@ -23,19 +23,14 @@ import type {
|
|
|
23
23
|
PerseusExpressionWidgetOptions,
|
|
24
24
|
PerseusExpressionAnswerForm,
|
|
25
25
|
} from "../perseus-types";
|
|
26
|
-
import type {
|
|
26
|
+
import type {
|
|
27
|
+
APIOptions,
|
|
28
|
+
PerseusScore,
|
|
29
|
+
WidgetExports,
|
|
30
|
+
WidgetProps,
|
|
31
|
+
} from "../types";
|
|
27
32
|
import type {Keys as Key} from "@khanacademy/math-input";
|
|
28
|
-
|
|
29
|
-
const sendExpressionEvaluatedEvent = (
|
|
30
|
-
result: "correct" | "incorrect" | "invalid",
|
|
31
|
-
) => {
|
|
32
|
-
getDependencies().analytics({
|
|
33
|
-
type: "perseus:expression-evaluated",
|
|
34
|
-
payload: {
|
|
35
|
-
result,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
};
|
|
33
|
+
import type {PropsFor} from "@khanacademy/wonder-blocks-core";
|
|
39
34
|
|
|
40
35
|
type InputPath = ReadonlyArray<string>;
|
|
41
36
|
|
|
@@ -63,20 +58,43 @@ const insertBraces = (value) => {
|
|
|
63
58
|
return value.replace(/([_^])([^{])/g, "$1{$2}");
|
|
64
59
|
};
|
|
65
60
|
|
|
61
|
+
const deriveKeypadVersion = (apiOptions: APIOptions) => {
|
|
62
|
+
// We can derive which version of the keypad is in use. This is
|
|
63
|
+
// a bit tricky, but this code will be relatively short-lived
|
|
64
|
+
// as we coalesce onto the new, v2 Keypad, at which point we
|
|
65
|
+
// can remove this `virtualKeypadVersion` field entirely.
|
|
66
|
+
return apiOptions.nativeKeypadProxy != null
|
|
67
|
+
? "REACT_NATIVE_KEYPAD"
|
|
68
|
+
: apiOptions.customKeypad === true
|
|
69
|
+
? apiOptions.useV2Keypad === true
|
|
70
|
+
? "MATH_INPUT_KEYPAD_V2"
|
|
71
|
+
: "MATH_INPUT_KEYPAD_V1"
|
|
72
|
+
: "PERSEUS_MATH_INPUT";
|
|
73
|
+
};
|
|
74
|
+
|
|
66
75
|
type Rubric = PerseusExpressionWidgetOptions;
|
|
67
76
|
|
|
77
|
+
type RenderProps = {
|
|
78
|
+
buttonSets: PerseusExpressionWidgetOptions["buttonSets"];
|
|
79
|
+
buttonsVisible?: PerseusExpressionWidgetOptions["buttonsVisible"];
|
|
80
|
+
functions: PerseusExpressionWidgetOptions["functions"];
|
|
81
|
+
times: PerseusExpressionWidgetOptions["times"];
|
|
82
|
+
keypadConfiguration: ReturnType<typeof keypadConfigurationForProps>;
|
|
83
|
+
};
|
|
84
|
+
|
|
68
85
|
type ExternalProps = WidgetProps<RenderProps, Rubric>;
|
|
69
86
|
|
|
70
|
-
export type Props = ExternalProps &
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
export type Props = ExternalProps &
|
|
88
|
+
ReturnType<typeof useDependencies> & {
|
|
89
|
+
apiOptions: NonNullable<ExternalProps["apiOptions"]>;
|
|
90
|
+
buttonSets: NonNullable<ExternalProps["buttonSets"]>;
|
|
91
|
+
functions: NonNullable<ExternalProps["functions"]>;
|
|
92
|
+
linterContext: NonNullable<ExternalProps["linterContext"]>;
|
|
93
|
+
onBlur: NonNullable<ExternalProps["onBlur"]>;
|
|
94
|
+
onFocus: NonNullable<ExternalProps["onFocus"]>;
|
|
95
|
+
times: NonNullable<ExternalProps["times"]>;
|
|
96
|
+
value: string;
|
|
97
|
+
};
|
|
80
98
|
|
|
81
99
|
export type ExpressionState = {
|
|
82
100
|
showErrorTooltip: boolean;
|
|
@@ -211,8 +229,6 @@ export class Expression extends React.Component<Props, ExpressionState> {
|
|
|
211
229
|
// we did, whether it's considered correct, incorrect, or ungraded
|
|
212
230
|
if (!matchingAnswerForm) {
|
|
213
231
|
if (firstUngradedResult) {
|
|
214
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
215
|
-
|
|
216
232
|
// While we didn't directly match with any answer form, we
|
|
217
233
|
// did at some point get an "ungraded" validation result,
|
|
218
234
|
// which might indicate e.g. a mismatch in variable casing.
|
|
@@ -227,8 +243,6 @@ export class Expression extends React.Component<Props, ExpressionState> {
|
|
|
227
243
|
};
|
|
228
244
|
}
|
|
229
245
|
if (allEmpty) {
|
|
230
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
231
|
-
|
|
232
246
|
// If everything graded as empty, it's invalid.
|
|
233
247
|
return {
|
|
234
248
|
type: "invalid",
|
|
@@ -237,7 +251,6 @@ export class Expression extends React.Component<Props, ExpressionState> {
|
|
|
237
251
|
}
|
|
238
252
|
// We fell through all the possibilities and we're not empty,
|
|
239
253
|
// so the answer is considered incorrect.
|
|
240
|
-
sendExpressionEvaluatedEvent("incorrect");
|
|
241
254
|
return {
|
|
242
255
|
type: "points",
|
|
243
256
|
earned: 0,
|
|
@@ -252,7 +265,6 @@ export class Expression extends React.Component<Props, ExpressionState> {
|
|
|
252
265
|
userInput,
|
|
253
266
|
matchMessage,
|
|
254
267
|
);
|
|
255
|
-
sendExpressionEvaluatedEvent("invalid");
|
|
256
268
|
return {
|
|
257
269
|
type: "invalid",
|
|
258
270
|
message: apiResult === false ? null : matchMessage,
|
|
@@ -264,12 +276,6 @@ export class Expression extends React.Component<Props, ExpressionState> {
|
|
|
264
276
|
// TODO(eater): Seems silly to translate result to this
|
|
265
277
|
// invalid/points thing and immediately translate it back in
|
|
266
278
|
// ItemRenderer.scoreInput()
|
|
267
|
-
sendExpressionEvaluatedEvent(
|
|
268
|
-
matchingAnswerForm.considered === "correct"
|
|
269
|
-
? "correct"
|
|
270
|
-
: "incorrect",
|
|
271
|
-
);
|
|
272
|
-
|
|
273
279
|
return {
|
|
274
280
|
type: "points",
|
|
275
281
|
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
@@ -369,7 +375,21 @@ export class Expression extends React.Component<Props, ExpressionState> {
|
|
|
369
375
|
onInputError: OnInputErrorFunctionType,
|
|
370
376
|
) => PerseusScore = (rubric, onInputError) => {
|
|
371
377
|
onInputError = onInputError || function () {};
|
|
372
|
-
|
|
378
|
+
const result = Expression.validate(
|
|
379
|
+
this.getUserInput(),
|
|
380
|
+
rubric,
|
|
381
|
+
onInputError,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
this.sendExpressionEvaluatedEvent(
|
|
385
|
+
result.type === "invalid"
|
|
386
|
+
? "invalid"
|
|
387
|
+
: result.earned === result.total
|
|
388
|
+
? "correct"
|
|
389
|
+
: "incorrect",
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
return result;
|
|
373
393
|
};
|
|
374
394
|
|
|
375
395
|
getUserInput: () => string = () => {
|
|
@@ -471,6 +491,18 @@ export class Expression extends React.Component<Props, ExpressionState> {
|
|
|
471
491
|
);
|
|
472
492
|
};
|
|
473
493
|
|
|
494
|
+
sendExpressionEvaluatedEvent(result: "correct" | "incorrect" | "invalid") {
|
|
495
|
+
this.props.analytics.onAnalyticsEvent({
|
|
496
|
+
type: "perseus:expression-evaluated",
|
|
497
|
+
payload: {
|
|
498
|
+
result,
|
|
499
|
+
virtualKeypadVersion: deriveKeypadVersion(
|
|
500
|
+
this.props.apiOptions,
|
|
501
|
+
),
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
474
506
|
render():
|
|
475
507
|
| React.ReactNode
|
|
476
508
|
| React.ReactElement<React.ComponentProps<"div">> {
|
|
@@ -640,22 +672,19 @@ const propUpgrades = {
|
|
|
640
672
|
}),
|
|
641
673
|
} as const;
|
|
642
674
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
};
|
|
651
|
-
times: boolean;
|
|
652
|
-
};
|
|
675
|
+
const ExpressionWithDependencies = React.forwardRef<
|
|
676
|
+
Expression,
|
|
677
|
+
Omit<PropsFor<typeof Expression>, keyof ReturnType<typeof useDependencies>>
|
|
678
|
+
>((props, ref) => {
|
|
679
|
+
const deps = useDependencies();
|
|
680
|
+
return <Expression ref={ref} analytics={deps.analytics} {...props} />;
|
|
681
|
+
});
|
|
653
682
|
|
|
654
683
|
export default {
|
|
655
684
|
name: "expression",
|
|
656
685
|
displayName: "Expression / Equation",
|
|
657
686
|
defaultAlignment: "inline-block",
|
|
658
|
-
widget:
|
|
687
|
+
widget: ExpressionWithDependencies,
|
|
659
688
|
transform: (widgetOptions: PerseusExpressionWidgetOptions): RenderProps => {
|
|
660
689
|
const {times, functions, buttonSets, buttonsVisible} = widgetOptions;
|
|
661
690
|
return {
|
|
@@ -671,4 +700,4 @@ export default {
|
|
|
671
700
|
|
|
672
701
|
// For use by the editor
|
|
673
702
|
isLintable: true,
|
|
674
|
-
} as WidgetExports<typeof
|
|
703
|
+
} as WidgetExports<typeof ExpressionWithDependencies>;
|
|
@@ -25,7 +25,12 @@ import a11y from "../util/a11y";
|
|
|
25
25
|
import GradedGroupAnswerBar from "./graded-group-answer-bar";
|
|
26
26
|
|
|
27
27
|
import type {PerseusGradedGroupWidgetOptions} from "../perseus-types";
|
|
28
|
-
import type {
|
|
28
|
+
import type {
|
|
29
|
+
PerseusScore,
|
|
30
|
+
TrackingGradedGroupExtraArguments,
|
|
31
|
+
WidgetExports,
|
|
32
|
+
WidgetProps,
|
|
33
|
+
} from "../types";
|
|
29
34
|
import type {ANSWER_BAR_STATES} from "./graded-group-answer-bar";
|
|
30
35
|
|
|
31
36
|
const GRADING_STATUSES = {
|
|
@@ -64,7 +69,11 @@ const DEFAULT_INVALID_MESSAGE =
|
|
|
64
69
|
type Rubric = PerseusGradedGroupWidgetOptions;
|
|
65
70
|
type RenderProps = PerseusGradedGroupWidgetOptions; // exports has no 'transform'
|
|
66
71
|
|
|
67
|
-
type Props = WidgetProps<
|
|
72
|
+
type Props = WidgetProps<
|
|
73
|
+
RenderProps,
|
|
74
|
+
Rubric,
|
|
75
|
+
TrackingGradedGroupExtraArguments
|
|
76
|
+
> & {
|
|
68
77
|
inGradedGroupSet?: boolean; // Set by graded-group-set.jsx,
|
|
69
78
|
onNextQuestion?: () => unknown; // Set by graded-group-set.jsx
|
|
70
79
|
};
|
package/src/widgets/grapher.tsx
CHANGED
|
@@ -36,6 +36,7 @@ import type {ChangeableProps} from "../mixins/changeable";
|
|
|
36
36
|
import type {PerseusGrapherWidgetOptions} from "../perseus-types";
|
|
37
37
|
import type {PerseusScore, WidgetExports, WidgetProps} from "../types";
|
|
38
38
|
import type {GridDimensions} from "../util";
|
|
39
|
+
import type {PropsFor} from "@khanacademy/wonder-blocks-core";
|
|
39
40
|
|
|
40
41
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
41
42
|
const MovablePoint = Graphie.MovablePoint;
|
|
@@ -473,18 +474,9 @@ class Grapher extends React.Component<Props> {
|
|
|
473
474
|
}
|
|
474
475
|
|
|
475
476
|
_calculateMobileTickStep(
|
|
476
|
-
gridStep:
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
>["gridStep"],
|
|
480
|
-
step: JSX.LibraryManagedAttributes<
|
|
481
|
-
typeof Graphie,
|
|
482
|
-
Required<React.ComponentProps<typeof Graphie>>
|
|
483
|
-
>["step"],
|
|
484
|
-
ranges: JSX.LibraryManagedAttributes<
|
|
485
|
-
typeof Graphie,
|
|
486
|
-
Required<React.ComponentProps<typeof Graphie>>
|
|
487
|
-
>["ranges"],
|
|
477
|
+
gridStep: NonNullable<PropsFor<typeof Graphie>["gridStep"]>,
|
|
478
|
+
step: NonNullable<PropsFor<typeof Graphie>["step"]>,
|
|
479
|
+
ranges: NonNullable<PropsFor<typeof Graphie>["ranges"]>,
|
|
488
480
|
): any {
|
|
489
481
|
const tickStep = Util.constrainedTickStepsFromTickSteps(step, ranges);
|
|
490
482
|
|
|
@@ -13,9 +13,9 @@ import type {
|
|
|
13
13
|
PerseusScore,
|
|
14
14
|
WidgetExports,
|
|
15
15
|
WidgetProps,
|
|
16
|
-
LinterContextProps,
|
|
17
16
|
} from "../types";
|
|
18
17
|
import type {Passage, Reference} from "./passage";
|
|
18
|
+
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
19
19
|
|
|
20
20
|
const EN_DASH = "\u2013";
|
|
21
21
|
|
package/src/widgets/sequence.tsx
CHANGED
|
@@ -9,11 +9,19 @@ import Renderer from "../renderer";
|
|
|
9
9
|
import Util from "../util";
|
|
10
10
|
|
|
11
11
|
import type {PerseusSequenceWidgetOptions} from "../perseus-types";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
TrackingSequenceExtraArguments,
|
|
14
|
+
WidgetExports,
|
|
15
|
+
WidgetProps,
|
|
16
|
+
} from "../types";
|
|
13
17
|
|
|
14
18
|
type Rubric = PerseusSequenceWidgetOptions;
|
|
15
19
|
|
|
16
|
-
type ExternalProps = WidgetProps<
|
|
20
|
+
type ExternalProps = WidgetProps<
|
|
21
|
+
PerseusSequenceWidgetOptions,
|
|
22
|
+
Rubric,
|
|
23
|
+
TrackingSequenceExtraArguments
|
|
24
|
+
>;
|
|
17
25
|
|
|
18
26
|
type Props = ExternalProps;
|
|
19
27
|
|