@khanacademy/perseus 15.0.0 → 16.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 +6 -0
- package/dist/article-renderer.d.ts +2 -1
- package/dist/es/index.js +456 -10
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +457 -8
- package/dist/index.js.map +1 -1
- package/dist/item-renderer.d.ts +125 -0
- package/dist/mixins/provide-keypad.d.ts +39 -0
- package/dist/server-item-renderer.d.ts +15 -2
- package/package.json +1 -1
- package/src/__stories__/item-renderer.stories.tsx +30 -0
- package/src/__stories__/server-item-renderer.stories.tsx +1 -1
- package/src/__testdata__/item-renderer.testdata.ts +299 -0
- package/src/__tests__/__snapshots__/item-renderer.test.tsx.snap +224 -0
- package/src/__tests__/item-renderer.test.tsx +930 -0
- package/src/__tests__/server-item-renderer.test.tsx +1 -1
- package/src/article-renderer.tsx +15 -13
- package/src/index.ts +1 -0
- package/src/item-renderer.tsx +529 -0
- package/src/mixins/provide-keypad.tsx +137 -0
- package/src/server-item-renderer.tsx +13 -11
- package/tsconfig-build.tsbuildinfo +1 -1
- package/src/__testdata__/server-item-renderer.testdata.ts +0 -160
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
|
+
/**
|
|
3
|
+
* A mixin that renders a custom software keypad in additional to the base
|
|
4
|
+
* component. The base component will receive blur events when the keypad is
|
|
5
|
+
* dismissed and can access the keypad element itself so as to manage its
|
|
6
|
+
* activation and dismissal.
|
|
7
|
+
*
|
|
8
|
+
* TODO(charlie): This would make a nicer higher-order component, except that
|
|
9
|
+
* we need to expose methods on the base component (i.e., `ItemRenderer`). When
|
|
10
|
+
* `ItemRenderer` and friends are written as ES6 Classes, we can have them
|
|
11
|
+
* extend a `ProvideKeypad` component instead of using this mixin.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {MobileKeypad} from "@khanacademy/math-input";
|
|
15
|
+
import * as React from "react";
|
|
16
|
+
import ReactDOM from "react-dom";
|
|
17
|
+
|
|
18
|
+
import reactRender from "../util/react-render";
|
|
19
|
+
|
|
20
|
+
import type {APIOptions} from "../types";
|
|
21
|
+
import type {KeypadAPI} from "@khanacademy/math-input";
|
|
22
|
+
import type {CSSProperties} from "aphrodite";
|
|
23
|
+
|
|
24
|
+
export type KeypadProps = {
|
|
25
|
+
// An Aphrodite style object, to be applied to the keypad container.
|
|
26
|
+
// Note that, given our awkward structure of injecting the keypad, this
|
|
27
|
+
// style won't be applied or updated dynamically. Rather, it will only
|
|
28
|
+
// be applied in `componentDidMount`.
|
|
29
|
+
keypadStyle?: CSSProperties;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type KeypadApiOptions = {
|
|
33
|
+
customKeypad: boolean;
|
|
34
|
+
nativeKeypadProxy: any;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// This represents the shape of any component that uses this ProvideKeypad
|
|
38
|
+
// mixin.
|
|
39
|
+
type KeypadMixinHostComponent = {
|
|
40
|
+
_keypadContainer?: HTMLDivElement | null;
|
|
41
|
+
props: {
|
|
42
|
+
apiOptions: APIOptions;
|
|
43
|
+
blur?: () => void;
|
|
44
|
+
};
|
|
45
|
+
state: {
|
|
46
|
+
keypadElement: any | null;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// NOTE: This is not a real component. It's a collection of methods used to
|
|
51
|
+
// create and manage a Keypad instances.
|
|
52
|
+
// TODO(LP-10789): replace this with a React Context object to pass information
|
|
53
|
+
// between Perseus and the Keypad.
|
|
54
|
+
const ProvideKeypad = {
|
|
55
|
+
getInitialState(this: KeypadMixinHostComponent): {
|
|
56
|
+
keypadElement: any | null;
|
|
57
|
+
} {
|
|
58
|
+
const _this = this;
|
|
59
|
+
let keypadElement: KeypadAPI | null = null;
|
|
60
|
+
if (
|
|
61
|
+
_this.props.apiOptions &&
|
|
62
|
+
_this.props.apiOptions.customKeypad &&
|
|
63
|
+
_this.props.apiOptions.nativeKeypadProxy
|
|
64
|
+
) {
|
|
65
|
+
keypadElement = _this.props.apiOptions.nativeKeypadProxy(
|
|
66
|
+
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'. | TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
|
|
67
|
+
() => _this.blur && _this.blur(),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return {keypadElement};
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
componentDidMount(this: KeypadMixinHostComponent) {
|
|
74
|
+
const _this = this;
|
|
75
|
+
const apiOptions: APIOptions = _this.props.apiOptions;
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
apiOptions &&
|
|
79
|
+
apiOptions.customKeypad &&
|
|
80
|
+
!apiOptions.nativeKeypadProxy
|
|
81
|
+
) {
|
|
82
|
+
// TODO(charlie): Render this and the wrapped component in the same
|
|
83
|
+
// React tree. We may also want to add this keypad asynchronously
|
|
84
|
+
// or on-demand in the future.
|
|
85
|
+
_this._keypadContainer = document.createElement("div");
|
|
86
|
+
document.body?.appendChild(_this._keypadContainer);
|
|
87
|
+
|
|
88
|
+
reactRender(
|
|
89
|
+
<MobileKeypad
|
|
90
|
+
onElementMounted={(element) => {
|
|
91
|
+
// NOTE(kevinb): The reason why this setState works is
|
|
92
|
+
// b/c we're calling it manually from item-renderer.jsx
|
|
93
|
+
// and we're manually setting the 'this' by using 'call'
|
|
94
|
+
// @ts-expect-error - TS2339 - Property 'setState' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
|
|
95
|
+
_this.setState({
|
|
96
|
+
keypadElement: element,
|
|
97
|
+
});
|
|
98
|
+
}}
|
|
99
|
+
onDismiss={() => {
|
|
100
|
+
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'. | TS2339 - Property 'blur' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
|
|
101
|
+
_this.blur && _this.blur();
|
|
102
|
+
}}
|
|
103
|
+
// @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
|
|
104
|
+
style={_this.props.keypadStyle}
|
|
105
|
+
onAnalyticsEvent={async () => {
|
|
106
|
+
// Intentionally left empty. This component is
|
|
107
|
+
// deprecated and so we don't want/need to instrument
|
|
108
|
+
// it.
|
|
109
|
+
}}
|
|
110
|
+
/>,
|
|
111
|
+
_this._keypadContainer,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
componentWillUnmount(this: KeypadMixinHostComponent) {
|
|
117
|
+
const _this = this;
|
|
118
|
+
if (_this._keypadContainer) {
|
|
119
|
+
ReactDOM.unmountComponentAtNode(_this._keypadContainer);
|
|
120
|
+
if (_this._keypadContainer.parentNode) {
|
|
121
|
+
// Note ChildNode.remove() isn't available in older Android
|
|
122
|
+
// webviews.
|
|
123
|
+
_this._keypadContainer.parentNode.removeChild(
|
|
124
|
+
_this._keypadContainer,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
_this._keypadContainer = null;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
keypadElement(this: KeypadMixinHostComponent): any {
|
|
132
|
+
const _this = this;
|
|
133
|
+
return _this.state.keypadElement;
|
|
134
|
+
},
|
|
135
|
+
} as const;
|
|
136
|
+
|
|
137
|
+
export default ProvideKeypad;
|
|
@@ -20,6 +20,7 @@ import {ApiOptions} from "./perseus-api";
|
|
|
20
20
|
import Renderer from "./renderer";
|
|
21
21
|
import Util from "./util";
|
|
22
22
|
|
|
23
|
+
import type {KeypadProps} from "./mixins/provide-keypad";
|
|
23
24
|
import type {APIOptions, FocusPath, PerseusDependenciesV2} from "./types";
|
|
24
25
|
import type {KeypadAPI} from "@khanacademy/math-input";
|
|
25
26
|
import type {
|
|
@@ -30,18 +31,19 @@ import type {
|
|
|
30
31
|
|
|
31
32
|
const {mapObject} = Objective;
|
|
32
33
|
|
|
33
|
-
type OwnProps =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
type OwnProps = // These props are used by the ProvideKeypad mixin.
|
|
35
|
+
KeypadProps & {
|
|
36
|
+
apiOptions: APIOptions;
|
|
37
|
+
hintsVisible?: number;
|
|
38
|
+
item: {
|
|
39
|
+
hints: ReadonlyArray<any>;
|
|
40
|
+
question: any;
|
|
41
|
+
};
|
|
42
|
+
problemNum?: number;
|
|
43
|
+
reviewMode?: boolean;
|
|
44
|
+
keypadElement?: KeypadAPI | null | undefined;
|
|
45
|
+
dependencies: PerseusDependenciesV2;
|
|
39
46
|
};
|
|
40
|
-
problemNum?: number;
|
|
41
|
-
reviewMode?: boolean;
|
|
42
|
-
keypadElement?: KeypadAPI | null | undefined;
|
|
43
|
-
dependencies: PerseusDependenciesV2;
|
|
44
|
-
};
|
|
45
47
|
|
|
46
48
|
type HOCProps = {
|
|
47
49
|
onRendered: (isRendered: boolean) => void;
|