LCOV - code coverage report
Current view: top level - dashql-app/src/ui/foundations - name_input.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 43 43
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : interface Props {
       4              :     /// The persisted name, null if there is none
       5              :     name: string | null;
       6              :     /// Commit an edited name, blank input means no name
       7              :     onCommit: (name: string) => void;
       8              :     /// Abandon the edit with Escape
       9              :     onCancel?: () => void;
      10              :     /// The accessible label
      11              :     ariaLabel: string;
      12              :     /// The placeholder while there is no name
      13              :     placeholder: string;
      14              :     /// The class of the input
      15              :     className?: string;
      16              : }
      17              : 
      18              : /// An editable name. The input owns its draft so typing does not rerender the surrounding view;
      19              : /// blur or Enter commits the draft, Escape restores the persisted name.
      20            1 : export const NameInput = React.forwardRef<HTMLInputElement, Props>((props, forwardedRef) => {
      21            1 :     const inputRef = React.useRef<HTMLInputElement | null>(null);
      22            1 :     const skipBlurCommitRef = React.useRef(false);
      23            1 :     React.useImperativeHandle(forwardedRef, () => inputRef.current!, []);
      24              :     // Re-sync the draft when the persisted name changes (e.g. a rename from elsewhere, or switching
      25              :     // the named object while the input stays mounted).
      26            1 :     React.useEffect(() => {
      27            1 :         if (inputRef.current != null) inputRef.current.value = props.name ?? '';
      28            1 :     }, [props.name]);
      29              : 
      30            1 :     const commitOnBlur = React.useCallback((e: React.FocusEvent<HTMLInputElement>) => {
      31            1 :         if (!skipBlurCommitRef.current) props.onCommit(e.currentTarget.value);
      32            1 :     }, [props.onCommit]);
      33              :     // Drop the focus without a commit. blur() fires its event synchronously, and none at all while the
      34              :     // window lacks focus, so the keys cannot leave it to the event to commit or to reset the flag.
      35            1 :     const blurWithoutCommit = React.useCallback((input: HTMLInputElement) => {
      36            1 :         skipBlurCommitRef.current = true;
      37            1 :         input.blur();
      38            1 :         skipBlurCommitRef.current = false;
      39            1 :     }, []);
      40            1 :     const onKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
      41            1 :         if (e.key === 'Enter') {
      42            1 :             props.onCommit(e.currentTarget.value);
      43            1 :             blurWithoutCommit(e.currentTarget);
      44            1 :         } else if (e.key === 'Escape') {
      45              :             // Abandon the edit: restore the persisted value and drop focus.
      46            1 :             e.currentTarget.value = props.name ?? '';
      47            1 :             blurWithoutCommit(e.currentTarget);
      48            1 :             props.onCancel?.();
      49            1 :         }
      50            1 :         e.stopPropagation();
      51            1 :     }, [props.name, props.onCommit, props.onCancel, blurWithoutCommit]);
      52            1 :     const onPointerDown = React.useCallback((e: React.PointerEvent<HTMLInputElement>) => {
      53              :         // Inputs are often portaled from sortable rows. Keep selection gestures from bubbling
      54              :         // through the React tree and activating a drag sensor.
      55            1 :         e.stopPropagation();
      56            1 :     }, []);
      57              : 
      58            1 :     return (
      59            1 :         <input
      60            1 :             ref={inputRef}
      61            1 :             className={props.className}
      62            1 :             type="text"
      63            1 :             aria-label={props.ariaLabel}
      64            1 :             defaultValue={props.name ?? ''}
      65            1 :             placeholder={props.placeholder}
      66            1 :             spellCheck={false}
      67            1 :             onBlur={commitOnBlur}
      68            1 :             onKeyDown={onKeyDown}
      69            1 :             onPointerDown={onPointerDown}
      70            1 :         />
      71              :     );
      72            1 : });
        

Generated by: LCOV version 2.0-1