All files / libs/design-system/ext/src/lib/forms/formik-auto-complete-input FormikAutoCompleteInput.tsx

0% Statements 0/101
0% Branches 0/1
0% Functions 0/1
0% Lines 0/101

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                                                                                                                                                                                                           
import { memo, useCallback, useRef, useState } from 'react';

import {
  AutoCompleteInput,
  type AutoCompleteInputProps,
  type AutoCompleteOption,
} from '@allshares/studio-design-system';
import { useFormikFieldAdapter, type FormikFieldProps } from '@amalia/ext/formik';
import { type Merge } from '@amalia/ext/typescript';

type AutoCompleteInputFormikValue = Omit<
  AutoCompleteInputProps,
  'inputValue' | 'onChangeInputValue' | 'onClickOption'
> & {
  value?: string;
  onChange?: (value: string) => void;
};

/**
 * AutoCompleteInput props adapted for Formik.
 *
 * Replaces the dual-state API (inputValue + onClickOption) with a single Formik-managed
 * `value` (the selected option value). The input text is handled internally.
 */
export type FormikAutoCompleteInputProps = Merge<
  FormikFieldProps<AutoCompleteInputFormikValue>,
  {
    /** Called when the input text changes (useful for async search/filtering). */
    onSearch?: (search: string) => void;
    /** Called when an option is selected (for side effects beyond Formik). */
    onSelectOption?: (option: AutoCompleteOption) => void;
  }
>;

export const FormikAutoCompleteInput = memo(function FormikAutoCompleteInput({
  validate,
  onSearch,
  onSelectOption,
  ...props
}: FormikAutoCompleteInputProps) {
  const { onBlur, onChange, value, ...formikFieldProps } = useFormikFieldAdapter<AutoCompleteInputFormikValue['value']>(
    {
      ...props,
      validate,
    },
  );

  const [inputValue, setInputValue] = useState(value || '');
  // Option selection triggers an immediate blur in the design-system input.
  // Keep track of that case so Formik can process the new value before marking the field as touched.
  const shouldDeferBlurRef = useRef(false);

  const handleChangeInputValue = useCallback(
    (text: string) => {
      setInputValue(text);
      onSearch?.(text);
    },
    [onSearch],
  );

  const handleClickOption = useCallback(
    (option: AutoCompleteOption) => {
      // The next blur comes from selecting an option, not from the user leaving the field manually.
      shouldDeferBlurRef.current = true;
      onChange(option.value);
      setInputValue(option.value);
      onSelectOption?.(option);
    },
    [onChange, onSelectOption],
  );

  const handleBlur = useCallback(
    (event: unknown) => {
      if (!shouldDeferBlurRef.current) {
        onBlur?.(event);

        return;
      }

      shouldDeferBlurRef.current = false;

      // Defer Formik's blur handling to the next tick so validation sees the selected value
      // instead of the previous empty one.
      setTimeout(() => {
        onBlur?.(event);
      }, 0);
    },
    [onBlur],
  );

  return (
    <AutoCompleteInput
      {...props}
      {...formikFieldProps}
      inputValue={inputValue}
      onBlur={handleBlur}
      onChangeInputValue={handleChangeInputValue}
      onClickOption={handleClickOption}
    />
  );
});