@khanacademy/perseus 10.1.0 → 11.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 +12 -0
- package/dist/article-renderer.d.ts +5 -7
- package/dist/es/index.js +81 -89
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +81 -89
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +2 -2
- package/dist/server-item-renderer.d.ts +5 -4
- package/dist/types.d.ts +2 -1
- package/dist/widgets/expression.d.ts +12 -12
- package/package.json +5 -4
- package/src/__stories__/article-renderer.stories.tsx +22 -0
- package/src/__testdata__/article-renderer.testdata.ts +28 -0
- package/src/__tests__/server-item-renderer.test.tsx +12 -4
- package/src/article-renderer.tsx +14 -27
- package/src/server-item-renderer.tsx +10 -7
- package/src/types.ts +2 -4
- package/src/widgets/__stories__/expression.stories.tsx +3 -3
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/perseus-api.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export declare const ApiOptions: {
|
|
|
65
65
|
readonly defaults: Readonly<Readonly<{
|
|
66
66
|
isArticle?: boolean | undefined;
|
|
67
67
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
68
|
-
onFocusChange?: ((newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath, keypadElement?:
|
|
68
|
+
onFocusChange?: ((newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
69
69
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
70
70
|
showAlignmentOptions?: boolean | undefined;
|
|
71
71
|
readOnly?: boolean | undefined;
|
|
@@ -108,7 +108,7 @@ export declare const ApiOptions: {
|
|
|
108
108
|
isArticle: NonNullable<boolean | undefined>;
|
|
109
109
|
isMobile: NonNullable<boolean | undefined>;
|
|
110
110
|
useV2Keypad: NonNullable<boolean | undefined>;
|
|
111
|
-
onFocusChange: (newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath, keypadElement?:
|
|
111
|
+
onFocusChange: (newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
112
112
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
113
113
|
readOnly: NonNullable<boolean | undefined>;
|
|
114
114
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -10,7 +10,8 @@ import * as React from "react";
|
|
|
10
10
|
import Renderer from "./renderer";
|
|
11
11
|
import type { KeypadProps } from "./mixins/provide-keypad";
|
|
12
12
|
import type { APIOptions, FocusPath, PerseusDependenciesV2 } from "./types";
|
|
13
|
-
import type {
|
|
13
|
+
import type { KeypadAPI } from "@khanacademy/math-input";
|
|
14
|
+
import type { KeypadContextRendererInterface, RendererInterface, KEScore } from "@khanacademy/perseus-core";
|
|
14
15
|
type OwnProps = // These props are used by the ProvideKeypad mixin.
|
|
15
16
|
KeypadProps & {
|
|
16
17
|
apiOptions: APIOptions;
|
|
@@ -21,7 +22,7 @@ KeypadProps & {
|
|
|
21
22
|
};
|
|
22
23
|
problemNum?: number;
|
|
23
24
|
reviewMode?: boolean;
|
|
24
|
-
keypadElement?:
|
|
25
|
+
keypadElement?: KeypadAPI | null | undefined;
|
|
25
26
|
dependencies: PerseusDependenciesV2;
|
|
26
27
|
};
|
|
27
28
|
type HOCProps = {
|
|
@@ -43,7 +44,7 @@ type SerializedState = {
|
|
|
43
44
|
question: any;
|
|
44
45
|
hints: any;
|
|
45
46
|
};
|
|
46
|
-
export declare class ServerItemRenderer extends React.Component<Props, State> implements RendererInterface {
|
|
47
|
+
export declare class ServerItemRenderer extends React.Component<Props, State> implements RendererInterface, KeypadContextRendererInterface {
|
|
47
48
|
questionRenderer: Renderer;
|
|
48
49
|
hintsRenderer: any;
|
|
49
50
|
_currentFocus: FocusPath;
|
|
@@ -110,7 +111,7 @@ declare const ref: React.ForwardRefExoticComponent<Omit<KeypadProps & {
|
|
|
110
111
|
};
|
|
111
112
|
problemNum?: number | undefined;
|
|
112
113
|
reviewMode?: boolean | undefined;
|
|
113
|
-
keypadElement?:
|
|
114
|
+
keypadElement?: KeypadAPI | null | undefined;
|
|
114
115
|
dependencies: PerseusDependenciesV2;
|
|
115
116
|
} & HOCProps, "onRendered"> & React.RefAttributes<ServerItemRenderer>>;
|
|
116
117
|
export default ref;
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { AnalyticsEventHandlerFn } from "@khanacademy/perseus-core";
|
|
|
8
8
|
import type { LinterContextProps } from "@khanacademy/perseus-linter";
|
|
9
9
|
import type { Result } from "@khanacademy/wonder-blocks-data";
|
|
10
10
|
import type * as React from "react";
|
|
11
|
+
import type ReactDOM from "react-dom";
|
|
11
12
|
export type FocusPath = ReadonlyArray<string> | null | undefined;
|
|
12
13
|
type Empty = Record<never, never>;
|
|
13
14
|
export type Dimensions = {
|
|
@@ -85,7 +86,7 @@ type TrackInteractionArgs = {
|
|
|
85
86
|
export type APIOptions = Readonly<{
|
|
86
87
|
isArticle?: boolean;
|
|
87
88
|
onInputError?: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
88
|
-
onFocusChange?: (newFocusPath: FocusPath, oldFocusPath: FocusPath, keypadElement?:
|
|
89
|
+
onFocusChange?: (newFocusPath: FocusPath, oldFocusPath: FocusPath, keypadElement?: ReturnType<typeof ReactDOM.findDOMNode>, focusedElement?: HTMLElement) => unknown;
|
|
89
90
|
GroupMetadataEditor?: React.ComponentType<StubTagEditorType>;
|
|
90
91
|
showAlignmentOptions?: boolean;
|
|
91
92
|
readOnly?: boolean;
|
|
@@ -94,7 +94,7 @@ declare const _default: Readonly<{
|
|
|
94
94
|
apiOptions: Readonly<Readonly<{
|
|
95
95
|
isArticle?: boolean | undefined;
|
|
96
96
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
97
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
97
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
98
98
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
99
99
|
showAlignmentOptions?: boolean | undefined;
|
|
100
100
|
readOnly?: boolean | undefined;
|
|
@@ -137,7 +137,7 @@ declare const _default: Readonly<{
|
|
|
137
137
|
isArticle: NonNullable<boolean | undefined>;
|
|
138
138
|
isMobile: NonNullable<boolean | undefined>;
|
|
139
139
|
useV2Keypad: NonNullable<boolean | undefined>;
|
|
140
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
140
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
141
141
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
142
142
|
readOnly: NonNullable<boolean | undefined>;
|
|
143
143
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -159,7 +159,7 @@ declare const _default: Readonly<{
|
|
|
159
159
|
apiOptions: Readonly<Readonly<{
|
|
160
160
|
isArticle?: boolean | undefined;
|
|
161
161
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
162
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
162
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
163
163
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
164
164
|
showAlignmentOptions?: boolean | undefined;
|
|
165
165
|
readOnly?: boolean | undefined;
|
|
@@ -202,7 +202,7 @@ declare const _default: Readonly<{
|
|
|
202
202
|
isArticle: NonNullable<boolean | undefined>;
|
|
203
203
|
isMobile: NonNullable<boolean | undefined>;
|
|
204
204
|
useV2Keypad: NonNullable<boolean | undefined>;
|
|
205
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
205
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
206
206
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
207
207
|
readOnly: NonNullable<boolean | undefined>;
|
|
208
208
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -224,7 +224,7 @@ declare const _default: Readonly<{
|
|
|
224
224
|
apiOptions?: Readonly<Readonly<{
|
|
225
225
|
isArticle?: boolean | undefined;
|
|
226
226
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
227
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
227
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
228
228
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
229
229
|
showAlignmentOptions?: boolean | undefined;
|
|
230
230
|
readOnly?: boolean | undefined;
|
|
@@ -267,7 +267,7 @@ declare const _default: Readonly<{
|
|
|
267
267
|
isArticle: NonNullable<boolean | undefined>;
|
|
268
268
|
isMobile: NonNullable<boolean | undefined>;
|
|
269
269
|
useV2Keypad: NonNullable<boolean | undefined>;
|
|
270
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
270
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
271
271
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
272
272
|
readOnly: NonNullable<boolean | undefined>;
|
|
273
273
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -300,7 +300,7 @@ declare const _default: Readonly<{
|
|
|
300
300
|
apiOptions: Readonly<Readonly<{
|
|
301
301
|
isArticle?: boolean | undefined;
|
|
302
302
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
303
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
303
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
304
304
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
305
305
|
showAlignmentOptions?: boolean | undefined;
|
|
306
306
|
readOnly?: boolean | undefined;
|
|
@@ -343,7 +343,7 @@ declare const _default: Readonly<{
|
|
|
343
343
|
isArticle: NonNullable<boolean | undefined>;
|
|
344
344
|
isMobile: NonNullable<boolean | undefined>;
|
|
345
345
|
useV2Keypad: NonNullable<boolean | undefined>;
|
|
346
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
346
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
347
347
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
348
348
|
readOnly: NonNullable<boolean | undefined>;
|
|
349
349
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -365,7 +365,7 @@ declare const _default: Readonly<{
|
|
|
365
365
|
apiOptions: Readonly<Readonly<{
|
|
366
366
|
isArticle?: boolean | undefined;
|
|
367
367
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
368
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
368
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
369
369
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
370
370
|
showAlignmentOptions?: boolean | undefined;
|
|
371
371
|
readOnly?: boolean | undefined;
|
|
@@ -408,7 +408,7 @@ declare const _default: Readonly<{
|
|
|
408
408
|
isArticle: NonNullable<boolean | undefined>;
|
|
409
409
|
isMobile: NonNullable<boolean | undefined>;
|
|
410
410
|
useV2Keypad: NonNullable<boolean | undefined>;
|
|
411
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
411
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
412
412
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
413
413
|
readOnly: NonNullable<boolean | undefined>;
|
|
414
414
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -430,7 +430,7 @@ declare const _default: Readonly<{
|
|
|
430
430
|
apiOptions?: Readonly<Readonly<{
|
|
431
431
|
isArticle?: boolean | undefined;
|
|
432
432
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
433
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
433
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
434
434
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
435
435
|
showAlignmentOptions?: boolean | undefined;
|
|
436
436
|
readOnly?: boolean | undefined;
|
|
@@ -473,7 +473,7 @@ declare const _default: Readonly<{
|
|
|
473
473
|
isArticle: NonNullable<boolean | undefined>;
|
|
474
474
|
isMobile: NonNullable<boolean | undefined>;
|
|
475
475
|
useV2Keypad: NonNullable<boolean | undefined>;
|
|
476
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?:
|
|
476
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
477
477
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
478
478
|
readOnly: NonNullable<boolean | undefined>;
|
|
479
479
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
package/package.json
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
"description": "Core Perseus API (includes renderers and widgets)",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "11.0.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/Khan/perseus.git"
|
|
12
|
+
"url": "https://github.com/Khan/perseus.git",
|
|
13
|
+
"directory": "packages/perseus"
|
|
13
14
|
},
|
|
14
15
|
"bugs": {
|
|
15
16
|
"url": "https://github.com/Khan/perseus/issues"
|
|
@@ -23,8 +24,8 @@
|
|
|
23
24
|
"dependencies": {
|
|
24
25
|
"@khanacademy/kas": "^0.3.1",
|
|
25
26
|
"@khanacademy/kmath": "^0.1.2",
|
|
26
|
-
"@khanacademy/math-input": "^
|
|
27
|
-
"@khanacademy/perseus-core": "0.
|
|
27
|
+
"@khanacademy/math-input": "^11.0.0",
|
|
28
|
+
"@khanacademy/perseus-core": "1.0.0",
|
|
28
29
|
"@khanacademy/perseus-linter": "^0.3.2",
|
|
29
30
|
"@khanacademy/pure-markdown": "^0.2.4",
|
|
30
31
|
"@khanacademy/simple-markdown": "^0.10.0"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {KeypadContext} from "@khanacademy/math-input";
|
|
1
2
|
import React from "react";
|
|
2
3
|
|
|
3
4
|
import {storybookDependenciesV2} from "../../../../testing/test-dependencies";
|
|
@@ -5,8 +6,10 @@ import {
|
|
|
5
6
|
singleSectionArticle,
|
|
6
7
|
multiSectionArticle,
|
|
7
8
|
passageArticle,
|
|
9
|
+
articleWithExpression,
|
|
8
10
|
} from "../__testdata__/article-renderer.testdata";
|
|
9
11
|
import ArticleRenderer from "../article-renderer";
|
|
12
|
+
import TestKeypadContextWrapper from "../widgets/__stories__/test-keypad-context-wrapper";
|
|
10
13
|
|
|
11
14
|
export default {
|
|
12
15
|
title: "Perseus/Renderers/Article Renderer",
|
|
@@ -48,3 +51,22 @@ export const PassageArticle = ({useNewStyles}): any => (
|
|
|
48
51
|
useNewStyles={useNewStyles}
|
|
49
52
|
/>
|
|
50
53
|
);
|
|
54
|
+
|
|
55
|
+
export const ExpressionArticle = ({useNewStyles}): any => (
|
|
56
|
+
<TestKeypadContextWrapper>
|
|
57
|
+
<KeypadContext.Consumer>
|
|
58
|
+
{({keypadElement, setRenderer, scrollableElement}) => (
|
|
59
|
+
<ArticleRenderer
|
|
60
|
+
ref={(node) => {
|
|
61
|
+
setRenderer(node);
|
|
62
|
+
}}
|
|
63
|
+
json={articleWithExpression}
|
|
64
|
+
dependencies={storybookDependenciesV2}
|
|
65
|
+
useNewStyles={useNewStyles}
|
|
66
|
+
apiOptions={{isMobile: true, customKeypad: true}}
|
|
67
|
+
keypadElement={keypadElement}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
</KeypadContext.Consumer>
|
|
71
|
+
</TestKeypadContextWrapper>
|
|
72
|
+
);
|
|
@@ -58,6 +58,34 @@ export const passageArticle: PerseusRenderer = {
|
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
export const articleWithExpression: PerseusRenderer = {
|
|
62
|
+
content:
|
|
63
|
+
"### Practice Problem\n\n$8\\cdot(11i+2)=$ [[☃ expression 1]] \n*Your answer should be a complex number in the form $a+bi$ where $a$ and $b$ are real numbers.*",
|
|
64
|
+
images: {},
|
|
65
|
+
widgets: {
|
|
66
|
+
"expression 1": {
|
|
67
|
+
alignment: "default",
|
|
68
|
+
graded: true,
|
|
69
|
+
options: {
|
|
70
|
+
answerForms: [
|
|
71
|
+
{
|
|
72
|
+
considered: "correct",
|
|
73
|
+
form: true,
|
|
74
|
+
simplify: false,
|
|
75
|
+
value: "16+88i",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
buttonSets: ["basic"],
|
|
79
|
+
functions: ["f", "g", "h"],
|
|
80
|
+
times: false,
|
|
81
|
+
},
|
|
82
|
+
static: false,
|
|
83
|
+
type: "expression",
|
|
84
|
+
version: {major: 1, minor: 0},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
61
89
|
export const multiSectionArticle: ReadonlyArray<PerseusRenderer> = [
|
|
62
90
|
{
|
|
63
91
|
content:
|
|
@@ -28,6 +28,7 @@ import MockWidgetExport from "./mock-widget";
|
|
|
28
28
|
import type {PerseusItem} from "../perseus-types";
|
|
29
29
|
import type {APIOptions} from "../types";
|
|
30
30
|
import type {MockAssetLoadingWidget} from "./mock-asset-loading-widget";
|
|
31
|
+
import type {KeypadAPI} from "@khanacademy/math-input";
|
|
31
32
|
import type {PropsFor} from "@khanacademy/wonder-blocks-core";
|
|
32
33
|
|
|
33
34
|
// This looks alot like `widgets/__tests__/renderQuestion.jsx', except we use
|
|
@@ -342,12 +343,16 @@ describe("server item renderer", () => {
|
|
|
342
343
|
// Arranged
|
|
343
344
|
const onFocusChange = jest.fn();
|
|
344
345
|
const keypadElementDOMNode = <div />;
|
|
345
|
-
const keypadElement = {
|
|
346
|
+
const keypadElement: KeypadAPI = {
|
|
346
347
|
getDOMNode: jest
|
|
347
348
|
.fn()
|
|
348
349
|
.mockImplementation(() => keypadElementDOMNode),
|
|
349
350
|
activate: jest.fn(),
|
|
350
|
-
|
|
351
|
+
dismiss: jest.fn(),
|
|
352
|
+
configure: jest.fn(),
|
|
353
|
+
setCursor: jest.fn(),
|
|
354
|
+
setKeyHandler: jest.fn(),
|
|
355
|
+
};
|
|
351
356
|
const {renderer} = renderQuestion(
|
|
352
357
|
itemWithInput,
|
|
353
358
|
{onFocusChange, isMobile: true},
|
|
@@ -402,13 +407,16 @@ describe("server item renderer", () => {
|
|
|
402
407
|
// Arranged
|
|
403
408
|
const onFocusChange = jest.fn();
|
|
404
409
|
const keypadElementDOMNode = <div />;
|
|
405
|
-
const keypadElement = {
|
|
410
|
+
const keypadElement: KeypadAPI = {
|
|
406
411
|
getDOMNode: jest
|
|
407
412
|
.fn()
|
|
408
413
|
.mockImplementation(() => keypadElementDOMNode),
|
|
409
414
|
activate: jest.fn(),
|
|
410
415
|
dismiss: jest.fn(),
|
|
411
|
-
|
|
416
|
+
configure: jest.fn(),
|
|
417
|
+
setCursor: jest.fn(),
|
|
418
|
+
setKeyHandler: jest.fn(),
|
|
419
|
+
};
|
|
412
420
|
const {renderer} = renderQuestion(
|
|
413
421
|
itemWithInput,
|
|
414
422
|
{onFocusChange, isMobile: true},
|
package/src/article-renderer.tsx
CHANGED
|
@@ -8,7 +8,6 @@ import classNames from "classnames";
|
|
|
8
8
|
import * as React from "react";
|
|
9
9
|
|
|
10
10
|
import {DependenciesContext} from "./dependencies";
|
|
11
|
-
import ProvideKeypad from "./mixins/provide-keypad";
|
|
12
11
|
import {ClassNames as ApiClassNames, ApiOptions} from "./perseus-api";
|
|
13
12
|
import Renderer from "./renderer";
|
|
14
13
|
import Util from "./util";
|
|
@@ -16,6 +15,8 @@ import Util from "./util";
|
|
|
16
15
|
import type {KeypadProps} from "./mixins/provide-keypad";
|
|
17
16
|
import type {PerseusRenderer} from "./perseus-types";
|
|
18
17
|
import type {APIOptions, PerseusDependenciesV2} from "./types";
|
|
18
|
+
import type {KeypadAPI} from "@khanacademy/math-input";
|
|
19
|
+
import type {KeypadContextRendererInterface} from "@khanacademy/perseus-core";
|
|
19
20
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
20
21
|
|
|
21
22
|
type Props = {
|
|
@@ -28,7 +29,7 @@ type Props = {
|
|
|
28
29
|
useNewStyles: boolean;
|
|
29
30
|
linterContext: LinterContextProps;
|
|
30
31
|
legacyPerseusLint?: ReadonlyArray<string>;
|
|
31
|
-
|
|
32
|
+
keypadElement?: KeypadAPI | null | undefined;
|
|
32
33
|
dependencies: PerseusDependenciesV2;
|
|
33
34
|
} & KeypadProps;
|
|
34
35
|
|
|
@@ -38,11 +39,10 @@ type DefaultProps = {
|
|
|
38
39
|
linterContext: Props["linterContext"];
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class ArticleRenderer extends React.Component<Props, State> {
|
|
42
|
+
class ArticleRenderer
|
|
43
|
+
extends React.Component<Props>
|
|
44
|
+
implements KeypadContextRendererInterface
|
|
45
|
+
{
|
|
46
46
|
_currentFocus: any;
|
|
47
47
|
|
|
48
48
|
static defaultProps: DefaultProps = {
|
|
@@ -53,26 +53,16 @@ class ArticleRenderer extends React.Component<Props, State> {
|
|
|
53
53
|
|
|
54
54
|
constructor(props: Props) {
|
|
55
55
|
super(props);
|
|
56
|
-
this.state = ProvideKeypad.getInitialState.call(this);
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
componentDidMount() {
|
|
60
|
-
ProvideKeypad.componentDidMount.call(this);
|
|
61
59
|
this._currentFocus = null;
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
shouldComponentUpdate(nextProps: Props
|
|
65
|
-
return nextProps !== this.props
|
|
62
|
+
shouldComponentUpdate(nextProps: Props): boolean {
|
|
63
|
+
return nextProps !== this.props;
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
componentWillUnmount() {
|
|
69
|
-
ProvideKeypad.componentWillUnmount.call(this);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
keypadElement: () => void = () => {
|
|
73
|
-
return ProvideKeypad.keypadElement.call(this);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
66
|
_handleFocusChange: (arg1: any, arg2: any) => void = (
|
|
77
67
|
newFocusPath,
|
|
78
68
|
oldFocusPath,
|
|
@@ -87,7 +77,8 @@ class ArticleRenderer extends React.Component<Props, State> {
|
|
|
87
77
|
};
|
|
88
78
|
|
|
89
79
|
_setCurrentFocus: (arg1: any) => void = (newFocusPath) => {
|
|
90
|
-
const keypadElement = this.
|
|
80
|
+
const {keypadElement, apiOptions} = this.props;
|
|
81
|
+
const {isMobile} = apiOptions;
|
|
91
82
|
|
|
92
83
|
const prevFocusPath = this._currentFocus;
|
|
93
84
|
this._currentFocus = newFocusPath;
|
|
@@ -110,18 +101,14 @@ class ArticleRenderer extends React.Component<Props, State> {
|
|
|
110
101
|
this.props.apiOptions.onFocusChange(
|
|
111
102
|
this._currentFocus,
|
|
112
103
|
prevFocusPath,
|
|
113
|
-
|
|
114
|
-
didFocusInput && keypadElement && keypadElement.getDOMNode(),
|
|
104
|
+
didFocusInput ? keypadElement?.getDOMNode() : null,
|
|
115
105
|
);
|
|
116
106
|
}
|
|
117
107
|
|
|
118
|
-
|
|
119
|
-
if (keypadElement) {
|
|
108
|
+
if (keypadElement && isMobile) {
|
|
120
109
|
if (didFocusInput) {
|
|
121
|
-
// @ts-expect-error - TS2339 - Property 'activate' does not exist on type 'never'.
|
|
122
110
|
keypadElement.activate();
|
|
123
111
|
} else {
|
|
124
|
-
// @ts-expect-error - TS2339 - Property 'dismiss' does not exist on type 'never'.
|
|
125
112
|
keypadElement.dismiss();
|
|
126
113
|
}
|
|
127
114
|
}
|
|
@@ -189,7 +176,7 @@ class ArticleRenderer extends React.Component<Props, State> {
|
|
|
189
176
|
ref={refForSection}
|
|
190
177
|
key={i}
|
|
191
178
|
key_={i}
|
|
192
|
-
keypadElement={this.keypadElement
|
|
179
|
+
keypadElement={this.props.keypadElement}
|
|
193
180
|
apiOptions={{
|
|
194
181
|
...apiOptions,
|
|
195
182
|
onFocusChange: (newFocusPath, oldFocusPath) => {
|
|
@@ -22,7 +22,12 @@ import Util from "./util";
|
|
|
22
22
|
|
|
23
23
|
import type {KeypadProps} from "./mixins/provide-keypad";
|
|
24
24
|
import type {APIOptions, FocusPath, PerseusDependenciesV2} from "./types";
|
|
25
|
-
import type {
|
|
25
|
+
import type {KeypadAPI} from "@khanacademy/math-input";
|
|
26
|
+
import type {
|
|
27
|
+
KeypadContextRendererInterface,
|
|
28
|
+
RendererInterface,
|
|
29
|
+
KEScore,
|
|
30
|
+
} from "@khanacademy/perseus-core";
|
|
26
31
|
|
|
27
32
|
const {mapObject} = Objective;
|
|
28
33
|
|
|
@@ -36,9 +41,7 @@ type OwnProps = // These props are used by the ProvideKeypad mixin.
|
|
|
36
41
|
};
|
|
37
42
|
problemNum?: number;
|
|
38
43
|
reviewMode?: boolean;
|
|
39
|
-
|
|
40
|
-
keypadElement?: any | null | undefined;
|
|
41
|
-
|
|
44
|
+
keypadElement?: KeypadAPI | null | undefined;
|
|
42
45
|
dependencies: PerseusDependenciesV2;
|
|
43
46
|
};
|
|
44
47
|
|
|
@@ -71,7 +74,7 @@ type SerializedState = {
|
|
|
71
74
|
/* eslint-disable-next-line react/no-unsafe */
|
|
72
75
|
export class ServerItemRenderer
|
|
73
76
|
extends React.Component<Props, State>
|
|
74
|
-
implements RendererInterface
|
|
77
|
+
implements RendererInterface, KeypadContextRendererInterface
|
|
75
78
|
{
|
|
76
79
|
// @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
|
|
77
80
|
questionRenderer: Renderer;
|
|
@@ -176,8 +179,8 @@ export class ServerItemRenderer
|
|
|
176
179
|
onFocusChange(
|
|
177
180
|
this._currentFocus,
|
|
178
181
|
prevFocus,
|
|
179
|
-
didFocusInput
|
|
180
|
-
// @ts-expect-error - TS2345 - Argument of type 'false | Element | Text | null | undefined' is not assignable to parameter of type 'HTMLElement | undefined'.
|
|
182
|
+
didFocusInput ? keypadElement?.getDOMNode() : null,
|
|
183
|
+
// @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'false | Element | Text | null | undefined' is not assignable to parameter of type 'HTMLElement | undefined'.
|
|
181
184
|
didFocusInput &&
|
|
182
185
|
this.questionRenderer.getDOMNodeForPath(newFocus),
|
|
183
186
|
);
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
|
|
|
8
8
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
9
9
|
import type {Result} from "@khanacademy/wonder-blocks-data";
|
|
10
10
|
import type * as React from "react";
|
|
11
|
+
import type ReactDOM from "react-dom";
|
|
11
12
|
|
|
12
13
|
export type FocusPath = ReadonlyArray<string> | null | undefined;
|
|
13
14
|
|
|
@@ -143,10 +144,7 @@ export type APIOptions = Readonly<{
|
|
|
143
144
|
onFocusChange?: (
|
|
144
145
|
newFocusPath: FocusPath,
|
|
145
146
|
oldFocusPath: FocusPath,
|
|
146
|
-
|
|
147
|
-
// practice-exercise.jsx and in the mobile app
|
|
148
|
-
// (javascript/perseus-package/perseus-mobile-bridge.js).
|
|
149
|
-
keypadElement?: HTMLElement,
|
|
147
|
+
keypadElement?: ReturnType<typeof ReactDOM.findDOMNode>,
|
|
150
148
|
focusedElement?: HTMLElement,
|
|
151
149
|
) => unknown;
|
|
152
150
|
GroupMetadataEditor?: React.ComponentType<StubTagEditorType>;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "../__testdata__/expression.testdata";
|
|
9
9
|
import expressionExport from "../expression";
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import TestKeypadContextWrapper from "./test-keypad-context-wrapper";
|
|
12
12
|
|
|
13
13
|
import type {PerseusItem} from "../../perseus-types";
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ type WrappedKeypadContextProps = {
|
|
|
28
28
|
|
|
29
29
|
const WrappedKeypadContext = (props: WrappedKeypadContextProps) => {
|
|
30
30
|
return (
|
|
31
|
-
<
|
|
31
|
+
<TestKeypadContextWrapper>
|
|
32
32
|
<KeypadContext.Consumer>
|
|
33
33
|
{({keypadElement, setRenderer, scrollableElement}) => {
|
|
34
34
|
return (
|
|
@@ -41,7 +41,7 @@ const WrappedKeypadContext = (props: WrappedKeypadContextProps) => {
|
|
|
41
41
|
);
|
|
42
42
|
}}
|
|
43
43
|
</KeypadContext.Consumer>
|
|
44
|
-
</
|
|
44
|
+
</TestKeypadContextWrapper>
|
|
45
45
|
);
|
|
46
46
|
};
|
|
47
47
|
|