All files / src/components TextField.tsx

0% Statements 0/16
0% Branches 0/10
0% Functions 0/8
0% Lines 0/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125                                                                                                                                                                                                                                                         
import {useEffect, useState} from "react";
import styles from "./TextField.module.css";
 
/**
 * A styled text input that updates its value **in real time** at every keystroke.
 *
 * Automatically toggles between read-only and editable modes to integrate with
 * drag-based UIs (like React Flow). Calls `onCommit` when editing is completed.
 *
 * @param props - Component properties.
 * @param props.value - The current text input value.
 * @param props.setValue - Callback invoked on every keystroke to update the value.
 * @param props.onCommit - Callback invoked when editing is finalized (on blur or Enter).
 * @param props.placeholder - Optional placeholder text displayed when the input is empty.
 * @param props.className - Optional additional CSS class names.
 * @param props.id - Optional unique HTML `id` for the input element.
 * @param props.ariaLabel - Optional ARIA label for accessibility.
 * @param props.invalid - If true, applies error styling to indicate invalid input.
 *
 * @returns A styled `<input>` element that updates its value in real time.
 */
export function RealtimeTextField({
  value = "",
  setValue,
  onCommit,
  placeholder,
  className,
  id,
  ariaLabel,
  invalid = false,
} : {
  value: string,
  setValue: (value: string) => void,
  onCommit: () => void,
  placeholder?: string,
  className?: string,
  id?: string,
  ariaLabel?: string,
  invalid?: boolean,
}) {
  /** Tracks whether the input is currently read-only (for drag compatibility). */
  const [readOnly, setReadOnly] = useState(true);
 
  /** Finalizes editing and calls `onCommit` when the user exits the field. */
  const updateData  = () => {
    setReadOnly(true);
    onCommit();
  };
 
  /** Handles the Enter key — commits the input by triggering a blur event. */
  const updateOnEnter = (event: React.KeyboardEvent<HTMLInputElement>) => { 
    if (event.key === "Enter") 
      (event.target as HTMLInputElement).blur(); };
 
  return <input
    type={"text"}
    placeholder={placeholder}
    value={value}
    onChange={(e) => setValue(e.target.value)}
    onFocus={() => setReadOnly(false)}
    onBlur={updateData}
    onKeyDown={updateOnEnter}
    readOnly={readOnly}
    id={id}
    // ReactFlow uses the "drag" / "nodrag" classes to enable / disable dragging of nodes
    className={`${readOnly ? "drag" : "nodrag"} flex-1 ${styles.textField} ${invalid ? styles.invalid : ""} ${className}`}
    aria-label={ariaLabel}
  />;
}
 
/**
 * A styled text input that updates its value **only on commit** (when the user
 * presses Enter or clicks outside the input).
 *
 * Internally wraps `RealtimeTextField` and buffers input changes locally,
 * calling `setValue` only once editing is complete.
 *
 * @param props - Component properties.
 * @param props.value - The current text input value.
 * @param props.setValue - Callback invoked when the user commits the change.
 * @param props.placeholder - Optional placeholder text displayed when the input is empty.
 * @param props.className - Optional additional CSS class names.
 * @param props.id - Optional unique HTML `id` for the input element.
 * @param props.ariaLabel - Optional ARIA label for accessibility.
 * @param props.invalid - If true, applies error styling to indicate invalid input.
 *
 * @returns A styled `<input>` element that updates its parent state only on commit.
 */
export function TextField({
  value = "",
  setValue,
  placeholder,
  className,
  id,
  ariaLabel,
  invalid = false,
} : {
  value: string,
  setValue: (value: string) => void,
  placeholder?: string,
  className?: string,
  id?: string,
  ariaLabel?: string,
  invalid?: boolean,
}) {
  const [inputValue, setInputValue] = useState(value);
 
  useEffect(() => {
    setInputValue(value);
  }, [value]);
 
  const onCommit = () => setValue(inputValue);
 
  return <RealtimeTextField
    placeholder={placeholder}
    value={inputValue}
    setValue={setInputValue}
    onCommit={onCommit}
    id={id}
    className={className}
    ariaLabel={ariaLabel}
    invalid={invalid}
  />;
}