@khanacademy/perseus-editor 2.15.6 → 2.16.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 (36) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/components/blur-input.d.ts +2 -0
  3. package/dist/components/widget-editor.d.ts +1 -1
  4. package/dist/editor-page.d.ts +4 -1
  5. package/dist/editor.d.ts +3 -1
  6. package/dist/es/index.js +170 -260
  7. package/dist/es/index.js.map +1 -1
  8. package/dist/hint-editor.d.ts +2 -0
  9. package/dist/iframe-content-renderer.d.ts +1 -0
  10. package/dist/index.js +137 -256
  11. package/dist/index.js.map +1 -1
  12. package/dist/item-editor.d.ts +6 -0
  13. package/dist/widgets/example-widget-editor.d.ts +1 -0
  14. package/dist/widgets/example-widget.d.ts +7 -0
  15. package/dist/widgets/graded-group-editor.d.ts +3 -0
  16. package/dist/widgets/group-editor.d.ts +2 -0
  17. package/dist/widgets/input-number-editor.d.ts +2 -0
  18. package/dist/widgets/simple-markdown-tester-editor.d.ts +7 -0
  19. package/dist/widgets/table-editor.d.ts +2 -0
  20. package/package.json +13 -13
  21. package/src/components/__tests__/blur-input.test.tsx +67 -0
  22. package/src/components/blur-input.tsx +7 -0
  23. package/src/editor-page.tsx +11 -25
  24. package/src/editor.tsx +37 -65
  25. package/src/hint-editor.tsx +15 -27
  26. package/src/iframe-content-renderer.tsx +19 -20
  27. package/src/item-editor.tsx +11 -18
  28. package/src/util/register-all-widgets-and-editors-for-testing.test.ts +3 -1
  29. package/src/widgets/example-widget-editor.tsx +4 -5
  30. package/src/widgets/example-widget.tsx +8 -10
  31. package/src/widgets/graded-group-editor.tsx +9 -19
  32. package/src/widgets/group-editor.tsx +5 -8
  33. package/src/widgets/input-number-editor.tsx +4 -5
  34. package/src/widgets/simple-markdown-tester-editor.tsx +4 -5
  35. package/src/widgets/table-editor.tsx +4 -5
  36. package/tsconfig-build.tsbuildinfo +1 -1
@@ -74,6 +74,8 @@ export class HintEditor extends React.Component<HintEditorProps> {
74
74
  showRemoveButton: true,
75
75
  };
76
76
 
