@khanacademy/perseus 17.3.3 → 17.5.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 +25 -0
- package/dist/es/index.css +1 -1
- package/dist/es/index.js +22 -6
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -6
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +2 -2
- package/dist/server-item-renderer.d.ts +1 -1
- package/dist/types.d.ts +4 -8
- package/dist/widgets/expression.d.ts +12 -12
- package/dist/widgets/label-image.d.ts +2 -2
- package/package.json +1 -1
- package/src/__stories__/article-renderer.stories.tsx +2 -0
- package/src/__tests__/server-item-renderer.test.tsx +38 -25
- package/src/article-renderer.tsx +18 -7
- package/src/index.ts +5 -4
- package/src/server-item-renderer.tsx +20 -8
- package/src/styles/perseus-renderer.less +1 -1
- package/src/styles/variables.less +1 -0
- package/src/types.ts +8 -8
- package/src/widgets/__stories__/expression.stories.tsx +2 -0
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/perseus-api.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export declare const ApiOptions: {
|
|
|
64
64
|
readonly defaults: Readonly<Readonly<{
|
|
65
65
|
isArticle?: boolean | undefined;
|
|
66
66
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
67
|
-
onFocusChange?: ((newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath,
|
|
67
|
+
onFocusChange?: ((newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
68
68
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
69
69
|
showAlignmentOptions?: boolean | undefined;
|
|
70
70
|
readOnly?: boolean | undefined;
|
|
@@ -105,7 +105,7 @@ export declare const ApiOptions: {
|
|
|
105
105
|
inModal: NonNullable<boolean | undefined>;
|
|
106
106
|
isArticle: NonNullable<boolean | undefined>;
|
|
107
107
|
isMobile: NonNullable<boolean | undefined>;
|
|
108
|
-
onFocusChange: (newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath,
|
|
108
|
+
onFocusChange: (newFocusPath: import("./types").FocusPath, oldFocusPath: import("./types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
109
109
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
110
110
|
readOnly: NonNullable<boolean | undefined>;
|
|
111
111
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -109,7 +109,7 @@ declare const ref: React.ForwardRefExoticComponent<Omit<Pick<OwnProps & HOCProps
|
|
|
109
109
|
apiOptions?: Readonly<{
|
|
110
110
|
isArticle?: boolean | undefined;
|
|
111
111
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
112
|
-
onFocusChange?: ((newFocusPath: FocusPath, oldFocusPath: FocusPath,
|
|
112
|
+
onFocusChange?: ((newFocusPath: FocusPath, oldFocusPath: FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
113
113
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
114
114
|
showAlignmentOptions?: boolean | undefined;
|
|
115
115
|
readOnly?: boolean | undefined;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { SerializedHighlightSet } from "./components/highlighting/types";
|
|
2
2
|
import type { ILogger } from "./logging/log";
|
|
3
3
|
import type { Item } from "./multi-items/item-types";
|
|
4
|
-
import type { PerseusWidget } from "./perseus-types";
|
|
4
|
+
import type { PerseusAnswerArea, PerseusRenderer, PerseusWidget } from "./perseus-types";
|
|
5
5
|
import type { SizeClass } from "./util/sizing-utils";
|
|
6
6
|
import type { KeypadAPI } from "@khanacademy/math-input";
|
|
7
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";
|
|
12
11
|
export type FocusPath = ReadonlyArray<string> | null | undefined;
|
|
13
12
|
type Empty = Record<never, never>;
|
|
14
13
|
export type Dimensions = {
|
|
@@ -33,10 +32,7 @@ export type PerseusScore = {
|
|
|
33
32
|
total: number;
|
|
34
33
|
message?: string | null | undefined;
|
|
35
34
|
};
|
|
36
|
-
export type Hint = {
|
|
37
|
-
widgets: WidgetDict;
|
|
38
|
-
content: string;
|
|
39
|
-
images: ImageDict;
|
|
35
|
+
export type Hint = PerseusRenderer & {
|
|
40
36
|
replace?: boolean;
|
|
41
37
|
};
|
|
42
38
|
export type Version = {
|
|
@@ -60,7 +56,7 @@ export type ChangeHandler = (arg1: {
|
|
|
60
56
|
widgets?: WidgetDict;
|
|
61
57
|
images?: ImageDict;
|
|
62
58
|
question?: any;
|
|
63
|
-
answerArea?:
|
|
59
|
+
answerArea?: PerseusAnswerArea | null;
|
|
64
60
|
itemDataVersion?: Version;
|
|
65
61
|
item?: Item;
|
|
66
62
|
editorMode?: EditorMode;
|
|
@@ -86,7 +82,7 @@ type TrackInteractionArgs = {
|
|
|
86
82
|
export type APIOptions = Readonly<{
|
|
87
83
|
isArticle?: boolean;
|
|
88
84
|
onInputError?: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
89
|
-
onFocusChange?: (newFocusPath: FocusPath, oldFocusPath: FocusPath,
|
|
85
|
+
onFocusChange?: (newFocusPath: FocusPath, oldFocusPath: FocusPath, keypadHeight?: number, focusedElement?: HTMLElement) => unknown;
|
|
90
86
|
GroupMetadataEditor?: React.ComponentType<StubTagEditorType>;
|
|
91
87
|
showAlignmentOptions?: boolean;
|
|
92
88
|
readOnly?: boolean;
|
|
@@ -92,7 +92,7 @@ declare const _default: Readonly<{
|
|
|
92
92
|
apiOptions: Readonly<Readonly<{
|
|
93
93
|
isArticle?: boolean | undefined;
|
|
94
94
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
95
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
95
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
96
96
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
97
97
|
showAlignmentOptions?: boolean | undefined;
|
|
98
98
|
readOnly?: boolean | undefined;
|
|
@@ -133,7 +133,7 @@ declare const _default: Readonly<{
|
|
|
133
133
|
inModal: NonNullable<boolean | undefined>;
|
|
134
134
|
isArticle: NonNullable<boolean | undefined>;
|
|
135
135
|
isMobile: NonNullable<boolean | undefined>;
|
|
136
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
136
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
137
137
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
138
138
|
readOnly: NonNullable<boolean | undefined>;
|
|
139
139
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -155,7 +155,7 @@ declare const _default: Readonly<{
|
|
|
155
155
|
apiOptions: Readonly<Readonly<{
|
|
156
156
|
isArticle?: boolean | undefined;
|
|
157
157
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
158
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
158
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
159
159
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
160
160
|
showAlignmentOptions?: boolean | undefined;
|
|
161
161
|
readOnly?: boolean | undefined;
|
|
@@ -196,7 +196,7 @@ declare const _default: Readonly<{
|
|
|
196
196
|
inModal: NonNullable<boolean | undefined>;
|
|
197
197
|
isArticle: NonNullable<boolean | undefined>;
|
|
198
198
|
isMobile: NonNullable<boolean | undefined>;
|
|
199
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
199
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
200
200
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
201
201
|
readOnly: NonNullable<boolean | undefined>;
|
|
202
202
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -218,7 +218,7 @@ declare const _default: Readonly<{
|
|
|
218
218
|
apiOptions?: Readonly<Readonly<{
|
|
219
219
|
isArticle?: boolean | undefined;
|
|
220
220
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
221
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
221
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
222
222
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
223
223
|
showAlignmentOptions?: boolean | undefined;
|
|
224
224
|
readOnly?: boolean | undefined;
|
|
@@ -259,7 +259,7 @@ declare const _default: Readonly<{
|
|
|
259
259
|
inModal: NonNullable<boolean | undefined>;
|
|
260
260
|
isArticle: NonNullable<boolean | undefined>;
|
|
261
261
|
isMobile: NonNullable<boolean | undefined>;
|
|
262
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
262
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
263
263
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
264
264
|
readOnly: NonNullable<boolean | undefined>;
|
|
265
265
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -292,7 +292,7 @@ declare const _default: Readonly<{
|
|
|
292
292
|
apiOptions: Readonly<Readonly<{
|
|
293
293
|
isArticle?: boolean | undefined;
|
|
294
294
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
295
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
295
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
296
296
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
297
297
|
showAlignmentOptions?: boolean | undefined;
|
|
298
298
|
readOnly?: boolean | undefined;
|
|
@@ -333,7 +333,7 @@ declare const _default: Readonly<{
|
|
|
333
333
|
inModal: NonNullable<boolean | undefined>;
|
|
334
334
|
isArticle: NonNullable<boolean | undefined>;
|
|
335
335
|
isMobile: NonNullable<boolean | undefined>;
|
|
336
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
336
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
337
337
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
338
338
|
readOnly: NonNullable<boolean | undefined>;
|
|
339
339
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -355,7 +355,7 @@ declare const _default: Readonly<{
|
|
|
355
355
|
apiOptions: Readonly<Readonly<{
|
|
356
356
|
isArticle?: boolean | undefined;
|
|
357
357
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
358
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
358
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
359
359
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
360
360
|
showAlignmentOptions?: boolean | undefined;
|
|
361
361
|
readOnly?: boolean | undefined;
|
|
@@ -396,7 +396,7 @@ declare const _default: Readonly<{
|
|
|
396
396
|
inModal: NonNullable<boolean | undefined>;
|
|
397
397
|
isArticle: NonNullable<boolean | undefined>;
|
|
398
398
|
isMobile: NonNullable<boolean | undefined>;
|
|
399
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
399
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
400
400
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
401
401
|
readOnly: NonNullable<boolean | undefined>;
|
|
402
402
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -418,7 +418,7 @@ declare const _default: Readonly<{
|
|
|
418
418
|
apiOptions?: Readonly<Readonly<{
|
|
419
419
|
isArticle?: boolean | undefined;
|
|
420
420
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
421
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
421
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
422
422
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
423
423
|
showAlignmentOptions?: boolean | undefined;
|
|
424
424
|
readOnly?: boolean | undefined;
|
|
@@ -459,7 +459,7 @@ declare const _default: Readonly<{
|
|
|
459
459
|
inModal: NonNullable<boolean | undefined>;
|
|
460
460
|
isArticle: NonNullable<boolean | undefined>;
|
|
461
461
|
isMobile: NonNullable<boolean | undefined>;
|
|
462
|
-
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
462
|
+
onFocusChange: (newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown;
|
|
463
463
|
onInputError: (widgetId: any, value: string, message?: string | null | undefined) => unknown;
|
|
464
464
|
readOnly: NonNullable<boolean | undefined>;
|
|
465
465
|
setDrawingAreaAvailable: (arg1: boolean) => unknown;
|
|
@@ -98,7 +98,7 @@ declare const _default: Readonly<{
|
|
|
98
98
|
apiOptions: Readonly<{
|
|
99
99
|
isArticle?: boolean | undefined;
|
|
100
100
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
101
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
101
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
102
102
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
103
103
|
showAlignmentOptions?: boolean | undefined;
|
|
104
104
|
readOnly?: boolean | undefined;
|
|
@@ -157,7 +157,7 @@ declare const _default: Readonly<{
|
|
|
157
157
|
apiOptions: Readonly<{
|
|
158
158
|
isArticle?: boolean | undefined;
|
|
159
159
|
onInputError?: ((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined;
|
|
160
|
-
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath,
|
|
160
|
+
onFocusChange?: ((newFocusPath: import("../types").FocusPath, oldFocusPath: import("../types").FocusPath, keypadHeight?: number | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
|
|
161
161
|
GroupMetadataEditor?: React.ComponentType<any> | undefined;
|
|
162
162
|
showAlignmentOptions?: boolean | undefined;
|
|
163
163
|
readOnly?: boolean | undefined;
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {KeypadContext} from "@khanacademy/math-input";
|
|
2
|
+
import {action} from "@storybook/addon-actions";
|
|
2
3
|
import React from "react";
|
|
3
4
|
|
|
4
5
|
import {storybookDependenciesV2} from "../../../../testing/test-dependencies";
|
|
@@ -67,6 +68,7 @@ export const ExpressionArticle = ({useNewStyles}): any => (
|
|
|
67
68
|
apiOptions={{
|
|
68
69
|
isMobile: true,
|
|
69
70
|
customKeypad: true,
|
|
71
|
+
onFocusChange: action("onFocusChange"),
|
|
70
72
|
}}
|
|
71
73
|
keypadElement={keypadElement}
|
|
72
74
|
/>
|
|
@@ -326,16 +326,16 @@ describe("server item renderer", () => {
|
|
|
326
326
|
|
|
327
327
|
// Act
|
|
328
328
|
const gotFocus = renderer.focus();
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
jest.
|
|
329
|
+
|
|
330
|
+
// We have some async processes that need to be resolved here
|
|
331
|
+
jest.runAllTimers();
|
|
332
332
|
|
|
333
333
|
// Assert
|
|
334
334
|
expect(gotFocus).toBeTrue();
|
|
335
335
|
expect(onFocusChange).toHaveBeenCalledWith(
|
|
336
336
|
["input-number 1"],
|
|
337
337
|
null,
|
|
338
|
-
|
|
338
|
+
0,
|
|
339
339
|
expect.any(Object),
|
|
340
340
|
);
|
|
341
341
|
});
|
|
@@ -343,7 +343,15 @@ describe("server item renderer", () => {
|
|
|
343
343
|
it("activates the keypadElement when focusing the renderer on mobile", () => {
|
|
344
344
|
// Arranged
|
|
345
345
|
const onFocusChange = jest.fn();
|
|
346
|
-
const keypadElementDOMNode =
|
|
346
|
+
const keypadElementDOMNode = document.createElement("div");
|
|
347
|
+
|
|
348
|
+
// We need to mock the getBoundingClientRect() method for our
|
|
349
|
+
// onFocusChange() callback to work properly.
|
|
350
|
+
keypadElementDOMNode.getBoundingClientRect = () =>
|
|
351
|
+
({
|
|
352
|
+
height: 250,
|
|
353
|
+
} as DOMRect);
|
|
354
|
+
|
|
347
355
|
const keypadElement: KeypadAPI = {
|
|
348
356
|
getDOMNode: jest
|
|
349
357
|
.fn()
|
|
@@ -362,9 +370,9 @@ describe("server item renderer", () => {
|
|
|
362
370
|
|
|
363
371
|
// Act
|
|
364
372
|
const gotFocus = renderer.focus();
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
jest.
|
|
373
|
+
|
|
374
|
+
// We have some async processes that need to be resolved here
|
|
375
|
+
jest.runAllTimers();
|
|
368
376
|
|
|
369
377
|
// Assert
|
|
370
378
|
expect(keypadElement.activate).toHaveBeenCalled();
|
|
@@ -372,7 +380,7 @@ describe("server item renderer", () => {
|
|
|
372
380
|
expect(onFocusChange).toHaveBeenCalledWith(
|
|
373
381
|
["input-number 1"],
|
|
374
382
|
null,
|
|
375
|
-
|
|
383
|
+
250,
|
|
376
384
|
expect.any(Object),
|
|
377
385
|
);
|
|
378
386
|
});
|
|
@@ -384,22 +392,19 @@ describe("server item renderer", () => {
|
|
|
384
392
|
onFocusChange,
|
|
385
393
|
});
|
|
386
394
|
renderer.focus();
|
|
387
|
-
// We have _two_ different async processes that need to be resolved here
|
|
388
|
-
jest.runOnlyPendingTimers();
|
|
389
|
-
jest.runOnlyPendingTimers();
|
|
390
395
|
|
|
391
396
|
// Act
|
|
392
397
|
renderer.blur();
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
jest.
|
|
398
|
+
|
|
399
|
+
// We have some async processes that need to be resolved here
|
|
400
|
+
jest.runAllTimers();
|
|
396
401
|
|
|
397
402
|
// Assert
|
|
398
403
|
expect(onFocusChange).toHaveBeenCalledTimes(2);
|
|
399
404
|
expect(onFocusChange).toHaveBeenLastCalledWith(
|
|
400
405
|
null,
|
|
401
406
|
["input-number 1"],
|
|
402
|
-
|
|
407
|
+
0,
|
|
403
408
|
null,
|
|
404
409
|
);
|
|
405
410
|
});
|
|
@@ -407,7 +412,15 @@ describe("server item renderer", () => {
|
|
|
407
412
|
it("dismisses the keypadElement when blurring the renderer on mobile", () => {
|
|
408
413
|
// Arranged
|
|
409
414
|
const onFocusChange = jest.fn();
|
|
410
|
-
const keypadElementDOMNode =
|
|
415
|
+
const keypadElementDOMNode = document.createElement("div");
|
|
416
|
+
|
|
417
|
+
// We need to mock the getBoundingClientRect() method for our
|
|
418
|
+
// onFocusChange() callback to work properly.
|
|
419
|
+
keypadElementDOMNode.getBoundingClientRect = () =>
|
|
420
|
+
({
|
|
421
|
+
height: 250,
|
|
422
|
+
} as DOMRect);
|
|
423
|
+
|
|
411
424
|
const keypadElement: KeypadAPI = {
|
|
412
425
|
getDOMNode: jest
|
|
413
426
|
.fn()
|
|
@@ -424,15 +437,12 @@ describe("server item renderer", () => {
|
|
|
424
437
|
{keypadElement},
|
|
425
438
|
);
|
|
426
439
|
renderer.focus();
|
|
427
|
-
// We have _two_ different async processes that need to be resolved here
|
|
428
|
-
jest.runOnlyPendingTimers();
|
|
429
|
-
jest.runOnlyPendingTimers();
|
|
430
440
|
|
|
431
441
|
// Act
|
|
432
442
|
renderer.blur();
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
jest.
|
|
443
|
+
|
|
444
|
+
// We have some async processes that need to be resolved here
|
|
445
|
+
jest.runAllTimers();
|
|
436
446
|
|
|
437
447
|
// Assert
|
|
438
448
|
expect(keypadElement.dismiss).toHaveBeenCalled();
|
|
@@ -440,7 +450,7 @@ describe("server item renderer", () => {
|
|
|
440
450
|
expect(onFocusChange).toHaveBeenLastCalledWith(
|
|
441
451
|
null,
|
|
442
452
|
["input-number 1"],
|
|
443
|
-
|
|
453
|
+
0,
|
|
444
454
|
null,
|
|
445
455
|
);
|
|
446
456
|
});
|
|
@@ -455,11 +465,14 @@ describe("server item renderer", () => {
|
|
|
455
465
|
// Act
|
|
456
466
|
renderer.focusPath(["input-number 1"]);
|
|
457
467
|
|
|
468
|
+
// We have some async processes that need to be resolved here
|
|
469
|
+
jest.runAllTimers();
|
|
470
|
+
|
|
458
471
|
// Assert
|
|
459
472
|
expect(onFocusChange).toHaveBeenCalledWith(
|
|
460
473
|
["input-number 1"],
|
|
461
474
|
null,
|
|
462
|
-
|
|
475
|
+
0,
|
|
463
476
|
expect.any(Object),
|
|
464
477
|
);
|
|
465
478
|
});
|
package/src/article-renderer.tsx
CHANGED
|
@@ -104,13 +104,24 @@ class ArticleRenderer
|
|
|
104
104
|
);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
const {onFocusChange} = this.props.apiOptions;
|
|
108
|
+
if (onFocusChange) {
|
|
109
|
+
// Wait for the keypad to mount before getting the height
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
const keypadDomNode =
|
|
112
|
+
keypadElement?.getDOMNode() as HTMLElement;
|
|
113
|
+
const keypadHeight =
|
|
114
|
+
keypadDomNode && didFocusInput
|
|
115
|
+
? keypadDomNode.getBoundingClientRect().height
|
|
116
|
+
: 0;
|
|
117
|
+
|
|
118
|
+
onFocusChange(
|
|
119
|
+
this._currentFocus,
|
|
120
|
+
prevFocusPath,
|
|
121
|
+
keypadHeight,
|
|
122
|
+
didFocusInput ? focusedInput : null,
|
|
123
|
+
);
|
|
124
|
+
}, 0);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
if (keypadElement && isMobile) {
|
package/src/index.ts
CHANGED
|
@@ -120,15 +120,16 @@ export type {
|
|
|
120
120
|
} from "./types";
|
|
121
121
|
export type {ParsedValue} from "./util";
|
|
122
122
|
export type {
|
|
123
|
-
MathFormat,
|
|
124
123
|
InputNumberWidget,
|
|
124
|
+
MathFormat,
|
|
125
|
+
PerseusAnswerArea,
|
|
126
|
+
PerseusExpressionWidgetOptions,
|
|
125
127
|
PerseusImageBackground,
|
|
126
128
|
PerseusInteractiveGraphWidgetOptions,
|
|
127
|
-
|
|
128
|
-
PerseusExpressionWidgetOptions,
|
|
129
|
+
PerseusItem,
|
|
129
130
|
PerseusPlotterWidgetOptions,
|
|
131
|
+
PerseusRadioWidgetOptions,
|
|
130
132
|
PerseusRenderer,
|
|
131
|
-
PerseusAnswerArea,
|
|
132
133
|
} from "./perseus-types";
|
|
133
134
|
export type {Coord} from "./interactive2/types";
|
|
134
135
|
export type {MarkerType} from "./widgets/label-image/types";
|
|
@@ -181,14 +181,26 @@ export class ServerItemRenderer
|
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
if (onFocusChange != null) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
// Wait for the keypad to mount before getting the height
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
// First, calculate the current keypad height
|
|
187
|
+
const keypadDomNode: HTMLElement =
|
|
188
|
+
keypadElement?.getDOMNode() as HTMLElement;
|
|
189
|
+
const keypadHeight =
|
|
190
|
+
keypadDomNode && didFocusInput
|
|
191
|
+
? keypadDomNode.getBoundingClientRect().height
|
|
192
|
+
: 0;
|
|
193
|
+
|
|
194
|
+
// Then call the callback
|
|
195
|
+
onFocusChange(
|
|
196
|
+
this._currentFocus,
|
|
197
|
+
prevFocus,
|
|
198
|
+
keypadHeight,
|
|
199
|
+
// @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'false | Element | Text | null | undefined' is not assignable to parameter of type 'HTMLElement | undefined'.
|
|
200
|
+
didFocusInput &&
|
|
201
|
+
this.questionRenderer.getDOMNodeForPath(newFocus),
|
|
202
|
+
);
|
|
203
|
+
}, 0);
|
|
192
204
|
}
|
|
193
205
|
|
|
194
206
|
if (keypadElement && isMobile) {
|
package/src/types.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type {SerializedHighlightSet} from "./components/highlighting/types";
|
|
2
2
|
import type {ILogger} from "./logging/log";
|
|
3
3
|
import type {Item} from "./multi-items/item-types";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
PerseusAnswerArea,
|
|
6
|
+
PerseusRenderer,
|
|
7
|
+
PerseusWidget,
|
|
8
|
+
} from "./perseus-types";
|
|
5
9
|
import type {SizeClass} from "./util/sizing-utils";
|
|
6
10
|
import type {KeypadAPI} from "@khanacademy/math-input";
|
|
7
11
|
import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
|
|
8
12
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
9
13
|
import type {Result} from "@khanacademy/wonder-blocks-data";
|
|
10
14
|
import type * as React from "react";
|
|
11
|
-
import type ReactDOM from "react-dom";
|
|
12
15
|
|
|
13
16
|
export type FocusPath = ReadonlyArray<string> | null | undefined;
|
|
14
17
|
|
|
@@ -45,10 +48,7 @@ export type PerseusScore =
|
|
|
45
48
|
message?: string | null | undefined;
|
|
46
49
|
};
|
|
47
50
|
|
|
48
|
-
export type Hint = {
|
|
49
|
-
widgets: WidgetDict;
|
|
50
|
-
content: string; // JSON string,
|
|
51
|
-
images: ImageDict;
|
|
51
|
+
export type Hint = PerseusRenderer & {
|
|
52
52
|
replace?: boolean;
|
|
53
53
|
};
|
|
54
54
|
|
|
@@ -78,7 +78,7 @@ export type ChangeHandler = (
|
|
|
78
78
|
images?: ImageDict;
|
|
79
79
|
// used only in EditorPage
|
|
80
80
|
question?: any;
|
|
81
|
-
answerArea?:
|
|
81
|
+
answerArea?: PerseusAnswerArea | null;
|
|
82
82
|
itemDataVersion?: Version;
|
|
83
83
|
// used in MutirenderEditor
|
|
84
84
|
item?: Item;
|
|
@@ -144,7 +144,7 @@ export type APIOptions = Readonly<{
|
|
|
144
144
|
onFocusChange?: (
|
|
145
145
|
newFocusPath: FocusPath,
|
|
146
146
|
oldFocusPath: FocusPath,
|
|
147
|
-
|
|
147
|
+
keypadHeight?: number,
|
|
148
148
|
focusedElement?: HTMLElement,
|
|
149
149
|
) => unknown;
|
|
150
150
|
GroupMetadataEditor?: React.ComponentType<StubTagEditorType>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {KeypadContext, KeypadType} from "@khanacademy/math-input";
|
|
2
|
+
import {action} from "@storybook/addon-actions";
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
|
|
4
5
|
import {ServerItemRendererWithDebugUI} from "../../../../../testing/server-item-renderer-with-debug-ui";
|
|
@@ -46,6 +47,7 @@ const WrappedKeypadContext = ({
|
|
|
46
47
|
apiOptions={{
|
|
47
48
|
isMobile: isMobile,
|
|
48
49
|
customKeypad: customKeypad,
|
|
50
|
+
onFocusChange: action("onFocusChange"),
|
|
49
51
|
}}
|
|
50
52
|
/>
|
|
51
53
|
);
|