From 3545ae60b5b41d2fc863eaa68f60bdf67ca5a716 Mon Sep 17 00:00:00 2001 From: Cat Johnson Date: Wed, 1 Apr 2026 15:40:09 -0700 Subject: [PATCH 01/13] [catjohnson/lems-2748] Initial type fixes. --- .../src/components/blur-input.tsx | 4 +- .../src/components/drag-target.tsx | 22 ++-- .../src/components/dropdown-option.tsx | 14 +-- .../components/form-wrapped-text-field.tsx | 2 +- .../src/components/graph-settings.tsx | 118 ++++++++++-------- .../src/components/json-editor.tsx | 33 ++--- .../perseus-editor/src/components/link.ts | 4 +- packages/perseus-editor/src/editor.tsx | 2 +- 8 files changed, 105 insertions(+), 94 deletions(-) diff --git a/packages/perseus-editor/src/components/blur-input.tsx b/packages/perseus-editor/src/components/blur-input.tsx index e7a71a98c45..d8603399d46 100644 --- a/packages/perseus-editor/src/components/blur-input.tsx +++ b/packages/perseus-editor/src/components/blur-input.tsx @@ -39,11 +39,11 @@ class BlurInput extends React.Component { this.setState({value: nextProps.value}); } - handleChange: (e: any) => void = (e) => { + handleChange = (e: React.ChangeEvent) => { this.setState({value: e.target.value}); }; - handleBlur: (e: any) => void = (e) => { + handleBlur = (e: React.FocusEvent) => { this.props.onChange(e.target.value); }; diff --git a/packages/perseus-editor/src/components/drag-target.tsx b/packages/perseus-editor/src/components/drag-target.tsx index 956f1aea9cd..855e462c7d6 100644 --- a/packages/perseus-editor/src/components/drag-target.tsx +++ b/packages/perseus-editor/src/components/drag-target.tsx @@ -16,16 +16,14 @@ import * as React from "react"; type Props = { - onDrop: (e: DragEvent) => void; - component?: any; - shouldDragHighlight: (any) => boolean; - style?: any; - children?: any; + onDrop: (e: React.DragEvent) => void; + shouldDragHighlight: (e: React.DragEvent) => boolean; + style?: React.CSSProperties; + children?: React.ReactNode; className?: string; }; type DefaultProps = { - component: Props["component"]; shouldDragHighlight: Props["shouldDragHighlight"]; }; @@ -34,11 +32,10 @@ type State = { }; class DragTarget extends React.Component { static defaultProps: DefaultProps = { - component: "div", shouldDragHighlight: () => true, }; - constructor(props) { + constructor(props: Props) { super(props); this.state = { dragHover: false, @@ -51,7 +48,7 @@ class DragTarget extends React.Component { this.handleDragEnter = this.handleDragEnter.bind(this); } - handleDrop(e: DragEvent) { + handleDrop(e: React.DragEvent) { e.stopPropagation(); e.preventDefault(); this.setState({dragHover: false}); @@ -62,7 +59,7 @@ class DragTarget extends React.Component { this.setState({dragHover: false}); } - handleDragOver(e) { + handleDragOver(e: React.DragEvent) { e.preventDefault(); } @@ -70,21 +67,20 @@ class DragTarget extends React.Component { this.setState({dragHover: false}); } - handleDragEnter(e) { + handleDragEnter(e: React.DragEvent) { this.setState({dragHover: this.props.shouldDragHighlight(e)}); } render() { const opacity = this.state.dragHover ? {opacity: 0.3} : {}; const { - component: Component, // eslint-disable-next-line @typescript-eslint/no-unused-vars shouldDragHighlight, ...forwardProps } = this.props; return ( - { // @ts-expect-error - TS2564 - Property 'node' has no initializer and is not definitely assigned in the constructor. node: HTMLDivElement; - handleKeyDown(event: any): void { + handleKeyDown(event: React.KeyboardEvent): void { const {onDropdownClose} = this.props; const pressedKey = event.key; - const focusedElement = event.target; + const focusedElement = event.currentTarget; - if (pressedKey === "ArrowDown" && focusedElement.nextSibling) { + if (pressedKey === "ArrowDown" && focusedElement.nextElementSibling) { event.preventDefault(); - focusedElement.nextSibling.focus(); + (focusedElement.nextElementSibling as HTMLElement).focus(); } - if (pressedKey === "ArrowUp" && focusedElement.previousSibling) { + if (pressedKey === "ArrowUp" && focusedElement.previousElementSibling) { event.preventDefault(); - focusedElement.previousSibling.focus(); + (focusedElement.previousElementSibling as HTMLElement).focus(); } if ( pressedKey === "ArrowUp" && - !focusedElement.previousSibling && + !focusedElement.previousElementSibling && onDropdownClose ) { event.preventDefault(); diff --git a/packages/perseus-editor/src/components/form-wrapped-text-field.tsx b/packages/perseus-editor/src/components/form-wrapped-text-field.tsx index 2831f042846..2a30189f5c2 100644 --- a/packages/perseus-editor/src/components/form-wrapped-text-field.tsx +++ b/packages/perseus-editor/src/components/form-wrapped-text-field.tsx @@ -120,7 +120,7 @@ class FormWrappedTextField extends React.Component { } = this.props; const {focused} = this.state; - const extraStyles: any = {}; + const extraStyles: React.CSSProperties = {}; const spanStyle = [styles.input, styles.container]; // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions diff --git a/packages/perseus-editor/src/components/graph-settings.tsx b/packages/perseus-editor/src/components/graph-settings.tsx index 5b909b115a9..38aba435063 100644 --- a/packages/perseus-editor/src/components/graph-settings.tsx +++ b/packages/perseus-editor/src/components/graph-settings.tsx @@ -6,7 +6,6 @@ import {KhanMath} from "@khanacademy/kmath"; import { components, interactiveSizes, - Changeable, Dependencies, Util, } from "@khanacademy/perseus"; @@ -15,7 +14,11 @@ import * as React from "react"; import ReactDOM from "react-dom"; import _ from "underscore"; -import type {Coords, MarkingsType} from "@khanacademy/perseus-core"; +import type { + Coords, + MarkingsType, + PerseusImageBackground, +} from "@khanacademy/perseus-core"; const {ButtonGroup, InfoTip, RangeInput} = components; @@ -25,7 +28,7 @@ const defaultBackgroundImage = { height: 0, } as const; -function numSteps(range: any, step: any) { +function numSteps(range: [number, number], step: number) { return Math.floor((range[1] - range[0]) / step); } type Props = { @@ -39,14 +42,15 @@ type Props = { gridStep: [number, number]; snapStep: [number, number]; valid: boolean; - backgroundImage: any; + backgroundImage: PerseusImageBackground; markings: MarkingsType; showProtractor?: boolean; showRuler?: boolean; showTooltips?: boolean; rulerLabel: string; rulerTicks: number; -} & Changeable.ChangeableProps; + onChange: (values: Record) => void; +}; type DefaultProps = { editableSettings: Props["editableSettings"]; @@ -67,12 +71,12 @@ type DefaultProps = { }; type State = { - labelsTextbox: string[]; + labelsTextbox: readonly string[]; gridStepTextbox: number[]; snapStepTextbox: number[]; stepTextbox: number[]; - rangeTextbox: any[]; - backgroundImage: any; + rangeTextbox: [number, number][]; + backgroundImage: PerseusImageBackground; }; class GraphSettings extends React.Component { @@ -104,11 +108,10 @@ class GraphSettings extends React.Component { _isMounted = false; - constructor(props) { + constructor(props: Props) { super(props); this.state = this.getInitialState(); - this.change = this.change.bind(this); this.changeBackgroundUrl = this.changeBackgroundUrl.bind(this); this.changeGraph = this.changeGraph.bind(this); this.changeGridStep = this.changeGridStep.bind(this); @@ -132,7 +135,7 @@ class GraphSettings extends React.Component { this.changeGraph = _.debounce(this.changeGraph, 300); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: Props) { if ( !_.isEqual(this.props.labels, nextProps.labels) || !_.isEqual(this.props.gridStep, nextProps.gridStep) || @@ -149,7 +152,7 @@ class GraphSettings extends React.Component { this._isMounted = false; } - stateFromProps(props) { + stateFromProps(props: Props): State { return { labelsTextbox: props.labels, gridStepTextbox: props.gridStep, @@ -160,29 +163,25 @@ class GraphSettings extends React.Component { }; } - // TODO(benchristel): Refactor this component to be an ES6 class, so we can - // type change as ChangeFn. - change(...args) { - // TODO(LEMS-2656): remove TS suppression - // @ts-expect-error: Argument of type 'any[]' is not assignable to parameter of type '[newPropsOrSinglePropName: string | { [key: string]: any; }, propValue?: any, callback?: (() => unknown) | undefined]'. Target requires 1 element(s) but source may have fewer. - return Changeable.change.apply(this, args); - } - - changeRulerLabel(e) { - this.change({rulerLabel: e.target.value}); + changeRulerLabel(e: React.ChangeEvent) { + this.props.onChange({rulerLabel: e.target.value}); } - changeRulerTicks(e) { - this.change({rulerTicks: +e.target.value}); + changeRulerTicks(e: React.ChangeEvent) { + this.props.onChange({rulerTicks: +e.target.value}); } - changeBackgroundUrl(e) { + changeBackgroundUrl( + e: + | React.FocusEvent + | React.KeyboardEvent, + ) { // Only continue on blur or "enter" - if (e.type === "keypress" && e.key !== "Enter") { + if (e.type === "keypress" && "key" in e && e.key !== "Enter") { return; } - const setUrl = (url, width: number, height: number) => { + const setUrl = (url: string | null, width: number, height: number) => { const image = _.clone(this.props.backgroundImage); image.url = url; image.width = width; @@ -208,7 +207,7 @@ class GraphSettings extends React.Component { } } - renderLabelChoices(choices) { + renderLabelChoices(choices: ReadonlyArray<[string, string]>) { return _.map(choices, function ([name, value]) { return (