diff --git a/.changeset/neat-kiwis-relax.md b/.changeset/neat-kiwis-relax.md new file mode 100644 index 0000000000..84fc5e2d02 --- /dev/null +++ b/.changeset/neat-kiwis-relax.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus-editor": patch +--- + +[Interactive Graph Editor] Stop cursor jumps in number input fields diff --git a/packages/perseus-editor/src/components/__tests__/scrollless-number-text-field.test.tsx b/packages/perseus-editor/src/components/__tests__/scrollless-number-text-field.test.tsx index 1bce852dff..cdacd9d082 100644 --- a/packages/perseus-editor/src/components/__tests__/scrollless-number-text-field.test.tsx +++ b/packages/perseus-editor/src/components/__tests__/scrollless-number-text-field.test.tsx @@ -53,4 +53,45 @@ describe("ScrolllessNumberTextField", () => { // Assert expect(onChange).not.toHaveBeenCalled(); }); + + test("calls onFocus on focus", async () => { + // Arrange + const onFocus = jest.fn(); + render( + {}} + onFocus={onFocus} + />, + ); + + // Act + // Tab to focus on input + await userEvent.tab(); + + // Assert + expect(onFocus).toHaveBeenCalled(); + }); + + test("calls onBlur on blur", async () => { + // Arrange + const onBlur = jest.fn(); + render( + {}} + onBlur={onBlur} + />, + ); + + // Tab to focus on input + await userEvent.tab(); + + // Act + // Tab to move focus away + await userEvent.tab(); + + // Assert + expect(onBlur).toHaveBeenCalled(); + }); }); diff --git a/packages/perseus-editor/src/components/scrollless-number-text-field.tsx b/packages/perseus-editor/src/components/scrollless-number-text-field.tsx index 53553c0e64..60f9078e0d 100644 --- a/packages/perseus-editor/src/components/scrollless-number-text-field.tsx +++ b/packages/perseus-editor/src/components/scrollless-number-text-field.tsx @@ -6,8 +6,14 @@ import type {PropsFor} from "@khanacademy/wonder-blocks-core"; /** * This is a custom text field of type="number" for use in Perseus Editors. * - * This makes it so that the text field's input number updates on scroll - * without scrolling the page. + * This component makes it so that + * 1. the text field's input number updates on scroll without + * scrolling the page. + * 2. the input is controlled as long as it does not have focus. + * While it is focused, it becomes editable and emits onChange + * events. This is useful to make sure that input behavior + * remains predictable, rather than possibly having the cursor + * jump around uenxpectedly. * * NOTE 1: Native HTML number inputs do not update the number value on scroll, * they only scroll the page. Inputs in React do NOT work this way (explanation @@ -16,10 +22,14 @@ import type {PropsFor} from "@khanacademy/wonder-blocks-core"; * the page to scroll. The behavior in this component is an improvement on * the React behavior, but it's the opposite of the native HTML behavior. * - * NOTE 2: Firefox seems to have a custom override for this. Even with this - * stopPropogation, Firefox matches the native HTML behavior. + * NOTE 2: Firefox seems to have a custom override for input scroll. Even + * with this stopPropogation, Firefox matches the native HTML behavior. */ const ScrolllessNumberTextField = (props: PropsFor) => { + const {value, onChange, ...restOfProps} = props; + const [focused, setFocused] = React.useState(false); + const [wipValue, setWipValue] = React.useState(""); + const inputRef = React.useRef(null); React.useEffect(() => { @@ -39,7 +49,29 @@ const ScrolllessNumberTextField = (props: PropsFor) => { }; }, [inputRef]); - return ; + return ( + { + setWipValue(newValue); + onChange(newValue); + }} + onFocus={(e) => { + setWipValue(value); + setFocused(true); + + props.onFocus?.(e); + }} + onBlur={(e) => { + setFocused(false); + + props.onBlur?.(e); + }} + ref={inputRef} + /> + ); }; export default ScrolllessNumberTextField; diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-point-settings.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-point-settings.tsx index 103a74c783..b82dc3f938 100644 --- a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-point-settings.tsx +++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-point-settings.tsx @@ -98,6 +98,7 @@ const LockedPointSettings = (props: Props) => { onRemove, // defining point props showPoint, + error, expanded, onTogglePoint, onToggle, @@ -210,6 +211,7 @@ const LockedPointSettings = (props: Props) => { coord={coord} style={styles.spaceUnder} onChange={handleCoordChange} + error={!!error} /> {/* Toggle switch */}