fix: add cursor state to not skip to end on input components (#9375)
* Add cursor handling to input component * add cursor handling to text area component * [autofix.ci] apply automated fixes * Modified tests to check cursor position * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
4ab322290b
commit
181606fd80
6 changed files with 72 additions and 4 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { PopoverAnchor } from "@radix-ui/react-popover";
|
||||
import { uniqueId } from "lodash";
|
||||
|
||||
import { X } from "lucide-react";
|
||||
import { type ReactNode, useMemo, useState } from "react";
|
||||
import { type ReactNode, useEffect, useMemo, useState } from "react";
|
||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
|
@ -189,12 +189,20 @@ const CustomInputPopover = ({
|
|||
hasRefreshButton,
|
||||
}) => {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [cursor, setCursor] = useState<number | null>(null);
|
||||
const memoizedOptions = useMemo(() => new Set<string>(options), [options]);
|
||||
|
||||
const PopoverContentInput = editNode
|
||||
? PopoverContent
|
||||
: PopoverContentWithoutPortal;
|
||||
|
||||
// Restore cursor position after value changes
|
||||
useEffect(() => {
|
||||
if (cursor !== null && refInput.current) {
|
||||
refInput.current.setSelectionRange(cursor, cursor);
|
||||
}
|
||||
}, [cursor, value]);
|
||||
|
||||
const handleRemoveOption = (
|
||||
optionToRemove: string,
|
||||
e: React.MouseEvent<HTMLButtonElement>,
|
||||
|
|
@ -270,7 +278,7 @@ const CustomInputPopover = ({
|
|||
autoComplete="off"
|
||||
onFocus={() => setIsFocused(true)}
|
||||
autoFocus={autoFocus}
|
||||
id={id + uniqueId()}
|
||||
id={id}
|
||||
ref={refInput}
|
||||
type={!pwdVisible && password ? "password" : "text"}
|
||||
onBlur={() => {
|
||||
|
|
@ -292,7 +300,10 @@ const CustomInputPopover = ({
|
|||
? ""
|
||||
: placeholder
|
||||
}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setCursor(e.target.selectionStart);
|
||||
onChange?.(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown?.(e);
|
||||
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { PopoverAnchor } from "@radix-ui/react-popover";
|
||||
import { useEffect, useState } from "react";
|
||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import {
|
||||
Command,
|
||||
|
|
@ -40,11 +41,21 @@ const CustomInputPopoverObject = ({
|
|||
handleKeyDown,
|
||||
showOptions,
|
||||
}) => {
|
||||
const [cursor, setCursor] = useState<number | null>(null);
|
||||
|
||||
const PopoverContentInput = editNode
|
||||
? PopoverContent
|
||||
: PopoverContentWithoutPortal;
|
||||
|
||||
// Restore cursor position after value changes
|
||||
useEffect(() => {
|
||||
if (cursor !== null && refInput.current) {
|
||||
refInput.current.setSelectionRange(cursor, cursor);
|
||||
}
|
||||
}, [cursor, value]);
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setCursor(e.target.selectionStart);
|
||||
onChange && onChange(e.target.value);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export default function InputComponent({
|
|||
hasRefreshButton = false,
|
||||
}: InputComponentType): JSX.Element {
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
const [cursor, setCursor] = useState<number | null>(null);
|
||||
const refInput = useRef<HTMLInputElement>(null);
|
||||
const [showOptions, setShowOptions] = useState<boolean>(false);
|
||||
|
||||
|
|
@ -54,6 +55,13 @@ export default function InputComponent({
|
|||
}
|
||||
}, [disabled]);
|
||||
|
||||
// Restore cursor position after value changes
|
||||
useEffect(() => {
|
||||
if (cursor !== null && refInput.current) {
|
||||
refInput.current.setSelectionRange(cursor, cursor);
|
||||
}
|
||||
}, [cursor, value]);
|
||||
|
||||
function onInputLostFocus(event): void {
|
||||
if (onBlur) onBlur(event);
|
||||
}
|
||||
|
|
@ -83,6 +91,7 @@ export default function InputComponent({
|
|||
)}
|
||||
placeholder={password && editNode ? "Key" : placeholder}
|
||||
onChange={(e) => {
|
||||
setCursor(e.target.selectionStart);
|
||||
if (onChangeFolderName) {
|
||||
return onChangeFolderName(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export default function TextAreaComponent({
|
|||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const [cursor, setCursor] = useState<number | null>(null);
|
||||
|
||||
const isWebhook = useMemo(
|
||||
() => nodeInformationMetadata?.nodeType === "webhook",
|
||||
|
|
@ -100,6 +101,13 @@ export default function TextAreaComponent({
|
|||
}
|
||||
}, [isWebhook, value, nodeInformationMetadata, handleOnNewValue]);
|
||||
|
||||
// Restore cursor position after value changes
|
||||
useEffect(() => {
|
||||
if (cursor !== null && inputRef.current) {
|
||||
inputRef.current.setSelectionRange(cursor, cursor);
|
||||
}
|
||||
}, [cursor, value]);
|
||||
|
||||
const getInputClassName = () => {
|
||||
return cn(
|
||||
inputClasses.base({ isFocused, password: password! }),
|
||||
|
|
@ -111,6 +119,7 @@ export default function TextAreaComponent({
|
|||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCursor(e.target.selectionStart);
|
||||
handleOnNewValue({ value: e.target.value });
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,19 @@ test(
|
|||
expect(false).toBeTruthy();
|
||||
}
|
||||
|
||||
// Test cursor position preservation
|
||||
const input = page.getByTestId("popover-anchor-input-collection_name");
|
||||
await input.click();
|
||||
await input.press("Home"); // Move cursor to start
|
||||
await input.press("ArrowRight"); // Move cursor to position 1
|
||||
await input.press("ArrowRight"); // Move cursor to position 2
|
||||
await input.pressSequentially("X", { delay: 100 }); // Type at position 2
|
||||
const cursorValue = await input.inputValue();
|
||||
if (!cursorValue.startsWith("coX")) {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
await input.fill("collection_name_test_123123123!@#$&*(&%$@");
|
||||
|
||||
await page.getByTestId("div-generic-node").click();
|
||||
|
||||
await page.getByTestId("edit-button-modal").last().click();
|
||||
|
|
|
|||
|
|
@ -44,6 +44,21 @@ test(
|
|||
"test test test test test test test test test test test !@#%*)( 123456789101010101010101111111111 !!!!!!!!!!",
|
||||
);
|
||||
|
||||
// Test cursor position preservation
|
||||
const textInput = page.getByTestId("textarea_str_text");
|
||||
await textInput.click();
|
||||
await textInput.press("Home"); // Move cursor to start
|
||||
await textInput.press("ArrowRight"); // Move cursor to position 1
|
||||
await textInput.press("ArrowRight"); // Move cursor to position 2
|
||||
await textInput.pressSequentially("Y", { delay: 100 }); // Type at position 2
|
||||
const cursorValue = await textInput.inputValue();
|
||||
if (!cursorValue.startsWith("teY")) {
|
||||
expect(false).toBeTruthy();
|
||||
}
|
||||
await textInput.fill(
|
||||
"test test test test test test test test test test test !@#%*)( 123456789101010101010101111111111 !!!!!!!!!!",
|
||||
);
|
||||
|
||||
await page
|
||||
.getByTestId("button_open_text_area_modal_textarea_str_text")
|
||||
.click();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue