import { jsx, jsxs } from 'react/jsx-runtime';
import { forwardRef, useState, useRef, useId, useLayoutEffect, useCallback, useEffect } from 'react';
import { P as Popover } from './Popover-CdRuNv3C.js';
import { S as Spinner } from './Spinner-B7tTWcP6.js';
import { T as TextField } from './TextField-WQTlqKso.js';
import { m as mergeRefs } from './useMergeRefs-Dfmtq9cI.js';
import { c as cx } from './index-De1g9FRV.js';
import { m as menuStyles } from './Menu.module-CoyI86tZ.js';
import { u as useOptionallyControlledState } from './useOptionallyControlledState-DbDuos5L.js';
import { u as useDebouncedCallback } from './useDebouncedCallback-DLkapjcP.js';

import './TypeaheadTextField.css';const option = "_option_d1rwd_1";
const styles$1 = {
	option: option
};

const SuggestionItem = forwardRef(
  ({ label, selected, disabled, className, ...rest }, ref) => /* @__PURE__ */ jsx(
    "div",
    {
      ...rest,
      ref,
      role: "option",
      "aria-selected": selected ?? false,
      "aria-disabled": disabled || void 0,
      "data-anv": "suggestion-option",
      className: cx(menuStyles["item"], styles$1["option"], className),
      children: label
    }
  )
);
SuggestionItem.displayName = "SuggestionItem";

const listbox = "_listbox_1jyom_1";
const styles = {
	listbox: listbox
};

