// packages/block-editor/src/components/url-input/index.js
import clsx from "clsx";
import { __, sprintf, _n } from "@wordpress/i18n";
import { Component, createRef } from "@wordpress/element";
import { UP, DOWN, ENTER, TAB } from "@wordpress/keycodes";
import {
  BaseControl,
  Button,
  Spinner,
  withSpokenMessages,
  Popover,
  privateApis as componentsPrivateApis
} from "@wordpress/components";
import {
  compose,
  debounce,
  withInstanceId,
  withSafeTimeout
} from "@wordpress/compose";
import { withSelect } from "@wordpress/data";
import { isURL } from "@wordpress/url";
import { store as blockEditorStore } from "../../store/index.mjs";
import { unlock } from "../../lock-unlock.mjs";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { createElement } from "react";
var { ValidatedInputControl } = unlock(componentsPrivateApis);
function isFunction(maybeFunc) {
  return typeof maybeFunc === "function";
}
var URLInput = class extends Component {
  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
    this.onFocus = this.onFocus.bind(this);
    this.onKeyDown = this.onKeyDown.bind(this);
    this.selectLink = this.selectLink.bind(this);
    this.handleOnClick = this.handleOnClick.bind(this);
    this.bindSuggestionNode = this.bindSuggestionNode.bind(this);
    this.autocompleteRef = props.autocompleteRef || createRef();
    this.inputRef = props.inputRef || createRef();
    this.updateSuggestions = debounce(
      this.updateSuggestions.bind(this),
      200
    );
    this.suggestionNodes = [];
    this.suggestionsRequest = null;
    this.state = {
      suggestions: [],
      showSuggestions: false,
      suggestionsValue: null,
      selectedSuggestion: null,
      suggestionsListboxId: "",
      suggestionOptionIdPrefix: ""
    };
  }
  componentDidUpdate(prevProps) {
    const { showSuggestions, selectedSuggestion } = this.state;
    const { value, __experimentalShowInitialSuggestions = false } = this.props;
    if (showSuggestions && selectedSuggestion !== null && this.suggestionNodes[selectedSuggestion]) {
      this.suggestionNodes[selectedSuggestion].scrollIntoView({
        behavior: "instant",
        block: "nearest",
        inline: "nearest"
      });
    }
    if (prevProps.value !== value && !this.props.disableSuggestions) {
      if (value?.length) {
        this.updateSuggestions(value);
      } else if (__experimentalShowInitialSuggestions) {
        this.updateSuggestions();
      }
    }
  }
  componentDidMount() {
    if (this.shouldShowInitialSuggestions()) {
      this.updateSuggestions();
    }
  }
  componentWillUnmount() {
    this.suggestionsRequest?.cancel?.();
    this.suggestionsRequest = null;
  }
  bindSuggestionNode(index) {
    return (ref) => {
      this.suggestionNodes[index] = ref;
    };
  }
  shouldShowInitialSuggestions() {
    const { __experimentalShowInitialSuggestions = false, value } = this.props;
    return __experimentalShowInitialSuggestions && !(value && value.length);
  }
  updateSuggestions(value = "") {
    const {
      __experimentalFetchLinkSuggestions: fetchLinkSuggestions,
      __experimentalHandleURLSuggestions: handleURLSuggestions
    } = this.props;
    if (!fetchLinkSuggestions) {
      return;
    }
    const isInitialSuggestions = !value?.length;
    value = value.trim();
    if (!isInitialSuggestions && (value.length < 2 || !handleURLSuggestions && isURL(value))) {
      this.suggestionsRequest?.cancel?.();
      this.suggestionsRequest = null;
      this.setState({
        suggestions: [],
        showSuggestions: false,
        suggestionsValue: value,
        selectedSuggestion: null,
        loading: false
      });
      return;
    }
    this.setState({
      selectedSuggestion: null,
      loading: true
    });
    const request = fetchLinkSuggestions(value, {
      isInitialSuggestions
    });
    request.then((suggestions) => {
      if (this.suggestionsRequest !== request) {
        return;
      }
      this.setState({
        suggestions,
        suggestionsValue: value,
        loading: false,
        showSuggestions: !!suggestions.length
      });
      if (!!suggestions.length) {
        this.props.debouncedSpeak(
          sprintf(
            /* translators: %d: number of results. */
            _n(
              "%d result found, use up and down arrow keys to navigate.",
              "%d results found, use up and down arrow keys to navigate.",
              suggestions.length
            ),
            suggestions.length
          ),
          "assertive"
        );
      } else {
        this.props.debouncedSpeak(
          __("No results."),
          "assertive"
        );
      }
    }).catch(() => {
      if (this.suggestionsRequest !== request) {
        return;
      }
      this.setState({
        loading: false
      });
    }).finally(() => {
      if (this.suggestionsRequest === request) {
        this.suggestionsRequest = null;
      }
    });
    this.suggestionsRequest = request;
  }
  onChange(newValue) {
    this.props.onChange(newValue);
  }
  onFocus() {
    const { suggestions } = this.state;
    const { disableSuggestions, value } = this.props;
    if (value && !disableSuggestions && !(suggestions && suggestions.length) && this.suggestionsRequest === null) {
      this.updateSuggestions(value);
    }
  }
  onKeyDown(event) {
    this.props.onKeyDown?.(event);
    const { showSuggestions, selectedSuggestion, suggestions, loading } = this.state;
    if (!showSuggestions || !suggestions.length || loading) {
      switch (event.keyCode) {
        // When UP is pressed, if the caret is at the start of the text, move it to the 0
        // position.
        case UP: {
          if (0 !== event.target.selectionStart) {
            event.preventDefault();
            event.target.setSelectionRange(0, 0);
          }
          break;
        }
        // When DOWN is pressed, if the caret is not at the end of the text, move it to the
        // last position.
        case DOWN: {
          if (this.props.value.length !== event.target.selectionStart) {
            event.preventDefault();
            event.target.setSelectionRange(
              this.props.value.length,
              this.props.value.length
            );
          }
          break;
        }
        // Submitting while loading should trigger onSubmit.
        case ENTER: {
          if (this.props.onSubmit) {
            event.preventDefault();
            this.props.onSubmit(null, event);
          }
          break;
        }
      }
      return;
    }
    const suggestion = this.state.suggestions[this.state.selectedSuggestion];
    switch (event.keyCode) {
      case UP: {
        event.preventDefault();
        const previousIndex = !selectedSuggestion ? suggestions.length - 1 : selectedSuggestion - 1;
        this.setState({
          selectedSuggestion: previousIndex
        });
        break;
      }
      case DOWN: {
        event.preventDefault();
        const nextIndex = selectedSuggestion === null || selectedSuggestion === suggestions.length - 1 ? 0 : selectedSuggestion + 1;
        this.setState({
          selectedSuggestion: nextIndex
        });
        break;
      }
      case TAB: {
        if (this.state.selectedSuggestion !== null) {
          this.selectLink(suggestion);
          this.props.speak(__("Link selected."));
        }
        break;
      }
      case ENTER: {
        event.preventDefault();
        if (this.state.selectedSuggestion !== null) {
          this.selectLink(suggestion);
          if (this.props.onSubmit) {
            this.props.onSubmit(suggestion, event);
          }
        } else if (this.props.onSubmit) {
          this.props.onSubmit(null, event);
        }
        break;
      }
    }
  }
  selectLink(suggestion) {
    this.props.onChange(suggestion.url, suggestion);
    this.setState({
      selectedSuggestion: null,
      showSuggestions: false
    });
  }
  handleOnClick(suggestion) {
    this.selectLink(suggestion);
    this.inputRef.current.focus();
  }
  static getDerivedStateFromProps({
    value,
    instanceId,
    disableSuggestions,
    __experimentalShowInitialSuggestions = false
  }, { showSuggestions }) {
    let shouldShowSuggestions = showSuggestions;
    const hasValue = value && value.length;
    if (!__experimentalShowInitialSuggestions && !hasValue) {
      shouldShowSuggestions = false;
    }
    if (disableSuggestions === true) {
      shouldShowSuggestions = false;
    }
    return {
      showSuggestions: shouldShowSuggestions,
      suggestionsListboxId: `block-editor-url-input-suggestions-${instanceId}`,
      suggestionOptionIdPrefix: `block-editor-url-input-suggestion-${instanceId}`
    };
  }
  render() {
    return /* @__PURE__ */ jsxs(Fragment, { children: [
      this.renderControl(),
      this.renderSuggestions()
    ] });
  }
  renderControl() {
    const {
      label = null,
      className,
      isFullWidth,
      instanceId,
      placeholder = __("Paste URL or type to search"),
      __experimentalRenderControl: renderControl,
      value = "",
      hideLabelFromVision = false,
      help = null,
      disabled = false,
      customValidity,
      markWhenOptional
    } = this.props;
    const {
      loading,
      showSuggestions,
      selectedSuggestion,
      suggestionsListboxId,
      suggestionOptionIdPrefix
    } = this.state;
    const inputId = `url-input-control-${instanceId}`;
    const controlProps = {
      id: inputId,
      // Passes attribute to label for the for attribute
      label,
      className: clsx("block-editor-url-input", className, {
        "is-full-width": isFullWidth
      }),
      hideLabelFromVision
    };
    const inputProps = {
      id: inputId,
      value,
      required: true,
      type: "text",
      name: inputId,
      autoComplete: "off",
      onChange: disabled ? () => {
      } : this.onChange,
      // Disable onChange when disabled
      onFocus: disabled ? () => {
      } : this.onFocus,
      // Disable onFocus when disabled
      placeholder,
      onKeyDown: disabled ? () => {
      } : this.onKeyDown,
      // Disable onKeyDown when disabled
      role: "combobox",
      "aria-label": label ? void 0 : __("URL"),
      // Ensure input always has an accessible label
      "aria-expanded": showSuggestions,
      "aria-autocomplete": "list",
      "aria-owns": suggestionsListboxId,
      "aria-activedescendant": selectedSuggestion !== null ? `${suggestionOptionIdPrefix}-${selectedSuggestion}` : void 0,
      ref: this.inputRef,
      disabled,
      suffix: this.props.suffix,
      help
    };
    const validationProps = {
      customValidity,
      // Suppress the "(Required)" indicator in the label.
      // The field is still required for validation, but the indicator
      // can be hidden when markWhenOptional is set to true.
      ...markWhenOptional !== void 0 && {
        markWhenOptional
      }
    };
    if (renderControl) {
      return renderControl(controlProps, inputProps, loading);
    }
    return /* @__PURE__ */ jsxs(BaseControl, { ...controlProps, children: [
      /* @__PURE__ */ jsx(
        ValidatedInputControl,
        {
          ...inputProps,
          ...validationProps,
          __next40pxDefaultSize: true
        }
      ),
      loading && /* @__PURE__ */ jsx(Spinner, {})
    ] });
  }
  renderSuggestions() {
    const {
      className,
      __experimentalRenderSuggestions: renderSuggestions
    } = this.props;
    const {
      showSuggestions,
      suggestions,
      suggestionsValue,
      selectedSuggestion,
      suggestionsListboxId,
      suggestionOptionIdPrefix,
      loading
    } = this.state;
    if (!showSuggestions || suggestions.length === 0) {
      return null;
    }
    const suggestionsListProps = {
      id: suggestionsListboxId,
      ref: this.autocompleteRef,
      role: "listbox"
    };
    const buildSuggestionItemProps = (suggestion, index) => {
      return {
        role: "option",
        tabIndex: "-1",
        id: `${suggestionOptionIdPrefix}-${index}`,
        ref: this.bindSuggestionNode(index),
        "aria-selected": index === selectedSuggestion ? true : void 0
      };
    };
    if (isFunction(renderSuggestions)) {
      return renderSuggestions({
        suggestions,
        selectedSuggestion,
        suggestionsListProps,
        buildSuggestionItemProps,
        isLoading: loading,
        handleSuggestionClick: this.handleOnClick,
        isInitialSuggestions: !suggestionsValue?.length,
        currentInputValue: suggestionsValue
      });
    }
    return /* @__PURE__ */ jsx(Popover, { placement: "bottom", focusOnMount: false, children: /* @__PURE__ */ jsx(
      "div",
      {
        ...suggestionsListProps,
        className: clsx("block-editor-url-input__suggestions", {
          [`${className}__suggestions`]: className
        }),
        children: suggestions.map((suggestion, index) => /* @__PURE__ */ createElement(
          Button,
          {
            __next40pxDefaultSize: true,
            ...buildSuggestionItemProps(suggestion, index),
            key: suggestion.id,
            className: clsx(
              "block-editor-url-input__suggestion",
              {
                "is-selected": index === selectedSuggestion
              }
            ),
            onClick: () => this.handleOnClick(suggestion)
          },
          suggestion.title
        ))
      }
    ) });
  }
};
var url_input_default = compose(
  withSafeTimeout,
  withSpokenMessages,
  withInstanceId,
  withSelect((select, props) => {
    if (isFunction(props.__experimentalFetchLinkSuggestions)) {
      return;
    }
    const { getSettings } = select(blockEditorStore);
    return {
      __experimentalFetchLinkSuggestions: getSettings().__experimentalFetchLinkSuggestions
    };
  })
)(URLInput);
export {
  url_input_default as default
};
//# sourceMappingURL=index.mjs.map
