@khanacademy/perseus 5.0.0 → 6.1.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/components/fixed-to-responsive.d.ts +27 -1
  3. package/dist/components/fixed-to-responsive.js.flow +27 -1
  4. package/dist/components/graph.d.ts +42 -26
  5. package/dist/components/graph.js.flow +42 -28
  6. package/dist/components/graphie.d.ts +34 -22
  7. package/dist/components/graphie.js.flow +37 -24
  8. package/dist/components/hud.d.ts +8 -14
  9. package/dist/components/hud.js.flow +7 -14
  10. package/dist/components/inline-icon.d.ts +9 -19
  11. package/dist/components/inline-icon.js.flow +26 -13
  12. package/dist/components/lint.d.ts +41 -13
  13. package/dist/components/lint.js.flow +42 -13
  14. package/dist/components/math-output.d.ts +15 -11
  15. package/dist/components/math-output.js.flow +15 -11
  16. package/dist/components/sortable.d.ts +17 -9
  17. package/dist/components/sortable.js.flow +22 -9
  18. package/dist/components/svg-image.d.ts +5 -3
  19. package/dist/components/svg-image.js.flow +5 -3
  20. package/dist/components/tooltip.d.ts +45 -4
  21. package/dist/components/tooltip.js.flow +44 -31
  22. package/dist/es/index.css +0 -3
  23. package/dist/es/index.js +478 -649
  24. package/dist/es/index.js.map +1 -1
  25. package/dist/index.css +0 -3
  26. package/dist/index.js +482 -646
  27. package/dist/index.js.map +1 -1
  28. package/dist/perseus-types.d.ts +11 -9
  29. package/dist/perseus-types.js.flow +11 -9
  30. package/dist/renderer.d.ts +2 -2
  31. package/dist/renderer.js.flow +1 -3
  32. package/dist/styles/shared.d.ts +0 -1
  33. package/dist/styles/shared.js.flow +0 -1
  34. package/dist/types.d.ts +0 -10
  35. package/dist/types.js.flow +0 -13
  36. package/dist/util/a11y.d.ts +0 -1
  37. package/dist/util/a11y.js.flow +0 -1
  38. package/dist/util/interactive.d.ts +2 -0
  39. package/dist/util/interactive.js.flow +2 -0
  40. package/dist/util.d.ts +3 -3
  41. package/dist/util.js.flow +4 -3
  42. package/dist/widgets/grapher.d.ts +1 -1
  43. package/dist/widgets/grapher.js.flow +12 -3
  44. package/dist/widgets/matcher.d.ts +7 -3
  45. package/dist/widgets/matcher.js.flow +7 -3
  46. package/dist/widgets/number-line.d.ts +1 -1
  47. package/dist/widgets/number-line.js.flow +1 -1
  48. package/package.json +2 -2
  49. package/src/__tests__/__snapshots__/item-renderer.test.tsx.snap +151 -159
  50. package/src/__tests__/__snapshots__/renderer.test.tsx.snap +358 -394
  51. package/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap +169 -189
  52. package/src/components/__stories__/lint.stories.tsx +56 -21
  53. package/src/components/__stories__/sortable.stories.tsx +5 -5
  54. package/src/components/__stories__/tooltip.stories.tsx +52 -0
  55. package/src/components/__tests__/__snapshots__/sortable.test.tsx.snap +114 -30
  56. package/src/components/__tests__/__snapshots__/sorter.test.tsx.snap +172 -204
  57. package/src/components/__tests__/sortable.test.tsx +54 -16
  58. package/src/components/__tests__/sorter.test.tsx +12 -9
  59. package/src/components/fixed-to-responsive.tsx +43 -38
  60. package/src/components/graph.tsx +95 -81
  61. package/src/components/graphie.tsx +62 -32
  62. package/src/components/hud.tsx +104 -115
  63. package/src/components/inline-icon.tsx +15 -32
  64. package/src/components/input-with-examples.tsx +4 -4
  65. package/src/components/lint.tsx +47 -41
  66. package/src/components/math-output.tsx +17 -11
  67. package/src/components/simple-keypad-input.tsx +2 -2
  68. package/src/components/sortable.tsx +134 -94
  69. package/src/components/svg-image.tsx +10 -11
  70. package/src/components/tooltip.tsx +189 -171
  71. package/src/interactive2/types.ts +2 -2
  72. package/src/mixins/provide-keypad.tsx +2 -2
  73. package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +708 -764
  74. package/src/perseus-types.ts +13 -9
  75. package/src/renderer.tsx +11 -22
  76. package/src/types.ts +0 -18
  77. package/src/util/interactive.ts +18 -5
  78. package/src/util/tex.ts +13 -77
  79. package/src/util.ts +3 -4
  80. package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +2 -2
  81. package/src/widgets/__tests__/__snapshots__/categorizer.test.ts.snap +717 -773
  82. package/src/widgets/__tests__/__snapshots__/cs-program.test.ts.snap +28 -36
  83. package/src/widgets/__tests__/__snapshots__/definition.test.ts.snap +52 -60
  84. package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +100 -108
  85. package/src/widgets/__tests__/__snapshots__/explanation.test.ts.snap +212 -260
  86. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt.test.ts.snap +613 -641
  87. package/src/widgets/__tests__/__snapshots__/graded-group-set.test.ts.snap +249 -261
  88. package/src/widgets/__tests__/__snapshots__/graded-group.test.ts.snap +671 -735
  89. package/src/widgets/__tests__/__snapshots__/grapher.test.ts.snap +2676 -2684
  90. package/src/widgets/__tests__/__snapshots__/group.test.tsx.snap +938 -1002
  91. package/src/widgets/__tests__/__snapshots__/iframe.test.ts.snap +28 -36
  92. package/src/widgets/__tests__/__snapshots__/image.test.ts.snap +227 -255
  93. package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +353 -357
  94. package/src/widgets/__tests__/__snapshots__/interaction.test.ts.snap +255 -259
  95. package/src/widgets/__tests__/__snapshots__/interactive-graph.test.ts.snap +2401 -2433
  96. package/src/widgets/__tests__/__snapshots__/matcher.test.tsx.snap +600 -756
  97. package/src/widgets/__tests__/__snapshots__/matrix.test.ts.snap +420 -428
  98. package/src/widgets/__tests__/__snapshots__/number-line.test.ts.snap +762 -770
  99. package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +373 -393
  100. package/src/widgets/__tests__/__snapshots__/orderer.test.ts.snap +116 -148
  101. package/src/widgets/__tests__/__snapshots__/passage-ref.test.ts.snap +289 -301
  102. package/src/widgets/__tests__/__snapshots__/passage.test.tsx.snap +1679 -1711
  103. package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +2692 -2960
  104. package/src/widgets/__tests__/__snapshots__/video.test.ts.snap +90 -98
  105. package/src/widgets/__tests__/matcher.test.tsx +33 -9
  106. package/src/widgets/expression.tsx +9 -6
  107. package/src/widgets/grapher.tsx +3 -3
  108. package/src/widgets/image.tsx +2 -2
  109. package/src/widgets/interactive-graph.tsx +5 -5
  110. package/src/widgets/matcher.tsx +43 -24
  111. package/src/widgets/number-line.tsx +1 -1
  112. package/src/widgets/simulator.tsx +4 -6
  113. package/{tsconfig.json → tsconfig-build.json} +6 -6
  114. package/tsconfig-build.tsbuildinfo +1 -0
  115. package/tsconfig.tsbuildinfo +0 -1
