{"version":3,"file":"use-combobox.mjs","sources":["../../../../src/components/Combobox/use-combobox/use-combobox.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useUncontrolled } from '@raikou/hooks';\nimport { getFirstIndex, getNextIndex, getPreviousIndex } from './get-index/get-index';\n\nexport type ComboboxDropdownEventSource = 'keyboard' | 'mouse' | 'unknown';\n\nexport interface ComboboxStore {\n  /** Current dropdown opened state */\n  dropdownOpened: boolean;\n\n  /** Opens dropdown */\n  openDropdown: (eventSource?: ComboboxDropdownEventSource) => void;\n\n  /** Closes dropdown */\n  closeDropdown: (eventSource?: ComboboxDropdownEventSource) => void;\n\n  /** Toggles dropdown opened state */\n  toggleDropdown: (eventSource?: ComboboxDropdownEventSource) => void;\n\n  /** Selected option index ref */\n  selectedOptionIndex: number;\n\n  /** Returns currently selected option index or `-1` if none of the options is selected */\n  getSelectedOptionIndex: () => number;\n\n  /** Selects `Combobox.Option` by index */\n  selectOption: (index: number) => void;\n\n  /** Selects first `Combobox.Option` with `active` prop.\n   *  If there are no such options, the function does nothing.\n   */\n  selectActiveOption: () => string | null;\n\n  /** Selects first `Combobox.Option` that is not disabled.\n   *  If there are no such options, the function does nothing.\n   * */\n  selectFirstOption: () => string | null;\n\n  /** Selects next `Combobox.Option` that is not disabled.\n   *  If the current option is the last one, the function selects first option, if `loop` is true.\n   */\n  selectNextOption: () => string | null;\n\n  /** Selects previous `Combobox.Option` that is not disabled.\n   *  If the current option is the first one, the function selects last option, if `loop` is true.\n   * */\n  selectPreviousOption: () => string | null;\n\n  /** Resets selected option index to -1, removes `data-combobox-selected` from selected option */\n  resetSelectedOption: () => void;\n\n  /** Triggers `onClick` event of selected option.\n   *  If there is no selected option, the function does nothing.\n   */\n  clickSelectedOption: () => void;\n\n  /** Updates selected option index to currently selected or active option.\n   *  The function is required to be used with searchable components to update selected option index\n   *  when options list changes based on search query.\n   */\n  updateSelectedOptionIndex: (\n    target?: 'active' | 'selected',\n    options?: { scrollIntoView?: boolean }\n  ) => void;\n\n  /** List id, used for `aria-*` attributes */\n  listId: string | null;\n\n  /** Sets list id */\n  setListId: (id: string) => void;\n\n  /** Ref of `Combobox.Search` input */\n  searchRef: React.MutableRefObject<HTMLInputElement | null>;\n\n  /** Moves focus to `Combobox.Search` input */\n  focusSearchInput: () => void;\n\n  /** Ref of the target element */\n  targetRef: React.MutableRefObject<HTMLElement | null>;\n\n  /** Moves focus to the target element */\n  focusTarget: () => void;\n}\n\nexport interface UseComboboxOptions {\n  /** Default value for `dropdownOpened`, `false` by default */\n  defaultOpened?: boolean;\n\n  /** Controlled `dropdownOpened` state */\n  opened?: boolean;\n\n  /** Called when `dropdownOpened` state changes */\n  onOpenedChange?: (opened: boolean) => void;\n\n  /** Called when dropdown closes with event source: keyboard, mouse or unknown */\n  onDropdownClose?: (eventSource: ComboboxDropdownEventSource) => void;\n\n  /** Called when dropdown opens with event source: keyboard, mouse or unknown */\n  onDropdownOpen?: (eventSource: ComboboxDropdownEventSource) => void;\n\n  /** Determines whether arrow key presses should loop though items (first to last and last to first), `true` by default */\n  loop?: boolean;\n\n  /** `behavior` passed down to `element.scrollIntoView`, `'instant'` by default */\n  scrollBehavior?: ScrollBehavior;\n}\n\nexport function useCombobox({\n  defaultOpened,\n  opened,\n  onOpenedChange,\n  onDropdownClose,\n  onDropdownOpen,\n  loop = true,\n  scrollBehavior = 'instant',\n}: UseComboboxOptions = {}): ComboboxStore {\n  const [dropdownOpened, setDropdownOpened] = useUncontrolled({\n    value: opened,\n    defaultValue: defaultOpened,\n    finalValue: false,\n    onChange: onOpenedChange,\n  });\n\n  const listId = useRef<string | null>(null);\n  const selectedOptionIndex = useRef<number>(-1);\n  const searchRef = useRef<HTMLInputElement | null>(null);\n  const targetRef = useRef<HTMLElement | null>(null);\n  const focusSearchTimeout = useRef<number>(-1);\n  const focusTargetTimeout = useRef<number>(-1);\n  const selectedIndexUpdateTimeout = useRef<number>(-1);\n\n  const openDropdown: ComboboxStore['openDropdown'] = useCallback(\n    (eventSource = 'unknown') => {\n      if (!dropdownOpened) {\n        setDropdownOpened(true);\n        onDropdownOpen?.(eventSource);\n      }\n    },\n    [setDropdownOpened, onDropdownOpen, dropdownOpened]\n  );\n\n  const closeDropdown: ComboboxStore['closeDropdown'] = useCallback(\n    (eventSource = 'unknown') => {\n      if (dropdownOpened) {\n        setDropdownOpened(false);\n        onDropdownClose?.(eventSource);\n      }\n    },\n    [setDropdownOpened, onDropdownClose, dropdownOpened]\n  );\n\n  const toggleDropdown: ComboboxStore['toggleDropdown'] = useCallback(\n    (eventSource = 'unknown') => {\n      if (dropdownOpened) {\n        closeDropdown(eventSource);\n      } else {\n        openDropdown(eventSource);\n      }\n    },\n    [closeDropdown, openDropdown, dropdownOpened]\n  );\n\n  const clearSelectedItem = useCallback(() => {\n    const selected = document.querySelector(`#${listId.current} [data-combobox-selected]`);\n    selected?.removeAttribute('data-combobox-selected');\n    selected?.removeAttribute('aria-selected');\n  }, []);\n\n  const selectOption = useCallback(\n    (index: number) => {\n      const list = document.getElementById(listId.current!);\n      const items = list?.querySelectorAll('[data-combobox-option]');\n\n      if (!items) {\n        return null;\n      }\n\n      const nextIndex = index >= items!.length ? 0 : index < 0 ? items!.length - 1 : index;\n      selectedOptionIndex.current = nextIndex;\n\n      if (items?.[nextIndex] && !items[nextIndex].hasAttribute('data-combobox-disabled')) {\n        clearSelectedItem();\n        items[nextIndex].setAttribute('data-combobox-selected', 'true');\n        items[nextIndex].setAttribute('aria-selected', 'true');\n        items[nextIndex].scrollIntoView({ block: 'nearest', behavior: scrollBehavior });\n        return items[nextIndex].id;\n      }\n\n      return null;\n    },\n    [scrollBehavior, clearSelectedItem]\n  );\n\n  const selectActiveOption = useCallback(() => {\n    const activeOption = document.querySelector<HTMLDivElement>(\n      `#${listId.current} [data-combobox-active]`\n    );\n\n    if (activeOption) {\n      const items = document.querySelectorAll<HTMLDivElement>(\n        `#${listId.current} [data-combobox-option]`\n      );\n      const index = Array.from(items).findIndex((option) => option === activeOption);\n      return selectOption(index);\n    }\n\n    return selectOption(0);\n  }, [selectOption]);\n\n  const selectNextOption = useCallback(\n    () =>\n      selectOption(\n        getNextIndex(\n          selectedOptionIndex.current,\n          document.querySelectorAll<HTMLDivElement>(`#${listId.current} [data-combobox-option]`),\n          loop\n        )\n      ),\n    [selectOption, loop]\n  );\n\n  const selectPreviousOption = useCallback(\n    () =>\n      selectOption(\n        getPreviousIndex(\n          selectedOptionIndex.current,\n          document.querySelectorAll<HTMLDivElement>(`#${listId.current} [data-combobox-option]`),\n          loop\n        )\n      ),\n    [selectOption, loop]\n  );\n\n  const selectFirstOption = useCallback(\n    () =>\n      selectOption(\n        getFirstIndex(\n          document.querySelectorAll<HTMLDivElement>(`#${listId.current} [data-combobox-option]`)\n        )\n      ),\n    [selectOption]\n  );\n\n  const updateSelectedOptionIndex = useCallback(\n    (target: 'active' | 'selected' = 'selected', options?: { scrollIntoView?: boolean }) => {\n      selectedIndexUpdateTimeout.current = window.setTimeout(() => {\n        const items = document.querySelectorAll<HTMLDivElement>(\n          `#${listId.current} [data-combobox-option]`\n        );\n        const index = Array.from(items).findIndex((option) =>\n          option.hasAttribute(`data-combobox-${target}`)\n        );\n\n        selectedOptionIndex.current = index;\n\n        if (options?.scrollIntoView) {\n          items[index]?.scrollIntoView({ block: 'nearest', behavior: scrollBehavior });\n        }\n      }, 0);\n    },\n    []\n  );\n\n  const resetSelectedOption = useCallback(() => {\n    selectedOptionIndex.current = -1;\n    clearSelectedItem();\n  }, [clearSelectedItem]);\n\n  const clickSelectedOption = useCallback(() => {\n    const items = document.querySelectorAll<HTMLDivElement>(\n      `#${listId.current} [data-combobox-option]`\n    );\n    const item = items?.[selectedOptionIndex.current];\n    item?.click();\n  }, []);\n\n  const setListId = useCallback((id: string) => {\n    listId.current = id;\n  }, []);\n\n  const focusSearchInput = useCallback(() => {\n    focusSearchTimeout.current = window.setTimeout(() => searchRef.current!.focus(), 0);\n  }, []);\n\n  const focusTarget = useCallback(() => {\n    focusTargetTimeout.current = window.setTimeout(() => targetRef.current!.focus(), 0);\n  }, []);\n\n  const getSelectedOptionIndex = useCallback(() => selectedOptionIndex.current, []);\n\n  useEffect(\n    () => () => {\n      window.clearTimeout(focusSearchTimeout.current);\n      window.clearTimeout(focusTargetTimeout.current);\n      window.clearTimeout(selectedIndexUpdateTimeout.current);\n    },\n    []\n  );\n\n  return {\n    dropdownOpened,\n    openDropdown,\n    closeDropdown,\n    toggleDropdown,\n\n    selectedOptionIndex: selectedOptionIndex.current,\n    getSelectedOptionIndex,\n    selectOption,\n    selectFirstOption,\n    selectActiveOption,\n    selectNextOption,\n    selectPreviousOption,\n    resetSelectedOption,\n    updateSelectedOptionIndex,\n\n    listId: listId.current,\n    setListId,\n    clickSelectedOption,\n\n    searchRef,\n    focusSearchInput,\n\n    targetRef,\n    focusTarget,\n  };\n}\n"],"names":[],"mappings":"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"}