77
+ editor = React.createRef<Editor>();
78
+
77
79
  handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void = (
78
80
  e: React.ChangeEvent<HTMLInputElement>,
79
81
  ) => {
@@ -81,21 +83,15 @@ export class HintEditor extends React.Component<HintEditorProps> {
81
83
  };
82
84
 
83
85
  focus: () => void = () => {
84
- // eslint-disable-next-line react/no-string-refs
85
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
86
- this.refs.editor.focus();
86
+ this.editor.current?.focus();
87
87
  };
88
88
 
89
89
  getSaveWarnings: () => any = () => {
90
- // eslint-disable-next-line react/no-string-refs
91
- // @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
92
- return this.refs.editor.getSaveWarnings();
90
+ return this.editor.current?.getSaveWarnings();
93
91
  };
94
92
 
95
93
  serialize: (options?: any) => any = (options: any) => {
96
- // eslint-disable-next-line react/no-string-refs
97
- // @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
98
- return this.refs.editor.serialize(options);
94
+ return this.editor.current?.serialize(options);
99
95
  };
100
96
 
101
97
  render(): React.ReactNode {
@@ -103,8 +99,7 @@ export class HintEditor extends React.Component<HintEditorProps> {
103
99
  <div className={"perseus-hint-editor " + this.props.className}>
104
100
  {this.props.showTitle && <div className="pod-title">Hint</div>}
105
101
  <Editor
106
- // eslint-disable-next-line react/no-string-refs
107
- ref="editor"
102
+ ref={this.editor}
108
103
  // Using the AssessmentItem content ID as the key
109
104
  // ensures that when the user navigates to another
110
105
  // item in the Sidebar, the question editor is
@@ -192,6 +187,9 @@ class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
192
187
  highlightLint: false,
193
188
  };
194
189
 
190
+ editor = React.createRef<HintEditor>();
191
+ frame = React.createRef<IframeContentRenderer>();
192
+
195
193
  componentDidMount() {
196
194
  this.updatePreview();
197
195
  }
@@ -204,9 +202,7 @@ class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
204
202
  const shouldBold =
205
203
  this.props.isLast && !/\*\*/.test(this.props.hint.content);
206
204
 
207
- // eslint-disable-next-line react/no-string-refs
208
- // @ts-expect-error - TS2339 - Property 'sendNewData' does not exist on type 'ReactInstance'.
209
- this.refs.frame.sendNewData({
205
+ this.frame.current?.sendNewData({
210
206
  type: "hint",
211
207
  data: {
212
208
  hint: this.props.hint,
@@ -223,21 +219,15 @@ class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
223
219
  };
224
220
 
225
221
  getSaveWarnings = () => {
226
- // eslint-disable-next-line react/no-string-refs
227
- // @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
228
- return this.refs.editor.getSaveWarnings();
222
+ return this.editor.current?.getSaveWarnings();
229
223
  };
230
224
 
231
225
  serialize = (options: any) => {
232
- // eslint-disable-next-line react/no-string-refs
233
- // @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
234
- return this.refs.editor.serialize(options);
226
+ return this.editor.current?.serialize(options);
235
227
  };
236
228
 
237
229
  focus = () => {
238
- // eslint-disable-next-line react/no-string-refs
239
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
240
- this.refs.editor.focus();
230
+ this.editor.current?.focus();
241
231
  };
242
232
 
243
233
  render(): React.ReactNode {
@@ -252,8 +242,7 @@ class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
252
242
  >
253
243
  <div className="perseus-editor-left-cell">
254
244
  <HintEditor
255
- // eslint-disable-next-line react/no-string-refs
256
- ref="editor"
245
+ ref={this.editor}
257
246
  itemId={this.props.itemId}
258
247
  isFirst={this.props.isFirst}
259
248
  isLast={this.props.isLast}
@@ -274,8 +263,7 @@ class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
274
263
  nochrome={true}
275
264
  >
276
265
  <IframeContentRenderer
277
- // eslint-disable-next-line react/no-string-refs
278
- ref="frame"
266
+ ref={this.frame}
279
267
  datasetKey="mobile"
280
268
  datasetValue={isMobile}
281
269
  seamless={true}
@@ -66,6 +66,8 @@ type Props = {
66
66
 
67
67
  class IframeContentRenderer extends React.Component<Props> {
68
68
  _frame: HTMLIFrameElement | null | undefined;
69
+ container = React.createRef<HTMLDivElement>();
70
+
69
71
  // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
70
72
  _isMounted: boolean;
71
73
  _lastData: any;
@@ -89,10 +91,12 @@ class IframeContentRenderer extends React.Component<Props> {
89
91
 
90
92
  updateIframeHeight[this.iframeID] = (height: any) => {
91
93
  this._lastHeight = height;
92
- if (this._isMounted && this.props.seamless) {
93
- // eslint-disable-next-line react/no-string-refs
94
- // @ts-expect-error - TS2339 - Property 'style' does not exist on type 'ReactInstance'.
95
- this.refs.container.style.height = height + "px";
94
+ if (
95
+ this._isMounted &&
96
+ this.props.seamless &&
97
+ this.container.current
98
+ ) {
99
+ this.container.current.style.height = height + "px";
96
100
  }
97
101
  };
98
102
  }
@@ -105,14 +109,12 @@ class IframeContentRenderer extends React.Component<Props> {
105
109
  }
106
110
 
107
111
  componentDidUpdate(prevProps: Props) {
108
- if (!this.props.seamless) {
109
- // eslint-disable-next-line react/no-string-refs
110
- // @ts-expect-error - TS2339 - Property 'style' does not exist on type 'ReactInstance'.
111
- this.refs.container.style.height = "100%";
112
- } else {
113
- // eslint-disable-next-line react/no-string-refs
114
- // @ts-expect-error - TS2339 - Property 'style' does not exist on type 'ReactInstance'.
115
- this.refs.container.style.height = this._lastHeight + "px";
112
+ if (this.container.current) {
113
+ if (!this.props.seamless) {
114
+ this.container.current.style.height = "100%";
115
+ } else {
116
+ this.container.current.style.height = this._lastHeight + "px";
117
+ }
116
118
  }
117
119
 
118
120
  if (prevProps.datasetValue !== this.props.datasetValue) {
@@ -131,9 +133,7 @@ class IframeContentRenderer extends React.Component<Props> {
131
133
  _prepareFrame() {
132
134
  // Don't initialize the iframe until the page has loaded
133
135
  if (this._frame) {
134
- // eslint-disable-next-line react/no-string-refs
135
- // @ts-expect-error - TS2339 - Property 'removeChild' does not exist on type 'ReactInstance'.
136
- this.refs.container.removeChild(this._frame);
136
+ this.container.current?.removeChild(this._frame);
137
137
  }
138
138
 
139
139
  const frame = document.createElement("iframe");
@@ -159,9 +159,7 @@ class IframeContentRenderer extends React.Component<Props> {
159
159
  frame.dataset.lintGutter = "true";
160
160
  }
161
161
 
162
- // eslint-disable-next-line react/no-string-refs
163
- // @ts-expect-error - TS2339 - Property 'appendChild' does not exist on type 'ReactInstance'.
164
- this.refs.container.appendChild(frame);
162
+ this.container.current?.appendChild(frame);
165
163
 
166
164
  this._frame = frame;
167
165
  }
@@ -181,8 +179,9 @@ class IframeContentRenderer extends React.Component<Props> {
181
179
  }
182
180
 
183
181
  render(): React.ReactNode {
184
- // eslint-disable-next-line react/no-string-refs
185
- return <div ref="container" style={{width: "100%", height: "100%"}} />;
182
+ return (
183
+ <div ref={this.container} style={{width: "100%", height: "100%"}} />
184
+ );
186
185
  }
187
186
  }
188
187
 
@@ -42,6 +42,10 @@ class ItemEditor extends React.Component<Props> {
42
42
  answerArea: {},
43
43
  };
44
44
 
45
+ frame = React.createRef<IframeContentRenderer>();
46
+ questionEditor = React.createRef<Editor>();
47
+ itemExtrasEditor = React.createRef<ItemExtrasEditor>();
48
+
45
49
  // Notify the parent that the question or answer area has been updated.
46
50
  updateProps: ChangeHandler = (newProps, cb, silent) => {
47
51
  const props = _(this.props).pick("question", "answerArea");
@@ -50,9 +54,7 @@ class ItemEditor extends React.Component<Props> {
50
54
  };
51
55
 
52
56
  triggerPreviewUpdate: (newData?: any) => void = (newData: any) => {
53
- // eslint-disable-next-line react/no-string-refs
54
- // @ts-expect-error - TS2339 - Property 'sendNewData' does not exist on type 'ReactInstance'.
55
- this.refs.frame.sendNewData(newData);
57
+ this.frame.current?.sendNewData(newData);
56
58
  };
57
59
 
58
60
  handleEditorChange: ChangeHandler = (newProps, cb, silent) => {
@@ -66,9 +68,7 @@ class ItemEditor extends React.Component<Props> {
66
68
  };
67
69
 
68
70
  getSaveWarnings: () => any = () => {
69
- // eslint-disable-next-line react/no-string-refs
70
- // @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
71
- return this.refs.questionEditor.getSaveWarnings();
71
+ return this.questionEditor.current?.getSaveWarnings();
72
72
  };
73
73
 
74
74
  serialize: (options?: any) => {
@@ -80,12 +80,8 @@ class ItemEditor extends React.Component<Props> {
80
80
  question: any;
81
81
  } = (options: any) => {
82
82
  return {
83
- // eslint-disable-next-line react/no-string-refs
84
- // @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
85
- question: this.refs.questionEditor.serialize(options),
86
- // eslint-disable-next-line react/no-string-refs
87
- // @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
88
- answerArea: this.refs.itemExtrasEditor.serialize(options),
83
+ question: this.questionEditor.current?.serialize(options),
84
+ answerArea: this.itemExtrasEditor.current?.serialize(),
89
85
  itemDataVersion: ITEM_DATA_VERSION,
90
86
  };
91
87
  };
@@ -100,8 +96,7 @@ class ItemEditor extends React.Component<Props> {
100
96
  <div className="perseus-editor-left-cell">
101
97
  <div className="pod-title">Question</div>
102
98
  <Editor
103
- // eslint-disable-next-line react/no-string-refs
104
- ref="questionEditor"
99
+ ref={this.questionEditor}
105
100
  // Using the AssessmentItem content ID as the key
106
101
  // ensures that when the user navigates to another
107
102
  // item in the Sidebar, the question editor is
@@ -124,8 +119,7 @@ class ItemEditor extends React.Component<Props> {
124
119
  nochrome={true}
125
120
  >
126
121
  <IframeContentRenderer
127
- // eslint-disable-next-line react/no-string-refs
128
- ref="frame"
122
+ ref={this.frame}
129
123
  key={this.props.deviceType}
130
124
  datasetKey="mobile"
131
125
  datasetValue={isMobile}
@@ -146,8 +140,7 @@ class ItemEditor extends React.Component<Props> {
146
140
  <div className="perseus-editor-left-cell">
147
141
  <div className="pod-title">Question extras</div>
148
142
  <ItemExtrasEditor
149
- // eslint-disable-next-line react/no-string-refs
150
- ref="itemExtrasEditor"
143
+ ref={this.itemExtrasEditor}
151
144
  onChange={this.handleItemExtrasChange}
152
145
  {...this.props.answerArea}
153
146
  />
@@ -6,7 +6,9 @@ describe("Registering all widgets and editors", () => {
6
6
  it("should have an editor for every widget", () => {
7
7
  registerAllWidgetsAndEditorsForTesting();
8
8
 
9
- const allWidgetsTypes = Widgets.getAllWidgetTypes();
9
+ const allWidgetsTypes = Widgets.getAllWidgetTypes()
10
+ // Auto correct is a special kind of widget, it DOES NOT need an editor
11
+ .filter((a) => a !== "deprecated-standin");
10
12
 
11
13
  expect(allWidgetsTypes).not.toContain("undefined");
12
14
 
@@ -19,6 +19,8 @@ class ExampleWidgetEditor extends React.Component<Props> {
19
19
  correct: "",
20
20
  };
21
21
 
22
+ input = React.createRef<HTMLInputElement>();
23
+
22
24
  handleAnswerChange: (arg1: React.ChangeEvent<HTMLInputElement>) => void = (
23
25
  event,
24
26
  ) => {
@@ -36,8 +38,7 @@ class ExampleWidgetEditor extends React.Component<Props> {
36
38
  <input
37
39
  value={this.props.correct}
38
40
  onChange={this.handleAnswerChange}
39
- // eslint-disable-next-line react/no-string-refs
40
- ref="input"
41
+ ref={this.input}
41
42
  />
42
43
  </label>
43
44
  </div>
@@ -49,9 +50,7 @@ class ExampleWidgetEditor extends React.Component<Props> {
49
50
  };
50
51
 
51
52
  focus: () => boolean = () => {
52
- // eslint-disable-next-line react/no-string-refs
53
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
54
- this.refs.input.focus();
53
+ this.input.current?.focus();
55
54
  return true;
56
55
  };
57
56
 
@@ -16,11 +16,12 @@ import _ from "underscore";
16
16
  import type {WidgetExports} from "@khanacademy/perseus";
17
17
 
18
18
  class TextInput extends React.Component<any> {
19
+ input = React.createRef<HTMLInputElement>();
20
+
19
21
  render(): React.ReactNode {
20
22
  return (
21
23
  <input
22
- // eslint-disable-next-line react/no-string-refs
23
- ref="input"
24
+ ref={this.input}
24
25
  value={this.props.value || ""}
25
26
  onChange={this.changeValue}
26
27
  />
@@ -28,9 +29,7 @@ class TextInput extends React.Component<any> {
28
29
  }
29
30
 
30
31
  focus = () => {
31
- // eslint-disable-next-line react/no-string-refs
32
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
33
- this.refs.input.focus();
32
+ this.input.current?.focus();
34
33
  return true;
35
34
  };
36
35
 
@@ -56,6 +55,8 @@ class ExampleWidget extends React.Component<any> {
56
55
  value: "",
57
56
  };
58
57
 
58
+ input = React.createRef<TextInput>();
59
+
59
60
  /**
60
61
  * This is the widget's grading function. simpleValidate generally
61
62
  * defers to this function.
@@ -98,8 +99,7 @@ class ExampleWidget extends React.Component<any> {
98
99
  render(): React.ReactNode {
99
100
  return (
100
101
  <TextInput
101
- // eslint-disable-next-line react/no-string-refs
102
- ref="input"
102
+ ref={this.input}
103
103
  value={this.props.value}
104
104
  // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
105
105
  onChange={this.change("value")}
@@ -117,9 +117,7 @@ class ExampleWidget extends React.Component<any> {
117
117
  * focused on page load.
118
118
  */
119
119
  focus: () => boolean = () => {
120
- // eslint-disable-next-line react/no-string-refs
121
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
122
- this.refs.input.focus();
120
+ this.input.current?.focus();
123
121
  return true;
124
122
  };
125
123
 
@@ -33,6 +33,9 @@ class GradedGroupEditor extends React.Component<Props> {
33
33
  hint: null,
34
34
  };
35
35
 
36
+ editor = React.createRef<Editor>();
37
+ hintEditor = React.createRef<Editor>();
38
+
36
39
  change: (arg1: any, arg2: any, arg3: any) => any = (...args) => {
37
40
  return Changeable.change.apply(this, args);
38
41
  };
@@ -40,9 +43,7 @@ class GradedGroupEditor extends React.Component<Props> {
40
43
  handleAddHint: () => void = () => {
41
44
  const hint = {content: ""} as const;
42
45
  this.props.onChange({hint}, () => {
43
- // eslint-disable-next-line react/no-string-refs
44
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
45
- this.refs["hint-editor"].focus();
46
+ this.hintEditor.current?.focus();
46
47
  });
47
48
  };
48
49
 
@@ -65,8 +66,7 @@ class GradedGroupEditor extends React.Component<Props> {
65
66
  </label>
66
67
  </div>
67
68
  <Editor
68
- // eslint-disable-next-line react/no-string-refs
69
- ref="editor"
69
+ ref={this.editor}
70
70
  content={this.props.content}
71
71
  widgets={this.props.widgets}
72
72
  apiOptions={this.props.apiOptions}
@@ -91,8 +91,7 @@ class GradedGroupEditor extends React.Component<Props> {
91
91
  <div className="perseus-hint-editor">
92
92
  <div className={css(styles.hintsTitle)}>Hint</div>
93
93
  <Editor
94
- // eslint-disable-next-line react/no-string-refs
95
- ref="hint-editor"
94
+ ref={this.hintEditor}
96
95
  content={
97
96
  this.props.hint ? this.props.hint.content : ""
98
97
  }
@@ -127,9 +126,7 @@ class GradedGroupEditor extends React.Component<Props> {
127
126
  }
128
127
 
129
128
  getSaveWarnings: () => any = () => {
130
- // eslint-disable-next-line react/no-string-refs
131
- // @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
132
- return this.refs.editor.getSaveWarnings();
129
+ return this.editor.current?.getSaveWarnings();
133
130
  };
134
131
 
135
132
  serialize: () => {
@@ -138,15 +135,8 @@ class GradedGroupEditor extends React.Component<Props> {
138
135
  } = () => {
139
136
  return {
140
137
  title: this.props.title,
141
- // eslint-disable-next-line react/no-string-refs
142
- // @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
143
- ...this.refs.editor.serialize(),
144
- hint:
145
- // eslint-disable-next-line react/no-string-refs
146
- this.refs["hint-editor"] &&
147
- // eslint-disable-next-line react/no-string-refs
148
- // @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
149
- this.refs["hint-editor"].serialize(),
138
+ ...this.editor.current?.serialize(),
139
+ hint: this.hintEditor.current?.serialize(),
150
140
  };
151
141
  };
152
142
  }
@@ -30,6 +30,8 @@ class GroupEditor extends React.Component<Props> {
30
30
  metadata: undefined,
31
31
  };
32
32
 
33
+ editor = React.createRef<Editor>();
34
+
33
35
  render(): React.ReactNode {
34
36
  return (
35
37
  <div className="perseus-group-editor">
@@ -39,8 +41,7 @@ class GroupEditor extends React.Component<Props> {
39
41
  {this._renderMetadataEditor()}
40
42
  </div>
41
43
  <Editor
42
- // eslint-disable-next-line react/no-string-refs
43
- ref="editor"
44
+ ref={this.editor}
44
45
  content={this.props.content}
45
46
  widgets={this.props.widgets}
46
47
  apiOptions={this.props.apiOptions}
@@ -69,15 +70,11 @@ class GroupEditor extends React.Component<Props> {
69
70
  };
70
71
 
71
72
  getSaveWarnings: () => ReadonlyArray<any> = () => {
72
- // eslint-disable-next-line react/no-string-refs
73
- // @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
74
- return this.refs.editor.getSaveWarnings();
73
+ return this.editor.current?.getSaveWarnings();
75
74
  };
76
75
 
77
76
  serialize: () => any = () => {
78
- // eslint-disable-next-line react/no-string-refs
79
- // @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
80
- return _.extend({}, this.refs.editor.serialize(), {
77
+ return _.extend({}, this.editor.current?.serialize(), {
81
78
  metadata: this.props.metadata,
82
79
  });
83
80
  };
@@ -1,7 +1,6 @@
1
1
  /* eslint-disable react/sort-comp */
2
2
  import {components, Util} from "@khanacademy/perseus";
3
3
  import * as React from "react";
4
- import ReactDOM from "react-dom";
5
4
  import _ from "underscore";
6
5
 
7
6
  import BlurInput from "../components/blur-input";
@@ -90,6 +89,8 @@ class InputNumberEditor extends React.Component<Props> {
90
89
  rightAlign: false,
91
90
  };
92
91
 
92
+ input = React.createRef<BlurInput>();
93
+
93
94
  handleAnswerChange: (arg1: string) => void = (str) => {
94
95
  const value = Util.firstNumericalParse(str) || 0;
95
96
  this.props.onChange({value: value});
@@ -116,8 +117,7 @@ class InputNumberEditor extends React.Component<Props> {
116
117
  <BlurInput
117
118
  value={"" + this.props.value}
118
119
  onChange={this.handleAnswerChange}
119
- // eslint-disable-next-line react/no-string-refs
120
- ref="input"
120
+ ref={this.input}
121
121
  />
122
122
  </label>
123
123
  </div>
@@ -262,8 +262,7 @@ class InputNumberEditor extends React.Component<Props> {
262
262
  }
263
263
 
264
264
  focus: () => boolean = () => {
265
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'focus' does not exist on type 'Element | Text'.
266
- ReactDOM.findDOMNode(this.refs.input).focus(); // eslint-disable-line react/no-string-refs
265
+ this.input.current?.focus();
267
266
  return true;
268
267
  };
269
268
 
@@ -49,6 +49,8 @@ class SimpleMarkdownTesterEditor extends React.Component<SimpleMarkdownTesterEdi
49
49
  value: "",
50
50
  };
51
51
 
52
+ input = React.createRef<TextArea>();
53
+
52
54
  render(): React.ReactNode {
53
55
  return (
54
56
  <div>
@@ -59,8 +61,7 @@ class SimpleMarkdownTesterEditor extends React.Component<SimpleMarkdownTesterEdi
59
61
  value={this.props.value}
60
62
  // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
61
63
  onChange={this.change("value")}
62
- // eslint-disable-next-line react/no-string-refs
63
- ref="input"
64
+ ref={this.input}
64
65
  />
65
66
  </div>
66
67
  </label>
@@ -73,9 +74,7 @@ class SimpleMarkdownTesterEditor extends React.Component<SimpleMarkdownTesterEdi
73
74
  };
74
75
 
75
76
  focus: () => boolean = () => {
76
- // eslint-disable-next-line react/no-string-refs
77
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
78
- this.refs.input.focus();
77
+ this.input.current?.focus();
79
78
  return true;
80
79
  };
81
80
 
@@ -2,7 +2,6 @@
2
2
  import {components, TableWidget, Util} from "@khanacademy/perseus";
3
3
  import PropTypes from "prop-types";
4
4
  import * as React from "react";
5
- import ReactDOM from "react-dom";
6
5
  import _ from "underscore";
7
6
 
8
7
  import Editor from "../editor";
@@ -36,9 +35,10 @@ class TableEditor extends React.Component<Props> {
36
35
  };
37
36
  })();
38
37
 
38
+ numberOfColumns = React.createRef<components.NumberInput>();
39
+
39
40
  focus: () => void = () => {
40
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'focus' does not exist on type 'Element | Text'.
41
- ReactDOM.findDOMNode(this.refs.numberOfColumns).focus(); // eslint-disable-line react/no-string-refs
41
+ this.numberOfColumns.current?.focus();
42
42
  };
43
43
 
44
44
  render(): React.ReactNode {
@@ -63,8 +63,7 @@ class TableEditor extends React.Component<Props> {
63
63
  <label>
64
64
  Number of columns:{" "}
65
65
  <NumberInput
66
- // eslint-disable-next-line react/no-string-refs
67
- ref="numberOfColumns"
66
+ ref={this.numberOfColumns}
68
67
  value={this.props.columns}
69
68
  onChange={(val) => {
70
69
  if (val) {