Skip to content

Commit 9cce63f

Browse files
[cleaning-the-perseus-finddom] SSSSOMEBODY STOP ME
1 parent 4ee9640 commit 9cce63f

File tree

5 files changed

+67
-80
lines changed

5 files changed

+67
-80
lines changed

packages/perseus/src/components/simple-keypad-input.tsx

Lines changed: 51 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,80 +13,78 @@ import {KeypadInput, keypadElementPropType} from "@khanacademy/math-input";
1313
import PropTypes from "prop-types";
1414
import * as React from "react";
1515

16-
import type {Focusable} from "../types";
17-
18-
export default class SimpleKeypadInput
19-
extends React.Component<any>
20-
implements Focusable
21-
{
22-
static contextType = KeypadContext;
23-
declare context: React.ContextType<typeof KeypadContext>;
24-
_isMounted = false;
25-
inputRef = React.createRef<KeypadInput>();
16+
type SimpleKeypadInputProps = {
17+
keypadElement?: any;
18+
onFocus: () => void;
19+
onBlur: () => void;
20+
onChange: (value: string, callback: any) => void;
21+
value?: string | number | null;
22+
ariaLabel?: string;
23+
style?: React.CSSProperties;
24+
};
2625

27-
componentDidMount() {
28-
// TODO(scottgrant): This is a hack to remove the deprecated call to
29-
// this.isMounted() but is still considered an anti-pattern.
30-
this._isMounted = true;
31-
}
26+
const SimpleKeypadInput = React.forwardRef<HTMLElement, SimpleKeypadInputProps>(
27+
function SimpleKeypadInput(props, ref) {
28+
const keypadInputRef = React.useRef<any>(null);
29+
const context = React.useContext(KeypadContext);
3230

33-
componentWillUnmount() {
34-
this._isMounted = false;
35-
}
31+
// Use imperative handle to expose the DOM node properties and custom methods
32+
// required for consuming Perseus widgets to handle focus/blur events.
33+
React.useImperativeHandle(ref, () => {
34+
const keypadInstance = keypadInputRef.current;
35+
if (!keypadInstance) {
36+
return null;
37+
}
3638

37-
focus() {
38-
// The inputRef is a ref to a MathInput, which
39-
// also controls the keypad state during focus events.
40-
this.inputRef.current?.focus(this.context.setKeypadActive);
41-
}
39+
// Get the actual DOM node from the KeypadInput (MathInput) component's
40+
// internal inputRef.
41+
const inputElement = keypadInstance.inputRef;
4242

43-
blur() {
44-
this.inputRef.current?.blur();
45-
}
43+
if (!inputElement) {
44+
return null;
45+
}
4646

47-
getValue(): string | number {
48-
return this.props.value;
49-
}
47+
// Return the DOM node with focus/blur methods attached.
48+
return {
49+
...inputElement,
50+
focus: () => {
51+
keypadInstance.focus(context.setKeypadActive);
52+
},
53+
blur: () => {
54+
keypadInstance.blur();
55+
},
56+
getValue: () => {
57+
return props.value;
58+
},
59+
};
60+
});
5061

51-
render(): React.ReactNode {
52-
const _this = this;
53-
// Intercept the `onFocus` prop, as we need to configure the keypad
54-
// before continuing with the default focus logic for Perseus inputs.
55-
// Intercept the `value` prop so as to map `null` to the empty string,
56-
// as the `KeypadInput` does not support `null` values.
57-
const {keypadElement, onFocus, value, ...rest} = _this.props;
62+
const {keypadElement, onFocus, value, ...rest} = props;
5863

5964
return (
60-
// @ts-expect-error - TS2769 - No overload matches this call.
6165
<KeypadInput
62-
ref={this.inputRef}
66+
ref={keypadInputRef}
6367
keypadElement={keypadElement}
6468
onFocus={() => {
6569
if (keypadElement) {
66-
keypadElement.configure(
67-
{
68-
keypadType: "FRACTION",
69-
},
70-
() => {
71-
if (_this._isMounted) {
72-
onFocus?.();
73-
}
74-
},
75-
);
76-
} else {
77-
onFocus?.();
70+
keypadElement.configure({
71+
keypadType: "FRACTION",
72+
});
7873
}
74+
onFocus?.();
7975
}}
8076
value={value == null ? "" : "" + value}
77+
ariaLabel={props.ariaLabel || ""}
8178
{...rest}
8279
/>
8380
);
84-
}
85-
}
81+
},
82+
);
8683

