@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.
@@ -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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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?: any;
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, keypadElement?: ReturnType<typeof ReactDOM.findDOMNode>, focusedElement?: HTMLElement) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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, keypadElement?: Element | Text | null | undefined, focusedElement?: HTMLElement | undefined) => unknown) | undefined;
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
@@ -3,7 +3,7 @@
3
3
  "description": "Core Perseus API (includes renderers and widgets)",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "17.3.3",
6
+ "version": "17.5.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -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
- // We have _two_ different async processes that need to be resolved here
330
- jest.runOnlyPendingTimers();
331
- jest.runOnlyPendingTimers();
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
- undefined,
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 = <div />;
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
- // We have _two_ different async processes that need to be resolved here
366
- jest.runOnlyPendingTimers();
367
- jest.runOnlyPendingTimers();
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
- keypadElementDOMNode,
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
- // We have _two_ different async processes that need to be resolved here
394
- jest.runOnlyPendingTimers();
395
- jest.runOnlyPendingTimers();
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
- null,
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 = <div />;
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
- // We have _two_ different async processes that need to be resolved here
434
- jest.runOnlyPendingTimers();
435
- jest.runOnlyPendingTimers();
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
- null,
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
- undefined,
475
+ 0,
463
476
  expect.any(Object),
464
477
  );
465
478
  });
@@ -104,13 +104,24 @@ class ArticleRenderer
104
104
  );
105
105
  }
106
106
 
107
- if (this.props.apiOptions.onFocusChange != null) {
108
- this.props.apiOptions.onFocusChange(
109
- this._currentFocus,
110
- prevFocusPath,
111
- didFocusInput ? keypadElement?.getDOMNode() : null,
112
- didFocusInput ? focusedInput : null,
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
- PerseusRadioWidgetOptions,
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
- onFocusChange(
185
- this._currentFocus,
186
- prevFocus,
187
- didFocusInput ? keypadElement?.getDOMNode() : null,
188
- // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'false | Element | Text | null | undefined' is not assignable to parameter of type 'HTMLElement | undefined'.
189
- didFocusInput &&
190
- this.questionRenderer.getDOMNodeForPath(newFocus),
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) {
@@ -314,7 +314,7 @@
314
314
  tr:nth-child(odd) {
315
315
  // Doesn't work in IE8 :(
316
316
  td {
317
- background-color: #ededed;
317
+ background-color: @offWhite;
318
318
  }
319
319
  }
320
320
  }
@@ -15,6 +15,7 @@
15
15
  // TODO(charlie): DRY this up. How can we keep our global palette in sync across
16
16
  // repos?
17
17
  @white: #ffffff;
18
+ @offWhite: #f7f8fa;
18
19
  @gray98: #fafafa;
19
20
  @gray97: #f6f7f7;
20
21
  @gray95: #f0f1f2;
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 {PerseusWidget} from "./perseus-types";
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?: any;
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
- keypadElement?: ReturnType<typeof ReactDOM.findDOMNode>,
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
  );