@@ -4,7 +4,7 @@ import "@testing-library/jest-dom"; // Imports custom mathers
4
4
 
5
5
  import {testDependencies} from "../../../../../testing/test-dependencies";
6
6
  import * as Dependencies from "../../dependencies";
7
- import Sortable from "../sortable";
7
+ import Sortable, {Layout} from "../sortable";
8
8
 
9
9
  describe("Sortable", () => {
10
10
  beforeEach(() => {
@@ -17,15 +17,53 @@ describe("Sortable", () => {
17
17
  // Arrange and Act
18
18
  const {container} = render(
19
19
  <Sortable
20
- layout="horizontal"
20
+ layout={Layout.HORIZONTAL}
21
21
  options={["a", "b", "c"]}
22
- waitForKatexLoad={false}
22
+ waitForTexRendererToLoad={false}
23
23
  />,
24
24
  );
25
25
 
26
26
  // Assert
27
27
  expect(container).toMatchSnapshot("first render");
28
28
  });
29
+
30
+ it("renders a spinner while waiting for the TeX renderer to load", () => {
31
+ let simulateFakeTeXRendering = () => {};
32
+ function FakeTeX({
33
+ children,
34
+ onRender,
35
+ }: {
36
+ children: string;
37
+ onRender?: () => unknown;
38
+ }) {
39
+ simulateFakeTeXRendering = onRender || simulateFakeTeXRendering;
40
+ return <div className="fake-tex">{children}</div>;
41
+ }
42
+ jest.spyOn(Dependencies, "getDependencies").mockReturnValue({
43
+ ...testDependencies,
44
+ TeX: FakeTeX,
45
+ });
46
+
47
+ // Act
48
+ const {container} = render(
49
+ <Sortable
50
+ waitForTexRendererToLoad={true}
51
+ layout={Layout.HORIZONTAL}
52
+ options={["a", "b", "c"]}
53
+ />,
54
+ );
55
+
56
+ // Assert
57
+ expect(container).toMatchSnapshot("first render: displays a spinner");
58
+
59
+ // Act
60
+ simulateFakeTeXRendering();
61
+
62
+ // Assert
63
+ expect(container).toMatchSnapshot(
64
+ "second render: displays the sortable",
65
+ );
66
+ });
29
67
  });
30
68
 
31
69
  describe("moveOptionToIndex", () => {
@@ -41,9 +79,9 @@ describe("moveOptionToIndex", () => {
41
79
  render(
42
80
  <Sortable
43
81
  ref={(r) => (sortable = r)}
44
- layout="horizontal"
82
+ layout={Layout.HORIZONTAL}
45
83
  options={["a", "b", "c"]}
46
- waitForKatexLoad={false}
84
+ waitForTexRendererToLoad={false}
47
85
  />,
48
86
  );
49
87
 
@@ -64,9 +102,9 @@ describe("moveOptionToIndex", () => {
64
102
  render(
65
103
  <Sortable
66
104
  ref={(r) => (sortable = r)}
67
- layout="horizontal"
105
+ layout={Layout.HORIZONTAL}
68
106
  options={["a", "b", "c"]}
69
- waitForKatexLoad={false}
107
+ waitForTexRendererToLoad={false}
70
108
  />,
71
109
  );
72
110
 
@@ -85,9 +123,9 @@ describe("moveOptionToIndex", () => {
85
123
  render(
86
124
  <Sortable
87
125
  ref={(r) => (sortable = r)}
88
- layout="horizontal"
126
+ layout={Layout.HORIZONTAL}
89
127
  options={["a", "b", "c"]}
90
- waitForKatexLoad={false}
128
+ waitForTexRendererToLoad={false}
91
129
  />,
92
130
  );
93
131
 
@@ -106,9 +144,9 @@ describe("moveOptionToIndex", () => {
106
144
  render(
107
145
  <Sortable
108
146
  ref={(r) => (sortable = r)}
109
- layout="horizontal"
147
+ layout={Layout.HORIZONTAL}
110
148
  options={["a", "b", "c"]}
111
- waitForKatexLoad={false}
149
+ waitForTexRendererToLoad={false}
112
150
  />,
113
151
  );
114
152
 
@@ -147,7 +185,7 @@ describe("Sortable.itemsFromProps", () => {
147
185
  "height": 0,
148
186
  "key": 0,
149
187
  "option": "a",
150
- "type": "static",
188
+ "state": "static",
151
189
  "width": 0,
152
190
  },
153
191
  {
@@ -155,7 +193,7 @@ describe("Sortable.itemsFromProps", () => {
155
193
  "height": 0,
156
194
  "key": 1,
157
195
  "option": "b",
158
- "type": "static",
196
+ "state": "static",
159
197
  "width": 0,
160
198
  },
161
199
  {
@@ -163,7 +201,7 @@ describe("Sortable.itemsFromProps", () => {
163
201
  "height": 0,
164
202
  "key": 2,
165
203
  "option": "c",
166
- "type": "static",
204
+ "state": "static",
167
205
  "width": 0,
168
206
  },
169
207
  ]
@@ -182,7 +220,7 @@ describe("Sortable.itemsFromProps", () => {
182
220
 
183
221
  // Assert
184
222
  items.forEach((item) => {
185
- expect(item.type).toBe("disabled");
223
+ expect(item.state).toBe("disabled");
186
224
  });
187
225
  });
188
226
  it("sets the item type to static if disabled is false", () => {
@@ -197,7 +235,7 @@ describe("Sortable.itemsFromProps", () => {
197
235
 
198
236
  // Assert
199
237
  items.forEach((item) => {
200
- expect(item.type).toBe("static");
238
+ expect(item.state).toBe("static");
201
239
  });
202
240
  });
203
241
  });
@@ -1,4 +1,3 @@
1
- import getRenderA11yString from "katex/dist/contrib/render-a11y-string";
2
1
  import * as React from "react";
3
2
  import "@testing-library/jest-dom";
4
3
 
@@ -22,15 +21,19 @@ describe("sorter widget", () => {
22
21
 
23
22
  jest.spyOn(Dependencies, "getDependencies").mockReturnValue({
24
23
  ...testDependencies,
25
- getKaTeX: () => {
26
- return Promise.resolve({});
24
+ TeX: ({
25
+ children,
26
+ // alias onRender to onLoad to quiet the overzealous testing-library linter
27
+ onRender: onLoad,
28
+ }: {
29
+ children: React.ReactNode;
30
+ onRender?: () => unknown;
31
+ }) => {
32
+ React.useLayoutEffect(() => {
33
+ onLoad?.();
34
+ }, [onLoad]);
35
+ return <span className="tex-mock">{children}</span>;
27
36
  },
28
- TeX: ({children}: {children: React.ReactNode}) => (
29
- <span className="tex-mock">{children}</span>
30
- ),
31
- // @ts-expect-error [FEI-5003] - TS2322 - Type 'Promise<any>' is not assignable to type '() => Promise<katexA11y>'.
32
- getRenderA11yString: Promise.resolve(getRenderA11yString),
33
- shouldUseFutureKaTeX: (flag: boolean) => {},
34
37
  });
35
38
  });
36
39
 
@@ -1,4 +1,3 @@
1
- /* eslint-disable react/prop-types */
2
1
  /**
3
2
  * A wrapper for a component that would otherwise have a fixed width and
4
3
  * height, that magically makes it reponsive while preserving its aspect ratio.
@@ -15,41 +14,47 @@
15
14
  * </FixedToResponsive>
16
15
  */
17
16
  import classNames from "classnames";
18
- import createReactClass from "create-react-class";
19
- import PropTypes from "prop-types";
20
17
  import * as React from "react";
21
18
 
22
19
  import {negativePhoneMargin} from "../styles/constants";
23
20
 
24
21
  const MIN_VIEWPORT_HEIGHT = 480;
25
22
 
26
- const FixedToResponsive: any = createReactClass({
27
- displayName: "FixedToResponsive",
28
-
29
- propTypes: {
30
- width: PropTypes.number.isRequired,
31
- height: PropTypes.number.isRequired,
32
- className: PropTypes.string,
33
- constrainHeight: PropTypes.bool,
34
- allowFullBleed: PropTypes.bool,
35
- },
36
-
37
- getDefaultProps: function () {
38
- return {
39
- className: "",
40
- constrainHeight: false,
41
- allowFullBleed: false,
42
- };
43
- },
44
-
45
- getInitialState: function () {
46
- return {
47
- viewportHeight: null,
48
- viewportWidth: null,
49
- };
50
- },
51
-
52
- componentDidMount: function () {
23
+ type Props = {
24
+ width: number;
25
+ height: number;
26
+ children: React.ReactNode;
27
+ className?: string;
28
+ constrainHeight?: boolean;
29
+ allowFullBleed?: boolean;
30
+ };
31
+
32
+ type DefaultProps = {
33
+ className: Props["className"];
34
+ constrainHeight: Props["constrainHeight"];
35
+ allowFullBleed: Props["allowFullBleed"];
36
+ };
37
+
38
+ type State = {
39
+ viewportHeight: number | null;
40
+ viewportWidth: number | null;
41
+ };
42
+
43
+ class FixedToResponsive extends React.Component<Props, State> {
44
+ _isMounted = false;
45
+
46
+ static defaultProps: DefaultProps = {
47
+ className: "",
48
+ constrainHeight: false,
49
+ allowFullBleed: false,
50
+ };
51
+
52
+ state: State = {
53
+ viewportHeight: null,
54
+ viewportWidth: null,
55
+ };
56
+
57
+ componentDidMount() {
53
58
  // TODO(scottgrant): This is a hack to remove the deprecated call to
54
59
  // this.isMounted() but is still considered an anti-pattern.
55
60
  this._isMounted = true;
@@ -70,13 +75,13 @@ const FixedToResponsive: any = createReactClass({
70
75
  } else {
71
76
  this._cacheViewportSize();
72
77
  }
73
- },
78
+ }
74
79
 
75
- componentWillUnmount: function () {
80
+ componentWillUnmount() {
76
81
  this._isMounted = false;
77
- },
82
+ }
78
83
 
79
- _cacheViewportSize: function () {
84
+ _cacheViewportSize: () => void = (): void => {
80
85
  if (this._isMounted) {
81
86
  this.setState({
82
87
  viewportHeight: Math.max(
@@ -86,9 +91,9 @@ const FixedToResponsive: any = createReactClass({
86
91
  viewportWidth: window.innerWidth,
87
92
  });
88
93
  }
89
- },
94
+ };
90
95
 
91
- render: function () {
96
+ render() {
92
97
  // The ideal behavior for responsified, fixed size child components is
93
98
  // that they shrink when they need to (while preserving aspect ratio)
94
99
  // but never grow larger than their original dimensions. We accomplish
@@ -158,7 +163,7 @@ const FixedToResponsive: any = createReactClass({
158
163
  );
159
164
  }
160
165
  return container;
161
- },
162
- });
166
+ }
167
+ }
163
168
 
164
169
  export default FixedToResponsive;
@@ -1,10 +1,11 @@
1
- /* eslint-disable react/forbid-prop-types, react/no-unsafe, react/sort-comp */
1
+ /* eslint-disable react/no-unsafe */
2
2
  import $ from "jquery";
3
- import PropTypes from "prop-types";
4
3
  import * as React from "react";
5
4
  import ReactDOM from "react-dom";
6
5
  import _ from "underscore";
7
6
 
7
+ import {Coord} from "../interactive2/types";
8
+ import {PerseusImageBackground} from "../perseus-types";
8
9
  import {interactiveSizes} from "../styles/constants";
9
10
  import Util from "../util";
10
11
  import GraphUtils from "../util/graph-utils";
@@ -13,7 +14,7 @@ import SvgImage from "./svg-image";
13
14
 
14
15
  const defaultBackgroundImage = {
15
16
  url: null,
16
- } as const;
17
+ };
17
18
 
18
19
  /* Style objects */
19
20
  const defaultInstructionsStyle = {
@@ -38,7 +39,46 @@ function numSteps(range: any, step: any) {
38
39
  return Math.floor((range[1] - range[0]) / step);
39
40
  }
40
41
 
41
- class Graph extends React.Component<any> {
42
+ type Props = {
43
+ box: Readonly<[number, number]>;
44
+ labels: ReadonlyArray<string>;
45
+ range: Readonly<[Coord, Coord]>;
46
+ step: Readonly<[number, number]>;
47
+ gridStep: Readonly<[number, number]>;
48
+ snapStep: Readonly<[number, number]>;
49
+ markings: string;
50
+ backgroundImage: PerseusImageBackground;
51
+ showProtractor: boolean;
52
+ showRuler: boolean;
53
+ rulerLabel: string;
54
+ rulerTicks: number;
55
+ instructions?: string;
56
+ isMobile: boolean;
57
+
58
+ onGraphieUpdated?: (graphie: any) => void;
59
+ onClick?: (Coord) => void;
60
+ onMouseDown?: (Coord) => void;
61
+ onMouseUp?: (Coord) => void;
62
+ onMouseMove?: (Coord) => void;
63
+ setDrawingAreaAvailable?: (boolean) => void;
64
+ };
65
+
66
+ type DefaultProps = {
67
+ labels: Props["labels"];
68
+ range: Props["range"];
69
+ step: Props["step"];
70
+ gridStep: Props["gridStep"];
71
+ snapStep: Props["snapStep"];
72
+ markings: Props["markings"];
73
+ backgroundImage: Props["backgroundImage"];
74
+ showProtractor: Props["showProtractor"];
75
+ showRuler: Props["showRuler"];
76
+ rulerLabel: Props["rulerLabel"];
77
+ rulerTicks: Props["rulerTicks"];
78
+ isMobile: Props["isMobile"];
79
+ };
80
+
81
+ class Graph extends React.Component<Props> {
42
82
  protractor: any;
43
83
  ruler: any;
44
84
  _graphie: any;
@@ -47,29 +87,7 @@ class Graph extends React.Component<any> {
47
87
  // @ts-expect-error [FEI-5003] - TS2564 - Property '_shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
48
88
  _shouldSetupGraphie: boolean;
49
89
 
50
- static propTypes = {
51
- box: PropTypes.array.isRequired,
52
- labels: PropTypes.arrayOf(PropTypes.string),
53
- range: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
54
- step: PropTypes.arrayOf(PropTypes.number),
55
- gridStep: PropTypes.arrayOf(PropTypes.number),
56
- snapStep: PropTypes.arrayOf(PropTypes.number),
57
- markings: PropTypes.string,
58
- backgroundImage: PropTypes.shape({
59
- url: PropTypes.string,
60
- }),
61
- showProtractor: PropTypes.bool,
62
- showRuler: PropTypes.bool,
63
- rulerLabel: PropTypes.string,
64
- rulerTicks: PropTypes.number,
65
- onGraphieUpdated: PropTypes.func,
66
- instructions: PropTypes.string,
67
- onClick: PropTypes.func,
68
- setDrawingAreaAvailable: PropTypes.func,
69
- isMobile: PropTypes.bool,
70
- };
71
-
72
- static defaultProps: any = {
90
+ static defaultProps: DefaultProps = {
73
91
  labels: ["x", "y"],
74
92
  range: [
75
93
  [-10, 10],
@@ -84,67 +102,13 @@ class Graph extends React.Component<any> {
84
102
  showRuler: false,
85
103
  rulerLabel: "",
86
104
  rulerTicks: 10,
87
- instructions: null,
88
- onGraphieUpdated: null,
89
- onClick: null,
90
- onMouseDown: null,
91
105
  isMobile: false,
92
106
  };
93
107
 
94
- render(): React.ReactNode {
95
- let image;
96
- const imageData = this.props.backgroundImage;
97
- if (imageData.url) {
98
- const scale = this.props.box[0] / interactiveSizes.defaultBoxSize;
99
- image = (
100
- // @ts-expect-error [FEI-5003] - TS2741 - Property 'alt' is missing in type '{ src: any; width: any; height: any; scale: number; responsive: false; }' but required in type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | "height" | "width" | "title" | "alt" | "trackInteraction" | "preloader" | "allowFullBleed" | "extraGraphie" | "overrideAriaHidden">'.
101
- <SvgImage
102
- src={imageData.url}
103
- width={imageData.width}
104
- height={imageData.height}
105
- scale={scale}
106
- responsive={false}
107
- />
108
- );
109
- } else {
110
- image = null;
111
- }
112
-
113
- return (
114
- <div
115
- className="graphie-container above-scratchpad"
116
- style={{
117
- width: this.props.box[0],
118
- height: this.props.box[1],
119
- }}
120
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'onMouseOut' does not exist on type 'Graph'.
121
- onMouseOut={this.onMouseOut}
122
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'onMouseOver' does not exist on type 'Graph'.
123
- onMouseOver={this.onMouseOver}
124
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'onClick' does not exist on type 'Graph'.
125
- onClick={this.onClick}
126
- >
127
- {image}
128
- {/* eslint-disable-next-line react/no-string-refs */}
129
- <div className="graphie" ref="graphieDiv" />
130
- </div>
131
- );
132
- }
133
-
134
108
  componentDidMount() {
135
109
  this._setupGraphie(true);
136
110
  }
137
111
 
138
- componentDidUpdate() {
139
- // Only setupGraphie once per componentDidUpdate().
140
- // See explanation in setupGraphie().
141
- this._hasSetupGraphieThisUpdate = false;
142
- if (this._shouldSetupGraphie) {
143
- this._setupGraphie(false);
144
- this._shouldSetupGraphie = false;
145
- }
146
- }
147
-
148
112
  UNSAFE_componentWillReceiveProps(nextProps: any) {
149
113
  const potentialChanges = [
150
114
  "labels",
@@ -166,6 +130,16 @@ class Graph extends React.Component<any> {
166
130
  });
167
131
  }
168
132
 
133
+ componentDidUpdate() {
134
+ // Only setupGraphie once per componentDidUpdate().
135
+ // See explanation in setupGraphie().
136
+ this._hasSetupGraphieThisUpdate = false;
137
+ if (this._shouldSetupGraphie) {
138
+ this._setupGraphie(false);
139
+ this._shouldSetupGraphie = false;
140
+ }
141
+ }
142
+
169
143
  /* Reset the graphie canvas to its initial state
170
144
  *
171
145
  * Use when re-rendering the parent component and you need a blank
@@ -412,6 +386,46 @@ class Graph extends React.Component<any> {
412
386
  "snapStep",
413
387
  );
414
388
  };
389
+
390
+ render(): React.ReactNode {
391
+ let image;
392
+ const imageData = this.props.backgroundImage;
393
+ if (imageData.url) {
394
+ const scale = this.props.box[0] / interactiveSizes.defaultBoxSize;
395
+ image = (
396
+ // @ts-expect-error [FEI-5003] - TS2741 - Property 'alt' is missing in type '{ src: any; width: any; height: any; scale: number; responsive: false; }' but required in type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | "height" | "width" | "title" | "alt" | "trackInteraction" | "preloader" | "allowFullBleed" | "extraGraphie" | "overrideAriaHidden">'.
397
+ <SvgImage
398
+ src={imageData.url}
399
+ width={imageData.width}
400
+ height={imageData.height}
401
+ scale={scale}
402
+ responsive={false}
403
+ />
404
+ );
405
+ } else {
406
+ image = null;
407
+ }
408
+
409
+ return (
410
+ <div
411
+ className="graphie-container above-scratchpad"
412
+ style={{
413
+ width: this.props.box[0],
414
+ height: this.props.box[1],
415
+ }}
416
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'onMouseOut' does not exist on type 'Graph'.
417
+ onMouseOut={this.onMouseOut}
418
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'onMouseOver' does not exist on type 'Graph'.
419
+ onMouseOver={this.onMouseOver}
420
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'onClick' does not exist on type 'Graph'.
421
+ onClick={this.onClick}
422
+ >
423
+ {image}
424
+ {/* eslint-disable-next-line react/no-string-refs */}
425
+ <div className="graphie" ref="graphieDiv" />
426
+ </div>
427
+ );
428
+ }
415
429
  }
416
430
 
417
431
  export default Graph;
@@ -1,5 +1,4 @@
1
1
  import $ from "jquery";
2
- import PropTypes from "prop-types";
3
2
  import * as React from "react";
4
3
  import ReactDOM from "react-dom";
5
4
  import _ from "underscore";
@@ -12,34 +11,58 @@ import GraphUtils from "../util/graph-utils";
12
11
  import GraphieClasses from "./graphie-classes";
13
12
  import Movables from "./graphie-movables";
14
13
 
14
+ import type {Coord} from "../interactive2/types";
15
+ import type {Range, Size} from "../perseus-types";
16
+
15
17
  const GraphieMovable = GraphieClasses.GraphieMovable;
16
18
 
17
19
  const createGraphie = GraphUtils.createGraphie;
18
20
  const {deepEq, nestedMap} = Util;
19
21
  const {assert} = InteractiveUtil;
20
22
 
21
- type Props = any;
23
+ type Props = {
24
+ addMouseLayer?: boolean;
25
+ box: Size;
26
+ range: [Coord, Coord];
27
+ ranges?: [Range, Range];
28
+ gridStep?: [number, number];
29
+ step?: [number, number];
30
+ scale?: [number, number];
31
+
32
+ isMobile?: boolean;
33
+ responsive?: boolean;
34
+
35
+ children?: React.ReactNode;
36
+
37
+ // TODO(LC-772) - type this prop!
38
+ options: any;
39
+
40
+ setDrawingAreaAvailable?: (boolean) => void;
41
+ setup: (
42
+ graphie: any,
43
+ options: {
44
+ range: [Coord, Coord];
45
+ scale: [number, number];
46
+ },
47
+ ) => void;
48
+
49
+ onClick?: (at: Coord) => void;
50
+ onMouseDown?: (at: Coord) => void;
51
+ onMouseMove?: (at: Coord) => void;
52
+ onMouseUp?: (at: Coord) => void;
53
+ };
54
+
55
+ type DefaultProps = {
56
+ range: Props["range"];
57
+ options: Props["options"];
58
+ responsive: Props["responsive"];
59
+ addMouseLayer: Props["addMouseLayer"];
60
+ };
22
61
 
23
62
  class Graphie extends React.Component<Props> {
24
- static propTypes = {
25
- addMouseLayer: PropTypes.bool,
26
- box: PropTypes.arrayOf(PropTypes.number).isRequired,
27
- children: PropTypes.node,
28
- isMobile: PropTypes.bool,
29
- onClick: PropTypes.func,
30
- onMouseDown: PropTypes.func,
31
- onMouseMove: PropTypes.func,
32
- onMouseUp: PropTypes.func,
33
- options: PropTypes.shape({
34
- snapStep: PropTypes.arrayOf(PropTypes.number),
35
- }),
36
- range: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
37
- responsive: PropTypes.bool,
38
- setDrawingAreaAvailable: PropTypes.func,
39
- setup: PropTypes.func.isRequired,
40
- };
63
+ graphieDivRef = React.createRef<HTMLDivElement>();
41
64
 
42
- static defaultProps: any = {
65
+ static defaultProps: DefaultProps = {
43
66
  range: [
44
67
  [-10, 10],
45
68
  [-10, 10],
@@ -102,21 +125,30 @@ class Graphie extends React.Component<Props> {
102
125
  };
103
126
 
104
127
  // bounds-checked range
105
- _range: () => ReadonlyArray<[number, number]> = () => {
106
- return _.map(this.props.range, (dimRange) => {
107
- if (dimRange[0] >= dimRange[1]) {
128
+ _range: () => [Coord, Coord] = () => {
129
+ const boundsCheckRange = (range: Coord): Coord => {
130
+ if (range[0] >= range[1]) {
108
131
  return [-10, 10];
109
132
  }
110
- return dimRange;
111
- });
133
+ return range;
134
+ };
135
+ return [
136
+ boundsCheckRange(this.props.range[0]),
137
+ boundsCheckRange(this.props.range[1]),
138
+ ];
112
139
  };
113
140
 
114
- _box: () => ReadonlyArray<number> = () => {
115
- return _.map(this.props.box, (pixelDim) => {
141
+ _box: () => Size = () => {
142
+ const ensureMinSize = (pixelDim: number): number => {
116
143
  // 340 = default size in the editor. exact value
117
144
  // is arbitrary; this is just a safety check.
118
145
  return pixelDim > 0 ? pixelDim : 340;
119
- });
146
+ };
147
+
148
+ return [
149
+ ensureMinSize(this.props.box[0]),
150
+ ensureMinSize(this.props.box[1]),
151
+ ];
120
152
  };
121
153
 
122
154
  _scale: () => ReadonlyArray<number> = () => {
@@ -131,8 +163,7 @@ class Graphie extends React.Component<Props> {
131
163
  _setupGraphie: () => void = () => {
132
164
  this._removeMovables();
133
165
 
134
- // eslint-disable-next-line react/no-string-refs
135
- const graphieDiv = ReactDOM.findDOMNode(this.refs.graphieDiv);
166
+ const graphieDiv = ReactDOM.findDOMNode(this.graphieDivRef.current);
136
167
  // @ts-expect-error [FEI-5003] - TS2769 - No overload matches this call. | TS2339 - Property 'empty' does not exist on type 'JQueryStatic'.
137
168
  $(graphieDiv).empty();
138
169
  // @ts-expect-error [FEI-5003] - TS2339 - Property '_graphie' does not exist on type 'Graphie'.
@@ -342,8 +373,7 @@ class Graphie extends React.Component<Props> {
342
373
  render(): React.ReactNode {
343
374
  return (
344
375
  <div className="graphie-container">
345
- {/* eslint-disable-next-line react/no-string-refs */}
346
- <div className="graphie" ref="graphieDiv" />
376
+ <div className="graphie" ref={this.graphieDivRef} />
347
377
  </div>
348
378
  );
349
379
  }