87-
// @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
8884
SimpleKeypadInput.propTypes = {
8985
keypadElement: keypadElementPropType,
9086
onFocus: PropTypes.func,
9187
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
9288
};
89+
90+
export default SimpleKeypadInput;

packages/perseus/src/widgets/matrix/matrix.tsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {linterContextDefault} from "@khanacademy/perseus-linter";
33
import {StyleSheet} from "aphrodite";
44
import classNames from "classnames";
55
import * as React from "react";
6-
import ReactDOM from "react-dom";
76
import _ from "underscore";
87

98
import {PerseusI18nContext} from "../../components/i18n-context";
@@ -115,7 +114,7 @@ type State = {
115114
class Matrix extends React.Component<Props, State> implements Widget {
116115
static contextType = PerseusI18nContext;
117116
declare context: React.ContextType<typeof PerseusI18nContext>;
118-
answerRefs: Record<string, SimpleKeypadInput | HTMLInputElement> = {};
117+
answerRefs: Record<string, HTMLInputElement> = {};
119118

120119
// @ts-expect-error - TS2564 - Property 'cursorPosition' has no initializer and is not definitely assigned in the constructor.
121120
cursorPosition: [number, number];
@@ -188,12 +187,7 @@ class Matrix extends React.Component<Props, State> implements Widget {
188187
getDOMNodeForPath(path: FocusPath) {
189188
const inputID = getRefForPath(path);
190189
const inputRef = this.answerRefs[inputID];
191-
if (this.props.apiOptions.customKeypad) {
192-
// This is a SimpleKeypadInput, so we need to find the DOM node
193-
return ReactDOM.findDOMNode(inputRef);
194-
} else {
195-
return inputRef as HTMLInputElement;
196-
}
190+
return inputRef;
197191
}
198192

199193
handleKeyDown: (arg1: any, arg2: any, arg3: any) => void = (
@@ -205,8 +199,8 @@ class Matrix extends React.Component<Props, State> implements Widget {
205199
const maxCol = this.props.matrixBoardSize[1];
206200
let enterTheMatrix = null;
207201

208-
// eslint-disable-next-line react/no-string-refs
209-
const curInput = this.refs[getRefForPath(getInputPath(row, col))];
202+
const inputID = getRefForPath(getInputPath(row, col));
203+
const curInput = this.answerRefs[inputID];
210204
// @ts-expect-error - TS2339 - Property 'getStringValue' does not exist on type 'ReactInstance'.
211205
const curValueString = curInput.getStringValue();
212206
// @ts-expect-error - TS2339 - Property 'getSelectionStart' does not exist on type 'ReactInstance'.
@@ -247,22 +241,19 @@ class Matrix extends React.Component<Props, State> implements Widget {
247241
e.preventDefault();
248242

249243
// Focus the input and move the cursor to the end of it.
250-
// eslint-disable-next-line react/no-string-refs
251-
const input = this.refs[getRefForPath(nextPath)];
244+
const inputID = getRefForPath(nextPath);
245+
const input = this.answerRefs[inputID];
252246

253247
// Multiply by 2 to ensure the cursor always ends up at the end;
254248
// Opera sometimes sees a carriage return as 2 characters.
255249
// @ts-expect-error - TS2339 - Property 'getStringValue' does not exist on type 'ReactInstance'.
256250
const inputValString = input.getStringValue();
257251
const valueLength = inputValString.length * 2;
258252

259-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
260253
input.focus();
261254
if (e.key === "ArrowRight") {
262-
// @ts-expect-error - TS2339 - Property 'setSelectionRange' does not exist on type 'ReactInstance'.
263255
input.setSelectionRange(0, 0);
264256
} else {
265-
// @ts-expect-error - TS2339 - Property 'setSelectionRange' does not exist on type 'ReactInstance'.
266257
input.setSelectionRange(valueLength, valueLength);
267258
}
268259
}
@@ -470,7 +461,6 @@ class Matrix extends React.Component<Props, State> implements Widget {
470461
<SimpleKeypadInput
471462
{...inputProps}
472463
style={style}
473-
scrollable={true}
474464
keypadElement={
475465
this.props.keypadElement
476466
}

packages/perseus/src/widgets/number-line/number-line.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {number as knumber, KhanMath} from "@khanacademy/kmath";
22
import * as React from "react";
3-
import ReactDOM from "react-dom";
43
import _ from "underscore";
54

65
import Graphie from "../../components/graphie";
@@ -247,7 +246,7 @@ class NumberLine extends React.Component<Props, State> implements Widget {
247246
static contextType = PerseusI18nContext;
248247
declare context: React.ContextType<typeof PerseusI18nContext>;
249248

250-
tickControlRef: SimpleKeypadInput | HTMLInputElement | null = null;
249+
tickControlRef: HTMLInputElement | null = null;
251250

252251
static defaultProps: DefaultProps = {
253252
range: [0, 10],
@@ -366,16 +365,21 @@ class NumberLine extends React.Component<Props, State> implements Widget {
366365
}
367366
};
368367

368+
// There's only one input path for the tick control, but the renderer
369+
// expects this method to be implemented.
369370
getInputPaths: () => ReadonlyArray<ReadonlyArray<string>> = () => {
370371
if (this.props.isTickCtrl) {
371372
return [["tick-ctrl"]];
372373
}
373374
return [];
374375
};
375376

377+
// This consumes the input path returned by getInputPaths,
378+
// and returns the DOM node for the tick control input.
376379
getDOMNodeForPath(inputPath: FocusPath) {
380+
// If we have a tick control, return the DOM node for the tick control input.
377381
if (inputPath?.length === 1) {
378-
return ReactDOM.findDOMNode(this.tickControlRef);
382+
return this.tickControlRef;
379383
}
380384
return null;
381385
}

packages/perseus/src/widgets/numeric-input/numeric-input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export const NumericInputComponent = forwardRef<Focusable, NumericInputProps>(
8787
return (
8888
<div className={alignmentClass}>
8989
<SimpleKeypadInput
90-
ref={inputRef as React.RefObject<SimpleKeypadInput>}
90+
ref={inputRef as React.RefObject<HTMLInputElement>}
9191
value={props.userInput.currentValue}
9292
keypadElement={props.keypadElement}
9393
onChange={handleChange}

packages/perseus/src/widgets/table/table.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {linterContextDefault} from "@khanacademy/perseus-linter";
22
import * as React from "react";
3-
import {findDOMNode} from "react-dom";
43

54
import {PerseusI18nContext} from "../../components/i18n-context";
65
import SimpleKeypadInput from "../../components/simple-keypad-input";
@@ -77,7 +76,7 @@ class Table extends React.Component<Props> implements Widget {
7776
static contextType = PerseusI18nContext;
7877
declare context: React.ContextType<typeof PerseusI18nContext>;
7978
headerRefs: Record<string, any> = {};
80-
answerRefs: Record<string, SimpleKeypadInput | HTMLInputElement> = {};
79+
answerRefs: Record<string, HTMLInputElement> = {};
8180

8281
static defaultProps: DefaultProps = {
8382
apiOptions: ApiOptions.defaults,
@@ -154,11 +153,7 @@ class Table extends React.Component<Props> implements Widget {
154153
const inputID = getRefForPath(path as Path);
155154
const inputRef = this.answerRefs[inputID];
156155

157-
if (this.props.apiOptions.customKeypad) {
158-
return findDOMNode(inputRef);
159-
} else {
160-
return inputRef as HTMLInputElement;
161-
}
156+
return inputRef;
162157
}
163158

164159
getInputPaths(): string[][] {

0 commit comments

Comments
 (0)