{"version":3,"file":"useOptions.mjs","sources":["../../../../src/components/Combobox/useOptions.ts"],"sourcesContent":["/* Spreading unbound arrays can be very slow or even crash the browser if used for arguments */\n/* eslint no-restricted-syntax: [\"error\", \"SpreadElement\"] */\n\nimport { debounce } from 'lodash';\nimport { useState, useCallback, useMemo } from 'react';\n\nimport { t } from '../../utils/i18n';\n\nimport { fuzzyFind, itemToString } from './filter';\nimport { ComboboxOption } from './types';\nimport { StaleResultError, useLatestAsyncCall } from './useLatestAsyncCall';\n\ntype AsyncOptions<T extends string | number> =\n  | Array<ComboboxOption<T>>\n  | ((inputValue: string) => Promise<Array<ComboboxOption<T>>>);\n\nconst asyncNoop = () => Promise.resolve([]);\n\n/**\n * Abstracts away sync/async options for combobox components.\n * It also filters options based on the user's input.\n *\n * Returns:\n *  - options either filtered by user's input, or from async options fn\n *  - function to call when user types (to filter, or call async fn)\n *  - loading and error states\n */\nexport function useOptions<T extends string | number>(rawOptions: AsyncOptions<T>, createCustomValue: boolean) {\n  const isAsync = typeof rawOptions === 'function';\n\n  const loadOptions = useLatestAsyncCall(isAsync ? rawOptions : asyncNoop);\n\n  const debouncedLoadOptions = useMemo(\n    () =>\n      debounce((searchTerm: string) => {\n        return loadOptions(searchTerm)\n          .then((options) => {\n            setAsyncOptions(options);\n            setAsyncLoading(false);\n            setAsyncError(false);\n          })\n          .catch((error) => {\n            if (!(error instanceof StaleResultError)) {\n              setAsyncError(true);\n              setAsyncLoading(false);\n\n              if (error) {\n                console.error('Error loading async options for Combobox', error);\n              }\n            }\n          });\n      }, 200),\n    [loadOptions]\n  );\n\n  const [asyncOptions, setAsyncOptions] = useState<Array<ComboboxOption<T>>>([]);\n  const [asyncLoading, setAsyncLoading] = useState(false);\n  const [asyncError, setAsyncError] = useState(false);\n\n  // This hook keeps its own inputValue state (rather than accepting it as an arg) because it needs to be\n  // told it for async options loading anyway.\n  const [userTypedSearch, setUserTypedSearch] = useState('');\n\n  const addCustomValue = useCallback(\n    (opts: Array<ComboboxOption<T>>) => {\n      let currentOptions: Array<ComboboxOption<T>> = opts;\n      if (createCustomValue && userTypedSearch) {\n        // Since the label of a normal option does not have to match its value and a custom option has the same value and label,\n        // we just focus on the value to check if the option already exists\n        const customValueExists = opts.some((opt) => opt.value === userTypedSearch);\n        if (!customValueExists) {\n          // Make sure to clone the array first to avoid mutating the original array!\n          currentOptions = currentOptions.slice();\n          currentOptions.unshift({\n            label: userTypedSearch,\n            value: userTypedSearch as T,\n            description: t('combobox.custom-value.description', 'Use custom value'),\n          });\n        }\n      }\n      return currentOptions;\n    },\n    [createCustomValue, userTypedSearch]\n  );\n\n  const updateOptions = useCallback(\n    (inputValue: string) => {\n      setUserTypedSearch(inputValue);\n      if (isAsync) {\n        setAsyncLoading(true);\n        debouncedLoadOptions(inputValue);\n      }\n    },\n    [debouncedLoadOptions, isAsync]\n  );\n\n  const stringifiedOptions = useMemo(() => {\n    return isAsync ? [] : rawOptions.map(itemToString);\n  }, [isAsync, rawOptions]);\n\n  // Create a list of options filtered by the current search.\n  // If async, just returns the async options.\n  const filteredOptions = useMemo(() => {\n    if (isAsync) {\n      return asyncOptions;\n    }\n\n    return fuzzyFind(rawOptions, stringifiedOptions, userTypedSearch);\n  }, [asyncOptions, isAsync, rawOptions, stringifiedOptions, userTypedSearch]);\n\n  const [finalOptions, groupStartIndices] = useMemo(() => {\n    const { options, groupStartIndices } = sortByGroup(filteredOptions);\n\n    return [addCustomValue(options), groupStartIndices];\n  }, [filteredOptions, addCustomValue]);\n\n  return { options: finalOptions, groupStartIndices, updateOptions, asyncLoading, asyncError };\n}\n\n/**\n * Sorts options by group and returns the sorted options and the starting index of each group\n */\nexport function sortByGroup<T extends string | number>(options: Array<ComboboxOption<T>>) {\n  // Group options by their group\n  const groupedOptions = new Map<string | undefined, Array<ComboboxOption<T>>>();\n  const groupStartIndices = new Map<string | undefined, number>();\n\n  for (const option of options) {\n    const group = option.group;\n    const existing = groupedOptions.get(group);\n    if (existing) {\n      existing.push(option);\n    } else {\n      groupedOptions.set(group, [option]);\n    }\n  }\n\n  // If we only have one group (either the undefined group, or a single group), return the original array\n  if (groupedOptions.size <= 1) {\n    if (options[0]?.group) {\n      groupStartIndices.set(options[0]?.group, 0);\n    }\n\n    return {\n      options,\n      groupStartIndices,\n    };\n  }\n\n  // 'Preallocate' result array with same size as input - very minor optimization\n  const result: Array<ComboboxOption<T>> = new Array(options.length);\n\n  let currentIndex = 0;\n\n  // Fill result array with grouped and undefined grouped options\n  for (const [group, groupOptions] of groupedOptions) {\n    if (group) {\n      groupStartIndices.set(group, currentIndex);\n    }\n    for (const option of groupOptions) {\n      result[currentIndex++] = option;\n    }\n  }\n\n  return {\n    options: result,\n    groupStartIndices,\n  };\n}\n"],"names":["groupStartIndices"],"mappings":"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"}