const SUFFIX_SLOT_WIDTH = 20;
function TypeaheadTextField({
  value: valueProp,
  defaultValue,
  onChange,
  loadOptions,
  onSelectOption,
  debounceMs = 300,
  noSuggestionsLabel = "No suggestions found",
  loadingLabel = "Loading...",
  renderOption,
  openOnFocus = true,
  openOnEmptyFocus = false,
  onFocus,
  onBlur,
  onKeyDown,
  onClick,
  disabled,
  readOnly,
  loading: loadingProp,
  ...textFieldProps
}) {
  const [value, setValue] = useOptionallyControlledState({
    controlledValue: valueProp,
    defaultValue: defaultValue ?? "",
    onChange
  });
  const [open, setOpen] = useState(false);
  const [options, setOptions] = useState([]);
  const [internalLoading, setInternalLoading] = useState(false);
  const loading = loadingProp ?? internalLoading;
  const [highlightedIndex, setHighlightedIndex] = useState(-1);
  const inputRef = useRef(null);
  const listRef = useRef(null);
  const wrapperRef = useRef(null);
  const listboxId = useId();
  const optionId = (index) => `${listboxId}-option-${index}`;
  const [wrapperWidth, setWrapperWidth] = useState(0);
  useLayoutEffect(() => {
    const wrapper = wrapperRef.current;
    if (!wrapper) return;
    const measure = () => {
      const next = wrapper.offsetWidth;
      setWrapperWidth((prev) => prev === next ? prev : next);
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(wrapper);
    return () => ro.disconnect();
  }, []);
  const requestIdRef = useRef(0);
  const loadOptionsRef = useRef(loadOptions);
  loadOptionsRef.current = loadOptions;
  const performLoad = useCallback(async (search) => {
    const reqId = ++requestIdRef.current;
    try {
      const result = await Promise.resolve(loadOptionsRef.current(search));
      if (requestIdRef.current === reqId) {
        setOptions(result);
      }
    } catch {
      if (requestIdRef.current === reqId) {
        setOptions([]);
      }
    } finally {
      if (requestIdRef.current === reqId) {
        setInternalLoading(false);
      }
    }
  }, []);
  const debouncedLoad = useDebouncedCallback(performLoad, debounceMs);
  const isInteractive = !disabled && !readOnly;
  const requestOpen = useCallback(
    (search) => {
      if (!isInteractive) return;
      setOpen(true);
      setInternalLoading(true);
      debouncedLoad(search);
    },
    [debouncedLoad, isInteractive]
  );
  useEffect(() => {
    setHighlightedIndex(-1);
  }, [options]);
  useEffect(() => {
    if (!open) setHighlightedIndex(-1);
  }, [open]);
  useEffect(() => {
    if (!open || highlightedIndex < 0) return;
    const active = listRef.current?.querySelector(
      `#${CSS.escape(`${listboxId}-option-${highlightedIndex}`)}`
    );
    active?.scrollIntoView({ block: "nearest" });
  }, [highlightedIndex, open, listboxId]);
  const handleChange = (event) => {
    const next = event.target.value;
    setValue(next);
    requestOpen(next);
  };
  const handleFocus = (event) => {
    onFocus?.(event);
    if (!isInteractive || !openOnFocus) return;
    const relatedTarget = event.relatedTarget;
    if (relatedTarget && listRef.current?.contains(relatedTarget)) return;
    if (!value && !openOnEmptyFocus) return;
    requestOpen(value);
  };
  const handleClick = (event) => {
    onClick?.(event);
    if (!isInteractive || open) return;
    if (!value && !openOnEmptyFocus) return;
    requestOpen(value);
  };
  const handleBlur = (event) => {
    onBlur?.(event);
    const relatedTarget = event.relatedTarget;
    if (relatedTarget && listRef.current?.contains(relatedTarget)) return;
    setOpen(false);
  };
  const handleSelect = (option) => {
    setValue(option.label);
    setOpen(false);
    onSelectOption?.(option);
  };
  const handleKeyDown = (event) => {
    onKeyDown?.(event);
    if (event.defaultPrevented) return;
    if (event.key === "Escape" && open) {
      event.stopPropagation();
      event.preventDefault();
      setOpen(false);
      return;
    }
    if (event.key === "ArrowDown") {
      event.preventDefault();
      if (!open) {
        requestOpen(value);
        return;
      }
      if (options.length === 0) return;
      setHighlightedIndex((i) => i >= options.length - 1 ? 0 : i + 1);
      return;
    }
    if (event.key === "ArrowUp") {
      if (!open) return;
      event.preventDefault();
      if (options.length === 0) return;
      setHighlightedIndex((i) => i <= 0 ? options.length - 1 : i - 1);
      return;
    }
    if (event.key === "Enter" && open && highlightedIndex >= 0) {
      const option = options[highlightedIndex];
      if (option) {
        event.preventDefault();
        handleSelect(option);
      }
    }
  };
  const suffix = /* @__PURE__ */ jsx(
    "span",
    {
      style: {
        display: "inline-flex",
        alignItems: "center",
        justifyContent: "center",
        width: SUFFIX_SLOT_WIDTH,
        height: SUFFIX_SLOT_WIDTH
      },
      "data-anv": "typeahead-suffix",
      children: loading ? /* @__PURE__ */ jsx(Spinner, { size: "small", inherit: true }) : (
        // Invisible placeholder reserves the slot width so the textfield
        // doesn't reflow when transitioning from empty to typed.
        /* @__PURE__ */ jsx(
          "span",
          {
            "aria-hidden": true,
            style: { display: "inline-block", width: 1, height: 1 }
          }
        )
      )
    }
  );
  const renderList = () => {
    if (loading && options.length === 0) {
      return /* @__PURE__ */ jsx("div", { role: "presentation", "aria-live": "polite", "data-typeahead-status": true, children: loadingLabel });
    }
    if (options.length === 0) {
      return /* @__PURE__ */ jsx("div", { role: "presentation", "data-typeahead-status": true, children: noSuggestionsLabel });
    }
    return options.map((option, i) => /* @__PURE__ */ jsx(
      SuggestionItem,
      {
        id: optionId(i),
        label: renderOption ? renderOption(option) : option.label,
        selected: i === highlightedIndex,
        onClick: () => handleSelect(option),
        onMouseDown: (e) => e.preventDefault(),
        onMouseEnter: () => setHighlightedIndex(i)
      },
      `${option.label}-${i}`
    ));
  };
  return /* @__PURE__ */ jsxs(
    Popover,
    {
      open,
      onClose: () => setOpen(false),
      onClickOutside: () => setOpen(false),
      disableCaret: true,
      disableTriggerFocus: true,
      disableFlipFallback: true,
      noPadding: true,
      placement: "bottom-start",
      children: [
        /* @__PURE__ */ jsx(Popover.Trigger, { children: (triggerProps) => {
          const {
            onClick: _ignoreTriggerClick,
            onKeyDown: _ignoreTriggerKey,
            onFocus: _ignoreTriggerFocus,
            onBlur: _ignoreTriggerBlur,
            onMouseEnter: _ignoreTriggerEnter,
            onMouseLeave: _ignoreTriggerLeave,
            ref,
            ...rest
          } = triggerProps;
          const popoverRef = ref;
          return (
            // Wrapping div is measurement-only — it gives us the visible
            // TextField box width (which includes the suffix slot) so the
            // listbox can stretch to match. Popover positioning still anchors
            // to the inner <input> via the merged ref to avoid the 8px gap
            // that would appear if the popover measured this wrapper's
            // bottom edge.
            /* @__PURE__ */ jsx("div", { ref: wrapperRef, style: { width: "100%" }, children: /* @__PURE__ */ jsx(
              TextField,
              {
                ...textFieldProps,
                ...rest,
                ref: mergeRefs([popoverRef, inputRef]),
                value,
                disabled,
                readOnly,
                suffix,
                style: { width: "100%", ...textFieldProps.style },
                role: "combobox",
                "aria-haspopup": "listbox",
                "aria-autocomplete": "list",
                "aria-expanded": open,
                "aria-controls": listboxId,
                "aria-activedescendant": open && highlightedIndex >= 0 ? optionId(highlightedIndex) : void 0,
                onChange: handleChange,
                onFocus: handleFocus,
                onBlur: handleBlur,
                onClick: handleClick,
                onKeyDown: handleKeyDown
              }
            ) })
          );
        } }),
        /* @__PURE__ */ jsx(Popover.Content, { children: /* @__PURE__ */ jsx(
          "div",
          {
            ref: listRef,
            role: "listbox",
            id: listboxId,
            className: styles["listbox"],
            style: {
              "--typeahead-min-width": `${wrapperWidth}px`
            },
            "data-typeahead-list": true,
            children: renderList()
          }
        ) })
      ]
    }
  );
}
TypeaheadTextField.displayName = "TypeaheadTextField";

export { TypeaheadTextField as T };
//# sourceMappingURL=TypeaheadTextField-BkLECKZY.js.map
