@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.
@@ -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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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 { RendererInterface, KEScore } from "@khanacademy/perseus-core";
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?: any | null | undefined;
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?: any | null | undefined;
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?: HTMLElement, focusedElement?: HTMLElement) => unknown;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: HTMLElement | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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": "10.1.0",
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": "^10.1.1",
27
- "@khanacademy/perseus-core": "0.2.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
- } as const;
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
- } as const;
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},
@@ -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
- type State = {
42
- keypadElement: any | null;
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, nextState: State): boolean {
65
- return nextProps !== this.props || nextState !== this.state;
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.keypadElement();
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
- // @ts-expect-error - TS2339 - Property 'getDOMNode' does not exist on type 'never'.
114
- didFocusInput && keypadElement && keypadElement.getDOMNode(),
104
+ didFocusInput ? keypadElement?.getDOMNode() : null,
115
105
  );
116
106
  }
117
107
 
118
- // @ts-expect-error - TS1345 - An expression of type 'void' cannot be tested for truthiness.
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 {RendererInterface, KEScore} from "@khanacademy/perseus-core";
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
- // from KeypadContext
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 && keypadElement && keypadElement.getDOMNode(),
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
- // These two parameters are only used in library-exercise.jsx and
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 TestKeypadContext from "./test-keypad-context-wrapper";
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
- <TestKeypadContext>
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
- </TestKeypadContext>
44
+ </TestKeypadContextWrapper>
45
45
  );
46
46
  };
47
47