{"version":3,"file":"useOptions.cjs","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 '@grafana/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\nexport const DEBOUNCE_TIME_MS = 200;\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>(\n  rawOptions: AsyncOptions<T>,\n  createCustomValue: boolean,\n  customValueDescription?: string\n) {\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      }, DEBOUNCE_TIME_MS),\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: customValueDescription ?? t('combobox.custom-value.description', 'Use custom value'),\n          });\n        }\n      }\n      return currentOptions;\n    },\n    [createCustomValue, customValueDescription, 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":["useLatestAsyncCall","useMemo","debounce","StaleResultError","useState","useCallback","t","itemToString","fuzzyFind","groupStartIndices"],"mappings":"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"}