@uniformdev/search 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react/index.ts","../src/react/SearchProvider.tsx","../src/constants.ts","../src/utils.ts","../src/flattenBlockParams.ts","../src/react/useSearch.ts","../src/react/usePagination.ts","../src/react/useSearchPagination.ts","../src/react/SearchItemUrlResolver.tsx"],"sourcesContent":["export { SearchProvider, SearchContext } from './SearchProvider';\nexport { useSearch } from './useSearch';\nexport { usePagination, DOTS } from './usePagination';\nexport { useSearchPagination } from './useSearchPagination';\nexport {\n SearchItemUrlResolverProvider,\n SearchItemDefaultUrlResolverProvider,\n useUrlResolver,\n createDefaultUrlResolver,\n} from './SearchItemUrlResolver';\nexport type { SearchProviderProps, UseSearchReturn } from './SearchProvider';\nexport type { UseSearchPaginationReturn } from './useSearchPagination';\nexport type { UrlResolver } from './SearchItemUrlResolver';\n","import { createContext, FC, useMemo, useCallback, useState, useEffect, useRef, type ReactNode } from 'react';\n\nimport {\n DEFAULT_PAGE_SIZE,\n UNIFORM_SEARCH_ORDER_BY_KEY,\n UNIFORM_SEARCH_PAGE_KEY,\n UNIFORM_SEARCH_PAGE_SIZE_KEY,\n UNIFORM_SEARCH_QUERY_KEY,\n FIRST_PAGE,\n} from '../constants';\nimport { getSearchParamsFromUrl, buildOrderByQuery } from '../utils';\nimport { flattenBlockParams } from '../flattenBlockParams';\nimport type {\n SearchHit,\n FacetBy,\n FilterByItem,\n Pagination,\n Facets,\n OrderBy,\n PageSize,\n FilterQuery,\n CollectionResult,\n} from '../types';\nimport type { SearchParams } from '../client';\n\nexport interface SearchProviderProps {\n children: ReactNode;\n performSearch: (params: SearchParams) => Promise<CollectionResult>;\n orderBy?: unknown;\n baseFilterString?: string;\n pageSizes?: unknown;\n queryBy?: string[];\n searchDebounceMs?: number;\n locale?: string;\n maxFacetValues?: number;\n}\n\nexport interface UseSearchReturn {\n results: Pagination<SearchHit>;\n facets: Facets | null;\n isLoading: boolean;\n\n searchBoxValue: string;\n setSearchQuery: (value: string) => void;\n\n page: number;\n setPage: (page: number) => void;\n pageSize: number;\n setPageSize: (size: number) => void;\n pageSizes: PageSize[];\n /** Register a component's page-size config (last registration wins). `id` should be\n * stable per instance. Used by pagination components that own their config. */\n registerPageSizes: (id: string, pageSizes: unknown) => void;\n unregisterPageSizes: (id: string) => void;\n\n orderByOptions: Array<{ title: string; value: string }>;\n selectedOrderBy: string;\n setOrderBy: (query: string) => void;\n /** Register a sort component's order-by config (last registration wins). `id` should\n * be stable per component instance. Used by sort components that own their config\n * (App Router slots) instead of it being passed to the provider. */\n registerOrderBy: (id: string, orderBy: unknown) => void;\n unregisterOrderBy: (id: string) => void;\n\n filterOptions: FacetBy[];\n setFilterOptions: (filters: FacetBy[]) => void;\n /** Additive registration of a single facet option (keyed by fieldKey). Used by facet\n * components that mount independently (e.g. App Router slots) rather than being\n * enumerated by a parent. */\n registerFilterOption: (filter: FacetBy) => void;\n unregisterFilterOption: (fieldKey: string) => void;\n selectedFilters: Record<string, string[]>;\n setSelectedFilters: (filters: Record<string, string[]>) => void;\n clearFilters: () => void;\n\n formatResultsSummary: (template: string) => string;\n}\n\nexport const SearchContext = createContext<UseSearchReturn | null>(null);\n\nexport const SearchProvider: FC<SearchProviderProps> = ({\n children,\n performSearch,\n orderBy: orderByRaw,\n baseFilterString,\n pageSizes: pageSizesRaw,\n queryBy,\n searchDebounceMs = 300,\n locale,\n maxFacetValues = 100,\n}) => {\n const [filterBy, setFilterOptions] = useState<FacetBy[]>([]);\n\n const registerFilterOption = useCallback((filter: FacetBy) => {\n if (!filter?.fieldKey) return;\n setFilterOptions((prev) => [...prev.filter((f) => f.fieldKey !== filter.fieldKey), filter]);\n }, []);\n\n const unregisterFilterOption = useCallback((fieldKey: string) => {\n setFilterOptions((prev) => prev.filter((f) => f.fieldKey !== fieldKey));\n }, []);\n\n // Order-by config can come from the parent (`orderBy` prop — back-compat) OR be\n // registered by child sort components. Last registration wins; with nothing registered\n // and no prop, no default sort is applied → Typesense relevance ordering.\n const [orderByRegistry, setOrderByRegistry] = useState<Array<{ id: string; options: OrderBy[] }>>([]);\n\n const registerOrderBy = useCallback((id: string, raw: unknown) => {\n if (!id) return;\n const options = flattenBlockParams<OrderBy>(raw);\n // Move/insert this registrant last so the most-recently-registered config wins.\n setOrderByRegistry((prev) => [...prev.filter((e) => e.id !== id), { id, options }]);\n }, []);\n\n const unregisterOrderBy = useCallback((id: string) => {\n setOrderByRegistry((prev) => prev.filter((e) => e.id !== id));\n }, []);\n\n const propOrderByFlat = useMemo(() => flattenBlockParams<OrderBy>(orderByRaw), [orderByRaw]);\n // The last child that registered a NON-EMPTY config wins. Empty registrations (a sort\n // component present but not configured) are ignored so they don't clobber the\n // back-compat `orderBy` prop (existing projects that still set it on SearchEngine).\n const lastRegisteredOrderBy = [...orderByRegistry].reverse().find((e) => e.options.length > 0);\n const orderByFlat = lastRegisteredOrderBy ? lastRegisteredOrderBy.options : propOrderByFlat;\n\n // Page sizes follow the same pattern: parent prop (back-compat) OR child registration\n // (last wins). With neither, DEFAULT_PAGE_SIZE is used (see initPerPage below).\n const [pageSizesRegistry, setPageSizesRegistry] = useState<Array<{ id: string; sizes: PageSize[] }>>([]);\n\n const registerPageSizes = useCallback((id: string, raw: unknown) => {\n if (!id) return;\n const sizes = flattenBlockParams<PageSize>(raw);\n setPageSizesRegistry((prev) => [...prev.filter((e) => e.id !== id), { id, sizes }]);\n }, []);\n\n const unregisterPageSizes = useCallback((id: string) => {\n setPageSizesRegistry((prev) => prev.filter((e) => e.id !== id));\n }, []);\n\n const propPageSizesFlat = useMemo(() => flattenBlockParams<PageSize>(pageSizesRaw), [pageSizesRaw]);\n // Last non-empty registration wins; empty ones fall back to the back-compat\n // `pageSizes` prop (existing projects that still set it on SearchEngine).\n const lastRegisteredPageSizes = [...pageSizesRegistry].reverse().find((e) => e.sizes.length > 0);\n const pageSizesFlat = lastRegisteredPageSizes ? lastRegisteredPageSizes.sizes : propPageSizesFlat;\n\n const facetBy = useMemo(\n () =>\n filterBy\n .filter((f) => f?.fieldKey)\n .map((f) => f.fieldKey)\n .join(','),\n [filterBy]\n );\n\n // No configured sort → undefined, so no `sort_by` is sent and Typesense sorts by\n // relevance (text match). The author's first option is the default when configured.\n const defaultOrderByQuery = useMemo(\n () => (orderByFlat[0] ? buildOrderByQuery(orderByFlat[0]) : undefined),\n [orderByFlat]\n );\n\n const orderByOptions = useMemo(\n () => orderByFlat.map((o) => ({ title: o.title, value: buildOrderByQuery(o) })),\n [orderByFlat]\n );\n\n // Initial page size: the size flagged `default` (first one wins), else the first\n // configured size, else DEFAULT_PAGE_SIZE (when no page-size component is present).\n const initPerPage = pageSizesFlat.find((ps) => ps.default)?.size || pageSizesFlat[0]?.size || DEFAULT_PAGE_SIZE;\n\n const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const searchRequestIdRef = useRef(0);\n\n const [urlParams, setUrlParams] = useState<Record<string, string | string[]>>(() => {\n if (typeof window === 'undefined') return {};\n return getSearchParamsFromUrl(window.location.href);\n });\n\n const [searchBoxValue, setSearchBoxValue] = useState((urlParams?.[UNIFORM_SEARCH_QUERY_KEY] as string) || '');\n const [isLoading, setIsLoading] = useState(true);\n const [entries, setEntries] = useState<Pagination<SearchHit>>({\n items: [],\n page: FIRST_PAGE,\n perPage: initPerPage,\n total: 0,\n totalPages: 0,\n });\n const [facets, setFacets] = useState<Facets>({});\n\n const pathname = useMemo(() => {\n if (typeof window === 'undefined') return '/';\n return window.location.pathname;\n }, []);\n\n const page =\n Number(urlParams?.[UNIFORM_SEARCH_PAGE_KEY]) - 1 > 0\n ? Number(urlParams?.[UNIFORM_SEARCH_PAGE_KEY]) - 1\n : FIRST_PAGE;\n const search = (urlParams?.[UNIFORM_SEARCH_QUERY_KEY] as string) || '';\n const perPage = Number(urlParams?.[UNIFORM_SEARCH_PAGE_SIZE_KEY]) || initPerPage;\n\n const selectedFilters = useMemo(\n () =>\n filterBy.reduce<Record<string, string[]>>((acc, filter) => {\n const value = urlParams?.[filter.fieldKey];\n if (value) {\n acc[filter.fieldKey] = Array.isArray(value) ? value : [value];\n }\n return acc;\n }, {}),\n [filterBy, urlParams]\n );\n\n useEffect(() => {\n const requestId = ++searchRequestIdRef.current;\n\n const doFetch = async () => {\n const currentOrderByQuery = (urlParams?.[UNIFORM_SEARCH_ORDER_BY_KEY] as string) || defaultOrderByQuery;\n\n const buildFilters = (excludeField?: string) =>\n Object.entries(selectedFilters).reduce<Record<string, unknown>>((acc, [fieldKey, values]) => {\n if (excludeField && fieldKey === excludeField) return acc;\n if (!values || values.length === 0) return acc;\n const filterType = filterBy.find((f) => f.fieldKey === fieldKey)?.type;\n if (filterType === 'range') {\n const [min, max] = values;\n return { ...acc, [`${fieldKey}[gte]`]: min, [`${fieldKey}[lte]`]: max };\n }\n return { ...acc, [`${fieldKey}[in]`]: values };\n }, {});\n\n // Fields that have active selections need disjunctive queries so their\n // option lists stay complete (user can add more values via OR logic).\n const selectedFacetFields = Object.keys(selectedFilters).filter((k) => selectedFilters[k]?.length > 0);\n const queryByString = queryBy?.length ? queryBy.join(',') : undefined;\n const [mainResult, ...disjunctiveResults] = await Promise.all([\n performSearch({\n page,\n perPage,\n filters: buildFilters(),\n baseFilterBy: baseFilterString,\n facetBy,\n queryBy: queryByString,\n search,\n orderBy: currentOrderByQuery,\n locale,\n maxFacetValues,\n }),\n ...selectedFacetFields.map((field) =>\n performSearch({\n page: 0,\n perPage: 0,\n filters: buildFilters(field),\n baseFilterBy: baseFilterString,\n facetBy: field,\n queryBy: queryByString,\n search,\n orderBy: currentOrderByQuery,\n locale,\n maxFacetValues,\n })\n ),\n ]);\n\n if (requestId !== searchRequestIdRef.current) return;\n\n // Replace main facet counts for selected fields with their disjunctive counts.\n const mergedFacets = { ...(mainResult.facets ?? {}) };\n selectedFacetFields.forEach((field, i) => {\n const counts = disjunctiveResults[i]?.facets?.[field];\n if (counts) mergedFacets[field] = counts;\n });\n\n setEntries(mainResult.data ?? { items: [], total: 0, page, perPage, totalPages: 0 });\n setFacets(mergedFacets);\n };\n\n setIsLoading(true);\n doFetch()\n .catch((error) => console.error('Search fetch error:', error))\n .finally(() => setIsLoading(false));\n }, [\n baseFilterString,\n defaultOrderByQuery,\n facetBy,\n filterBy,\n locale,\n urlParams,\n page,\n perPage,\n performSearch,\n search,\n selectedFilters,\n ]);\n\n useEffect(() => {\n return () => {\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n };\n }, []);\n\n const updateUrl = useCallback(\n (params: URLSearchParams, { replace = false }: { replace?: boolean } = {}) => {\n const qs = params.toString();\n const url = qs ? `${pathname}?${qs}` : pathname;\n if (replace) {\n window.history.replaceState({}, '', url);\n } else {\n window.history.pushState({}, '', url);\n }\n setUrlParams(getSearchParamsFromUrl(window.location.href));\n },\n [pathname]\n );\n\n const commitSearchToUrl = useCallback(\n (value: string) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n if (!value) {\n params.delete(UNIFORM_SEARCH_QUERY_KEY);\n } else {\n params.set(UNIFORM_SEARCH_QUERY_KEY, value);\n }\n updateUrl(params, { replace: true });\n },\n [updateUrl]\n );\n\n const setSearchQuery = useCallback(\n (value: string) => {\n setSearchBoxValue(value);\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n if (!value) {\n commitSearchToUrl(value);\n } else {\n searchDebounceRef.current = setTimeout(() => commitSearchToUrl(value), searchDebounceMs);\n }\n },\n [commitSearchToUrl, searchDebounceMs]\n );\n\n const setPage = useCallback(\n (p: number) => {\n const params = new URLSearchParams(window.location.search);\n if (p === 0) {\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n } else {\n params.set(UNIFORM_SEARCH_PAGE_KEY, (p + 1).toString());\n }\n updateUrl(params);\n },\n [updateUrl]\n );\n\n const setPageSize = useCallback(\n (size: number) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n params.set(UNIFORM_SEARCH_PAGE_SIZE_KEY, size.toString());\n updateUrl(params);\n },\n [updateUrl]\n );\n\n const setOrderBy = useCallback(\n (orderByQuery: string) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n params.set(UNIFORM_SEARCH_ORDER_BY_KEY, orderByQuery);\n updateUrl(params);\n },\n [updateUrl]\n );\n\n const setSelectedFilters = useCallback(\n (nextSelected: Record<string, string[]>) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n filterBy.forEach((f) => params.delete(f.fieldKey));\n Object.entries(nextSelected).forEach(([key, value]) => {\n const filter = filterBy.find((f) => f.fieldKey === key);\n if (!filter) return;\n value.forEach((v) => params.append(key, v));\n });\n updateUrl(params);\n },\n [filterBy, updateUrl]\n );\n\n const clearFilters = useCallback(() => {\n setIsLoading(true);\n setSearchBoxValue('');\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n window.history.pushState({}, '', pathname);\n setUrlParams(getSearchParamsFromUrl(window.location.href));\n }, [pathname]);\n\n const formatResultsSummary = useCallback(\n (template: string) => {\n if (!template) return '';\n const vars: Record<string, number | undefined> = {\n page: entries.page,\n perPage: entries.perPage,\n totalItems: entries.total,\n totalPages: entries.totalPages,\n };\n return template.replace(/{(\\w+)}/g, (match, key) => {\n const val = vars[key];\n return val != null ? String(val) : match;\n });\n },\n [entries]\n );\n\n const value: UseSearchReturn = useMemo(\n () => ({\n results: entries,\n facets,\n isLoading,\n\n searchBoxValue,\n setSearchQuery,\n\n page,\n setPage,\n pageSize: perPage,\n setPageSize,\n pageSizes: pageSizesFlat,\n registerPageSizes,\n unregisterPageSizes,\n\n orderByOptions,\n selectedOrderBy: (urlParams?.[UNIFORM_SEARCH_ORDER_BY_KEY] as string) || defaultOrderByQuery || '',\n setOrderBy,\n registerOrderBy,\n unregisterOrderBy,\n\n filterOptions: filterBy,\n setFilterOptions,\n registerFilterOption,\n unregisterFilterOption,\n selectedFilters,\n setSelectedFilters,\n clearFilters,\n\n formatResultsSummary,\n }),\n [\n clearFilters,\n defaultOrderByQuery,\n entries,\n facets,\n filterBy,\n setFilterOptions,\n registerFilterOption,\n unregisterFilterOption,\n formatResultsSummary,\n isLoading,\n orderByOptions,\n registerOrderBy,\n unregisterOrderBy,\n page,\n pageSizesFlat,\n registerPageSizes,\n unregisterPageSizes,\n perPage,\n searchBoxValue,\n selectedFilters,\n setOrderBy,\n setPage,\n setPageSize,\n setSearchQuery,\n setSelectedFilters,\n urlParams,\n ]\n );\n\n return <SearchContext.Provider value={value}>{children}</SearchContext.Provider>;\n};\n","export const UNIFORM_SEARCH_QUERY_KEY = 'search';\nexport const UNIFORM_SEARCH_PAGE_KEY = 'page';\nexport const UNIFORM_SEARCH_PAGE_SIZE_KEY = 'pageSize';\nexport const UNIFORM_SEARCH_ORDER_BY_KEY = 'orderBy';\nexport const FIRST_PAGE = 0;\nexport const DEFAULT_PAGE_SIZE = 10;\n","import type { OrderBy } from './types';\n\nexport const buildOrderByQuery = (orderBy: OrderBy) => {\n const field = orderBy.field as unknown;\n // sortByConfig produces an object value: { field: string; direction: 'asc' | 'desc' }\n if (field && typeof field === 'object' && 'field' in (field as object)) {\n const sv = field as { field: string; direction: string };\n return `${sv.field}_${sv.direction.toUpperCase()}`;\n }\n // Plain string \"relevance\" or missing direction → no sort\n if (!field || field === 'relevance') return '';\n return `${field}_${orderBy.direction ?? 'ASC'}`;\n};\n\nexport const getSearchParamsFromUrl = (urlString: string): Record<string, string | string[]> => {\n if (!urlString) {\n return {};\n }\n\n const url = new URL(urlString);\n const params: Record<string, string | string[]> = {};\n\n url.searchParams.forEach((value, key) => {\n if (params[key]) {\n params[key] = Array.isArray(params[key])\n ? [...(params[key] as string[]), value]\n : [params[key] as string, value];\n } else {\n params[key] = value;\n }\n });\n\n return params;\n};\n","type RawField = {\n type?: string;\n value?: unknown;\n locales?: Record<string, unknown>;\n};\n\ntype RawBlockItem = {\n type?: string;\n fields?: Record<string, RawField>;\n};\n\nfunction resolveFieldValue(field: RawField, locale = 'en-us'): unknown {\n if (!field || typeof field !== 'object') return field;\n\n if (field.type === '$block' && Array.isArray(field.value)) {\n return flattenBlockParams(field.value as RawBlockItem[], locale);\n }\n\n if (field.locales && typeof field.locales === 'object') {\n return field.locales[locale] ?? field.value;\n }\n\n return field.value;\n}\n\nexport function flattenBlockParams<T = Record<string, unknown>>(\n items: unknown,\n locale = 'en-us'\n): T[] {\n if (!Array.isArray(items)) return [];\n return items.map(item => {\n const result: Record<string, unknown> = {};\n const blockItem = item as RawBlockItem;\n if (blockItem.fields) {\n for (const [key, field] of Object.entries(blockItem.fields)) {\n result[key] = resolveFieldValue(field, locale);\n }\n }\n return result as T;\n });\n}\n","import { useContext } from 'react';\nimport { SearchContext } from './SearchProvider';\nimport type { UseSearchReturn } from './SearchProvider';\n\nexport function useSearch(): UseSearchReturn {\n const context = useContext(SearchContext);\n if (!context) {\n throw new Error('useSearch must be used within a <SearchProvider>');\n }\n return context;\n}\n","import { useMemo } from 'react';\n\nexport const DOTS = '...';\n\ninterface UsePaginationParams {\n currentPage: number;\n totalCount: number;\n perPage: number;\n siblingCount?: number;\n}\n\nconst range = (start: number, end: number) => {\n const length = end - start + 1;\n return Array.from({ length }, (_, idx) => idx + start);\n};\n\nexport const usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }: UsePaginationParams) =>\n useMemo(() => {\n const totalPageCount = Math.ceil(totalCount / perPage);\n const totalPageNumbers = siblingCount + 5;\n\n if (totalPageNumbers >= totalPageCount) {\n return range(1, totalPageCount);\n }\n\n const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);\n const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPageCount);\n\n const shouldShowLeftDots = leftSiblingIndex > 2;\n const shouldShowRightDots = rightSiblingIndex < totalPageCount - 2;\n\n const firstPageIndex = 1;\n const lastPageIndex = totalPageCount;\n\n if (!shouldShowLeftDots && shouldShowRightDots) {\n const leftItemCount = 2 + 2 * siblingCount;\n const leftRange = range(1, leftItemCount);\n return [...leftRange, DOTS, totalPageCount];\n }\n\n if (shouldShowLeftDots && !shouldShowRightDots) {\n const rightItemCount = 2 + 2 * siblingCount;\n const rightRange = range(totalPageCount - rightItemCount + 1, totalPageCount);\n return [firstPageIndex, DOTS, ...rightRange];\n }\n\n if (shouldShowLeftDots && shouldShowRightDots) {\n const middleRange = range(leftSiblingIndex, rightSiblingIndex);\n return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];\n }\n }, [totalCount, perPage, siblingCount, currentPage]);\n","import { useMemo, useCallback } from 'react';\nimport { useSearch } from './useSearch';\nimport { usePagination, DOTS } from './usePagination';\n\nexport interface UseSearchPaginationReturn {\n pages: (number | string)[];\n currentPage: number;\n hasPrev: boolean;\n hasNext: boolean;\n goToPage: (page: number) => void;\n goToPrev: () => void;\n goToNext: () => void;\n isLoading: boolean;\n}\n\nexport function useSearchPagination(siblingCount?: number): UseSearchPaginationReturn {\n const { page, pageSize, results, setPage, isLoading } = useSearch();\n\n const pages = usePagination({\n currentPage: page,\n totalCount: results.total,\n perPage: pageSize,\n siblingCount,\n }) || [];\n\n const lastPage = useMemo(\n () => Number(pages[pages.length - 1]) || 0,\n [pages]\n );\n\n const goToPage = useCallback(\n (p: number) => { if (!isLoading) setPage(p); },\n [isLoading, setPage]\n );\n\n const goToPrev = useCallback(\n () => { if (page > 0 && !isLoading) setPage(page - 1); },\n [page, isLoading, setPage]\n );\n\n const goToNext = useCallback(\n () => { if (page < lastPage && !isLoading) setPage(page + 1); },\n [page, lastPage, isLoading, setPage]\n );\n\n return {\n pages,\n currentPage: page,\n hasPrev: page > 0,\n hasNext: page < lastPage,\n goToPage,\n goToPrev,\n goToNext,\n isLoading,\n };\n}\n\nexport { DOTS };\n","import { createContext, useContext, type FC, type ReactNode } from 'react';\nimport type { SearchHit, SearchItemConfig } from '../types';\n\nexport type UrlResolver = (hit: SearchHit) => string | undefined;\n\nconst NOOP_RESOLVER: UrlResolver = () => undefined;\n\n// Set by the user to override URL resolution for the entire subtree.\nconst UserUrlResolverContext = createContext<UrlResolver | null>(null);\n\n// Set by SearchList to provide config-driven defaults within its subtree.\nconst DefaultUrlResolverContext = createContext<UrlResolver>(NOOP_RESOLVER);\n\n/** Wrap your search tree to override URL building for all hits. */\nexport const SearchItemUrlResolverProvider: FC<{ resolver: UrlResolver; children: ReactNode }> = ({\n resolver,\n children,\n}) => (\n <UserUrlResolverContext.Provider value={resolver}>\n {children}\n </UserUrlResolverContext.Provider>\n);\n\n/** Used internally by SearchList to register the Canvas-config-driven default. */\nexport const SearchItemDefaultUrlResolverProvider: FC<{ resolver: UrlResolver; children: ReactNode }> = ({\n resolver,\n children,\n}) => (\n <DefaultUrlResolverContext.Provider value={resolver}>\n {children}\n </DefaultUrlResolverContext.Provider>\n);\n\n/**\n * Always returns a resolver. User-provided resolver (SearchItemUrlResolverProvider)\n * takes priority over the default registered by SearchList.\n */\nexport function useUrlResolver(): UrlResolver {\n const user = useContext(UserUrlResolverContext);\n const def = useContext(DefaultUrlResolverContext);\n return user ?? def;\n}\n\nexport interface UrlResolverOptions {\n /** nodeId → resolved locale-specific path (from project map). */\n pathsByNodeId?: Record<string, string>;\n /** Current locale, used as fallback for legacy :locale token in urlTemplate. */\n locale?: string;\n}\n\n/**\n * Builds the default URL resolver from Canvas SearchItem configs.\n * Exported so custom resolvers can compose with it.\n */\nfunction resolveField<T = unknown>(obj: SearchHit, path: string): T | undefined {\n return path\n .split(\".\")\n .reduce<any>((current, key) => {\n if (current == null) {\n return undefined;\n }\n\n // If current value is an array, use first item\n if (Array.isArray(current)) {\n current = current[0];\n }\n\n return current?.[key];\n }, obj);\n\n}\n\nexport function createDefaultUrlResolver(\n configs: SearchItemConfig[],\n options: UrlResolverOptions = {}\n): UrlResolver {\n const { pathsByNodeId = {}, locale } = options;\n\n return (hit: SearchHit): string | undefined => {\n const hitSource = typeof hit.source === 'string' ? hit.source : '';\n const hitType = typeof hit.type === 'string' ? hit.type : '';\n const config = configs.find(c => c.source === hitSource && c.type === hitType);\n if (config) {\n const template =\n (config.nodeId ? pathsByNodeId[config.nodeId] : undefined) ?? config.urlTemplate;\n\n if (template) {\n return template.replace(/:([a-zA-Z0-9_-]+)/g, (_, token) => {\n if (token === 'locale') return locale ?? '';\n const fieldKey = config.tokenMapping?.[token];\n if (!fieldKey) return '';\n const val = resolveField(hit, fieldKey);\n return Array.isArray(val) ? String(val[0] ?? '') : String(val ?? '');\n });\n }\n }\n\n if (hitSource === 'composition' && typeof hit.path === 'string') {\n return hit.path;\n }\n\n return undefined;\n };\n}\n"],"mappings":"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","names":["value","_a","_b","import_react","import_react","import_react","import_react","import_jsx_runtime","_a"]}
1
+ {"version":3,"sources":["../src/react/index.ts","../src/react/SearchProvider.tsx","../src/constants.ts","../src/utils.ts","../src/flattenBlockParams.ts","../src/react/useSearch.ts","../src/react/useAutocomplete.ts","../src/react/usePagination.ts","../src/react/useSearchPagination.ts","../src/react/SearchItemUrlResolver.tsx"],"sourcesContent":["export { SearchProvider, SearchContext } from './SearchProvider';\nexport { useSearch } from './useSearch';\nexport { useAutocomplete } from './useAutocomplete';\nexport { usePagination, DOTS } from './usePagination';\nexport { useSearchPagination } from './useSearchPagination';\nexport {\n SearchItemUrlResolverProvider,\n SearchItemDefaultUrlResolverProvider,\n useUrlResolver,\n createDefaultUrlResolver,\n} from './SearchItemUrlResolver';\nexport type { SearchProviderProps, UseSearchReturn } from './SearchProvider';\nexport type {\n UseAutocompleteOptions,\n UseAutocompleteReturn,\n AutocompleteInputProps,\n AutocompleteListboxProps,\n AutocompleteItemProps,\n} from './useAutocomplete';\nexport type { UseSearchPaginationReturn } from './useSearchPagination';\nexport type { UrlResolver } from './SearchItemUrlResolver';\n","import { createContext, FC, useMemo, useCallback, useState, useEffect, useRef, type ReactNode } from 'react';\n\nimport {\n DEFAULT_PAGE_SIZE,\n UNIFORM_SEARCH_ORDER_BY_KEY,\n UNIFORM_SEARCH_PAGE_KEY,\n UNIFORM_SEARCH_PAGE_SIZE_KEY,\n UNIFORM_SEARCH_QUERY_KEY,\n FIRST_PAGE,\n} from '../constants';\nimport { getSearchParamsFromUrl, buildOrderByQuery } from '../utils';\nimport { flattenBlockParams } from '../flattenBlockParams';\nimport type {\n SearchHit,\n FacetBy,\n FilterByItem,\n Pagination,\n Facets,\n OrderBy,\n PageSize,\n FilterQuery,\n CollectionResult,\n} from '../types';\nimport type { SearchParams } from '../client';\n\nexport interface SearchProviderProps {\n children: ReactNode;\n performSearch: (params: SearchParams) => Promise<CollectionResult>;\n orderBy?: unknown;\n baseFilterString?: string;\n pageSizes?: unknown;\n queryBy?: string[];\n searchDebounceMs?: number;\n locale?: string;\n maxFacetValues?: number;\n}\n\nexport interface UseSearchReturn {\n results: Pagination<SearchHit>;\n facets: Facets | null;\n isLoading: boolean;\n\n searchBoxValue: string;\n setSearchQuery: (value: string) => void;\n\n page: number;\n setPage: (page: number) => void;\n pageSize: number;\n setPageSize: (size: number) => void;\n pageSizes: PageSize[];\n /** Register a component's page-size config (last registration wins). `id` should be\n * stable per instance. Used by pagination components that own their config. */\n registerPageSizes: (id: string, pageSizes: unknown) => void;\n unregisterPageSizes: (id: string) => void;\n\n orderByOptions: Array<{ title: string; value: string }>;\n selectedOrderBy: string;\n setOrderBy: (query: string) => void;\n /** Register a sort component's order-by config (last registration wins). `id` should\n * be stable per component instance. Used by sort components that own their config\n * (App Router slots) instead of it being passed to the provider. */\n registerOrderBy: (id: string, orderBy: unknown) => void;\n unregisterOrderBy: (id: string) => void;\n\n /** The search function the provider drives (typo-tolerance wrapper included). Exposed\n * so in-engine components (e.g. an autocomplete box) issue the SAME request shape. */\n performSearch: (params: SearchParams) => Promise<CollectionResult>;\n /** Fields the query string is matched against (as configured on the engine). */\n queryBy?: string[];\n /** Locale the search is scoped to (per-locale collection). */\n locale?: string;\n /** Always-applied base `filter_by` string (from the engine's base filters). */\n baseFilterString?: string;\n /** The active facet selections as a `performSearch` `filters` object — mirrors the\n * main results request so an autocomplete can reproduce the current filtered context. */\n currentFilters: Record<string, unknown>;\n\n filterOptions: FacetBy[];\n setFilterOptions: (filters: FacetBy[]) => void;\n /** Additive registration of a single facet option (keyed by fieldKey). Used by facet\n * components that mount independently (e.g. App Router slots) rather than being\n * enumerated by a parent. */\n registerFilterOption: (filter: FacetBy) => void;\n unregisterFilterOption: (fieldKey: string) => void;\n selectedFilters: Record<string, string[]>;\n setSelectedFilters: (filters: Record<string, string[]>) => void;\n clearFilters: () => void;\n\n formatResultsSummary: (template: string) => string;\n}\n\nexport const SearchContext = createContext<UseSearchReturn | null>(null);\n\nexport const SearchProvider: FC<SearchProviderProps> = ({\n children,\n performSearch,\n orderBy: orderByRaw,\n baseFilterString,\n pageSizes: pageSizesRaw,\n queryBy,\n searchDebounceMs = 300,\n locale,\n maxFacetValues = 100,\n}) => {\n const [filterBy, setFilterOptions] = useState<FacetBy[]>([]);\n\n const registerFilterOption = useCallback((filter: FacetBy) => {\n if (!filter?.fieldKey) return;\n setFilterOptions((prev) => [...prev.filter((f) => f.fieldKey !== filter.fieldKey), filter]);\n }, []);\n\n const unregisterFilterOption = useCallback((fieldKey: string) => {\n setFilterOptions((prev) => prev.filter((f) => f.fieldKey !== fieldKey));\n }, []);\n\n // Order-by config can come from the parent (`orderBy` prop — back-compat) OR be\n // registered by child sort components. Last registration wins; with nothing registered\n // and no prop, no default sort is applied → Typesense relevance ordering.\n const [orderByRegistry, setOrderByRegistry] = useState<Array<{ id: string; options: OrderBy[] }>>([]);\n\n const registerOrderBy = useCallback((id: string, raw: unknown) => {\n if (!id) return;\n const options = flattenBlockParams<OrderBy>(raw);\n // Move/insert this registrant last so the most-recently-registered config wins.\n setOrderByRegistry((prev) => [...prev.filter((e) => e.id !== id), { id, options }]);\n }, []);\n\n const unregisterOrderBy = useCallback((id: string) => {\n setOrderByRegistry((prev) => prev.filter((e) => e.id !== id));\n }, []);\n\n const propOrderByFlat = useMemo(() => flattenBlockParams<OrderBy>(orderByRaw), [orderByRaw]);\n // The last child that registered a NON-EMPTY config wins. Empty registrations (a sort\n // component present but not configured) are ignored so they don't clobber the\n // back-compat `orderBy` prop (existing projects that still set it on SearchEngine).\n const lastRegisteredOrderBy = [...orderByRegistry].reverse().find((e) => e.options.length > 0);\n const orderByFlat = lastRegisteredOrderBy ? lastRegisteredOrderBy.options : propOrderByFlat;\n\n // Page sizes follow the same pattern: parent prop (back-compat) OR child registration\n // (last wins). With neither, DEFAULT_PAGE_SIZE is used (see initPerPage below).\n const [pageSizesRegistry, setPageSizesRegistry] = useState<Array<{ id: string; sizes: PageSize[] }>>([]);\n\n const registerPageSizes = useCallback((id: string, raw: unknown) => {\n if (!id) return;\n const sizes = flattenBlockParams<PageSize>(raw);\n setPageSizesRegistry((prev) => [...prev.filter((e) => e.id !== id), { id, sizes }]);\n }, []);\n\n const unregisterPageSizes = useCallback((id: string) => {\n setPageSizesRegistry((prev) => prev.filter((e) => e.id !== id));\n }, []);\n\n const propPageSizesFlat = useMemo(() => flattenBlockParams<PageSize>(pageSizesRaw), [pageSizesRaw]);\n // Last non-empty registration wins; empty ones fall back to the back-compat\n // `pageSizes` prop (existing projects that still set it on SearchEngine).\n const lastRegisteredPageSizes = [...pageSizesRegistry].reverse().find((e) => e.sizes.length > 0);\n const pageSizesFlat = lastRegisteredPageSizes ? lastRegisteredPageSizes.sizes : propPageSizesFlat;\n\n const facetBy = useMemo(\n () =>\n filterBy\n .filter((f) => f?.fieldKey)\n .map((f) => f.fieldKey)\n .join(','),\n [filterBy]\n );\n\n // No configured sort → undefined, so no `sort_by` is sent and Typesense sorts by\n // relevance (text match). The author's first option is the default when configured.\n const defaultOrderByQuery = useMemo(\n () => (orderByFlat[0] ? buildOrderByQuery(orderByFlat[0]) : undefined),\n [orderByFlat]\n );\n\n const orderByOptions = useMemo(\n () => orderByFlat.map((o) => ({ title: o.title, value: buildOrderByQuery(o) })),\n [orderByFlat]\n );\n\n // Initial page size: the size flagged `default` (first one wins), else the first\n // configured size, else DEFAULT_PAGE_SIZE (when no page-size component is present).\n const initPerPage = pageSizesFlat.find((ps) => ps.default)?.size || pageSizesFlat[0]?.size || DEFAULT_PAGE_SIZE;\n\n const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const searchRequestIdRef = useRef(0);\n\n const [urlParams, setUrlParams] = useState<Record<string, string | string[]>>(() => {\n if (typeof window === 'undefined') return {};\n return getSearchParamsFromUrl(window.location.href);\n });\n\n const [searchBoxValue, setSearchBoxValue] = useState((urlParams?.[UNIFORM_SEARCH_QUERY_KEY] as string) || '');\n const [isLoading, setIsLoading] = useState(true);\n const [entries, setEntries] = useState<Pagination<SearchHit>>({\n items: [],\n page: FIRST_PAGE,\n perPage: initPerPage,\n total: 0,\n totalPages: 0,\n });\n const [facets, setFacets] = useState<Facets>({});\n\n const pathname = useMemo(() => {\n if (typeof window === 'undefined') return '/';\n return window.location.pathname;\n }, []);\n\n const page =\n Number(urlParams?.[UNIFORM_SEARCH_PAGE_KEY]) - 1 > 0\n ? Number(urlParams?.[UNIFORM_SEARCH_PAGE_KEY]) - 1\n : FIRST_PAGE;\n const search = (urlParams?.[UNIFORM_SEARCH_QUERY_KEY] as string) || '';\n const perPage = Number(urlParams?.[UNIFORM_SEARCH_PAGE_SIZE_KEY]) || initPerPage;\n\n const selectedFilters = useMemo(\n () =>\n filterBy.reduce<Record<string, string[]>>((acc, filter) => {\n const value = urlParams?.[filter.fieldKey];\n if (value) {\n acc[filter.fieldKey] = Array.isArray(value) ? value : [value];\n }\n return acc;\n }, {}),\n [filterBy, urlParams]\n );\n\n // Translate the active facet selections into a `performSearch` `filters` object.\n // Hoisted to component scope (was inline in the fetch effect) so it can also feed the\n // `currentFilters` context value that in-engine components reuse. `excludeField` drops\n // one field for the disjunctive-facet requests below.\n const buildFilters = useCallback(\n (excludeField?: string) =>\n Object.entries(selectedFilters).reduce<Record<string, unknown>>((acc, [fieldKey, values]) => {\n if (excludeField && fieldKey === excludeField) return acc;\n if (!values || values.length === 0) return acc;\n const filterType = filterBy.find((f) => f.fieldKey === fieldKey)?.type;\n if (filterType === 'range') {\n const [min, max] = values;\n return { ...acc, [`${fieldKey}[gte]`]: min, [`${fieldKey}[lte]`]: max };\n }\n return { ...acc, [`${fieldKey}[in]`]: values };\n }, {}),\n [selectedFilters, filterBy]\n );\n\n const currentFilters = useMemo(() => buildFilters(), [buildFilters]);\n\n useEffect(() => {\n const requestId = ++searchRequestIdRef.current;\n\n const doFetch = async () => {\n const currentOrderByQuery = (urlParams?.[UNIFORM_SEARCH_ORDER_BY_KEY] as string) || defaultOrderByQuery;\n\n // Fields that have active selections need disjunctive queries so their\n // option lists stay complete (user can add more values via OR logic).\n const selectedFacetFields = Object.keys(selectedFilters).filter((k) => selectedFilters[k]?.length > 0);\n const queryByString = queryBy?.length ? queryBy.join(',') : undefined;\n const [mainResult, ...disjunctiveResults] = await Promise.all([\n performSearch({\n page,\n perPage,\n filters: buildFilters(),\n baseFilterBy: baseFilterString,\n facetBy,\n queryBy: queryByString,\n search,\n orderBy: currentOrderByQuery,\n locale,\n maxFacetValues,\n }),\n ...selectedFacetFields.map((field) =>\n performSearch({\n page: 0,\n perPage: 0,\n filters: buildFilters(field),\n baseFilterBy: baseFilterString,\n facetBy: field,\n queryBy: queryByString,\n search,\n orderBy: currentOrderByQuery,\n locale,\n maxFacetValues,\n })\n ),\n ]);\n\n if (requestId !== searchRequestIdRef.current) return;\n\n // Replace main facet counts for selected fields with their disjunctive counts.\n const mergedFacets = { ...(mainResult.facets ?? {}) };\n selectedFacetFields.forEach((field, i) => {\n const counts = disjunctiveResults[i]?.facets?.[field];\n if (counts) mergedFacets[field] = counts;\n });\n\n setEntries(mainResult.data ?? { items: [], total: 0, page, perPage, totalPages: 0 });\n setFacets(mergedFacets);\n };\n\n setIsLoading(true);\n doFetch()\n .catch((error) => console.error('Search fetch error:', error))\n .finally(() => setIsLoading(false));\n }, [\n baseFilterString,\n buildFilters,\n defaultOrderByQuery,\n facetBy,\n filterBy,\n locale,\n urlParams,\n page,\n perPage,\n performSearch,\n search,\n selectedFilters,\n ]);\n\n useEffect(() => {\n return () => {\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n };\n }, []);\n\n const updateUrl = useCallback(\n (params: URLSearchParams, { replace = false }: { replace?: boolean } = {}) => {\n const qs = params.toString();\n const url = qs ? `${pathname}?${qs}` : pathname;\n if (replace) {\n window.history.replaceState({}, '', url);\n } else {\n window.history.pushState({}, '', url);\n }\n setUrlParams(getSearchParamsFromUrl(window.location.href));\n },\n [pathname]\n );\n\n const commitSearchToUrl = useCallback(\n (value: string) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n if (!value) {\n params.delete(UNIFORM_SEARCH_QUERY_KEY);\n } else {\n params.set(UNIFORM_SEARCH_QUERY_KEY, value);\n }\n updateUrl(params, { replace: true });\n },\n [updateUrl]\n );\n\n const setSearchQuery = useCallback(\n (value: string) => {\n setSearchBoxValue(value);\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n if (!value) {\n commitSearchToUrl(value);\n } else {\n searchDebounceRef.current = setTimeout(() => commitSearchToUrl(value), searchDebounceMs);\n }\n },\n [commitSearchToUrl, searchDebounceMs]\n );\n\n const setPage = useCallback(\n (p: number) => {\n const params = new URLSearchParams(window.location.search);\n if (p === 0) {\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n } else {\n params.set(UNIFORM_SEARCH_PAGE_KEY, (p + 1).toString());\n }\n updateUrl(params);\n },\n [updateUrl]\n );\n\n const setPageSize = useCallback(\n (size: number) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n params.set(UNIFORM_SEARCH_PAGE_SIZE_KEY, size.toString());\n updateUrl(params);\n },\n [updateUrl]\n );\n\n const setOrderBy = useCallback(\n (orderByQuery: string) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n params.set(UNIFORM_SEARCH_ORDER_BY_KEY, orderByQuery);\n updateUrl(params);\n },\n [updateUrl]\n );\n\n const setSelectedFilters = useCallback(\n (nextSelected: Record<string, string[]>) => {\n const params = new URLSearchParams(window.location.search);\n params.delete(UNIFORM_SEARCH_PAGE_KEY);\n filterBy.forEach((f) => params.delete(f.fieldKey));\n Object.entries(nextSelected).forEach(([key, value]) => {\n const filter = filterBy.find((f) => f.fieldKey === key);\n if (!filter) return;\n value.forEach((v) => params.append(key, v));\n });\n updateUrl(params);\n },\n [filterBy, updateUrl]\n );\n\n const clearFilters = useCallback(() => {\n setIsLoading(true);\n setSearchBoxValue('');\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n window.history.pushState({}, '', pathname);\n setUrlParams(getSearchParamsFromUrl(window.location.href));\n }, [pathname]);\n\n const formatResultsSummary = useCallback(\n (template: string) => {\n if (!template) return '';\n const vars: Record<string, number | undefined> = {\n page: entries.page,\n perPage: entries.perPage,\n totalItems: entries.total,\n totalPages: entries.totalPages,\n };\n return template.replace(/{(\\w+)}/g, (match, key) => {\n const val = vars[key];\n return val != null ? String(val) : match;\n });\n },\n [entries]\n );\n\n const value: UseSearchReturn = useMemo(\n () => ({\n results: entries,\n facets,\n isLoading,\n\n searchBoxValue,\n setSearchQuery,\n\n page,\n setPage,\n pageSize: perPage,\n setPageSize,\n pageSizes: pageSizesFlat,\n registerPageSizes,\n unregisterPageSizes,\n\n orderByOptions,\n selectedOrderBy: (urlParams?.[UNIFORM_SEARCH_ORDER_BY_KEY] as string) || defaultOrderByQuery || '',\n setOrderBy,\n registerOrderBy,\n unregisterOrderBy,\n\n performSearch,\n queryBy,\n locale,\n baseFilterString,\n currentFilters,\n\n filterOptions: filterBy,\n setFilterOptions,\n registerFilterOption,\n unregisterFilterOption,\n selectedFilters,\n setSelectedFilters,\n clearFilters,\n\n formatResultsSummary,\n }),\n [\n baseFilterString,\n clearFilters,\n currentFilters,\n defaultOrderByQuery,\n entries,\n facets,\n filterBy,\n locale,\n performSearch,\n queryBy,\n setFilterOptions,\n registerFilterOption,\n unregisterFilterOption,\n formatResultsSummary,\n isLoading,\n orderByOptions,\n registerOrderBy,\n unregisterOrderBy,\n page,\n pageSizesFlat,\n registerPageSizes,\n unregisterPageSizes,\n perPage,\n searchBoxValue,\n selectedFilters,\n setOrderBy,\n setPage,\n setPageSize,\n setSearchQuery,\n setSelectedFilters,\n urlParams,\n ]\n );\n\n return <SearchContext.Provider value={value}>{children}</SearchContext.Provider>;\n};\n","export const UNIFORM_SEARCH_QUERY_KEY = 'search';\nexport const UNIFORM_SEARCH_PAGE_KEY = 'page';\nexport const UNIFORM_SEARCH_PAGE_SIZE_KEY = 'pageSize';\nexport const UNIFORM_SEARCH_ORDER_BY_KEY = 'orderBy';\nexport const FIRST_PAGE = 0;\nexport const DEFAULT_PAGE_SIZE = 10;\n","import type { OrderBy } from './types';\n\nexport const buildOrderByQuery = (orderBy: OrderBy) => {\n const field = orderBy.field as unknown;\n // sortByConfig produces an object value: { field: string; direction: 'asc' | 'desc' }\n if (field && typeof field === 'object' && 'field' in (field as object)) {\n const sv = field as { field: string; direction: string };\n return `${sv.field}_${sv.direction.toUpperCase()}`;\n }\n // Plain string \"relevance\" or missing direction → no sort\n if (!field || field === 'relevance') return '';\n return `${field}_${orderBy.direction ?? 'ASC'}`;\n};\n\nexport const getSearchParamsFromUrl = (urlString: string): Record<string, string | string[]> => {\n if (!urlString) {\n return {};\n }\n\n const url = new URL(urlString);\n const params: Record<string, string | string[]> = {};\n\n url.searchParams.forEach((value, key) => {\n if (params[key]) {\n params[key] = Array.isArray(params[key])\n ? [...(params[key] as string[]), value]\n : [params[key] as string, value];\n } else {\n params[key] = value;\n }\n });\n\n return params;\n};\n","type RawField = {\n type?: string;\n value?: unknown;\n locales?: Record<string, unknown>;\n};\n\ntype RawBlockItem = {\n type?: string;\n fields?: Record<string, RawField>;\n};\n\nfunction resolveFieldValue(field: RawField, locale = 'en-us'): unknown {\n if (!field || typeof field !== 'object') return field;\n\n if (field.type === '$block' && Array.isArray(field.value)) {\n return flattenBlockParams(field.value as RawBlockItem[], locale);\n }\n\n if (field.locales && typeof field.locales === 'object') {\n return field.locales[locale] ?? field.value;\n }\n\n return field.value;\n}\n\nexport function flattenBlockParams<T = Record<string, unknown>>(\n items: unknown,\n locale = 'en-us'\n): T[] {\n if (!Array.isArray(items)) return [];\n return items.map(item => {\n const result: Record<string, unknown> = {};\n const blockItem = item as RawBlockItem;\n if (blockItem.fields) {\n for (const [key, field] of Object.entries(blockItem.fields)) {\n result[key] = resolveFieldValue(field, locale);\n }\n }\n return result as T;\n });\n}\n","import { useContext } from 'react';\nimport { SearchContext } from './SearchProvider';\nimport type { UseSearchReturn } from './SearchProvider';\n\nexport function useSearch(): UseSearchReturn {\n const context = useContext(SearchContext);\n if (!context) {\n throw new Error('useSearch must be used within a <SearchProvider>');\n }\n return context;\n}\n","import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type FocusEvent,\n type HTMLAttributes,\n type InputHTMLAttributes,\n type KeyboardEvent,\n type MouseEvent,\n} from 'react';\n\nimport type { CollectionResult, SearchHit } from '../types';\nimport type { SearchParams } from '../client';\n\n/**\n * Headless typeahead / autocomplete (\"search-as-you-type\").\n *\n * This hook owns the query state, debounced fetching, open/close + keyboard state, and\n * exposes WAI-ARIA prop getters (combobox pattern) so a UI can be wired up with almost no\n * boilerplate. It is intentionally independent of `SearchProvider` so it can live anywhere\n * (a site header, a modal, etc.) and drive its own lightweight instant-results request.\n *\n * It leans on the search backend's prefix search (enabled by default in Typesense) — every\n * keystroke performs a small `performSearch` for the top few hits. Matched terms come back\n * as highlights on each hit (see `getHighlightMatch`).\n */\n\nconst NO_ACTIVE_INDEX = -1;\nconst DEFAULT_PER_PAGE = 6;\nconst DEFAULT_MIN_QUERY_LENGTH = 1;\nconst DEFAULT_DEBOUNCE_MS = 150;\n\nexport interface UseAutocompleteOptions {\n /**\n * The search function (same signature used by `SearchProvider`). Typically\n * `createSearchClient(...).performSearch`.\n */\n performSearch: (params: SearchParams) => Promise<CollectionResult>;\n /** Fields to query against (mirrors `SearchProvider`'s `queryBy`). */\n queryBy?: string[];\n /** A base Typesense `filter_by` string applied to every request. */\n baseFilterBy?: string;\n /** Active facet selections (same `filters` object `SearchProvider` sends), so the\n * autocomplete request mirrors the main results' filtered context. */\n filters?: Record<string, unknown>;\n /** Locale to scope the search to (per-locale collection). */\n locale?: string;\n /** Max number of suggestions to fetch/show. Default: 6. */\n perPage?: number;\n /** Minimum number of characters before a request is made. Default: 1. */\n minQueryLength?: number;\n /** Debounce, in ms, between the last keystroke and the request. Default: 150. */\n debounceMs?: number;\n /** Open the panel as soon as the input is focused (if there is anything to show). Default: false. */\n openOnFocus?: boolean;\n /**\n * Called when a suggestion is chosen (Enter on the active item, or a click). Use this to\n * navigate to the item's page.\n */\n onSelect?: (item: SearchHit) => void;\n /**\n * Called when the raw query is submitted (Enter with no active suggestion). Use this to send\n * the user to a full search-results page.\n */\n onSubmit?: (query: string) => void;\n\n // ── Typo tolerance passthrough (optional; omit → backend defaults) ──\n maxTypos?: number;\n minLengthFor1Typo?: number;\n minLengthFor2Typos?: number;\n typoFallbackThreshold?: number;\n prioritizeExactMatch?: boolean;\n}\n\nexport interface AutocompleteInputProps extends InputHTMLAttributes<HTMLInputElement> {\n role: 'combobox';\n 'aria-autocomplete': 'list';\n 'aria-expanded': boolean;\n 'aria-controls': string;\n 'aria-activedescendant'?: string;\n autoComplete: 'off';\n}\n\nexport interface AutocompleteListboxProps extends HTMLAttributes<HTMLElement> {\n role: 'listbox';\n id: string;\n}\n\nexport interface AutocompleteItemProps extends HTMLAttributes<HTMLElement> {\n role: 'option';\n id: string;\n 'aria-selected': boolean;\n}\n\nexport interface UseAutocompleteReturn {\n /** Current input value. */\n query: string;\n /** Set the input value programmatically (does not trigger `onSubmit`). */\n setQuery: (value: string) => void;\n /** The current suggestions (top hits for the query). */\n suggestions: SearchHit[];\n /** True while a request is in flight. */\n isLoading: boolean;\n /** Whether the suggestions panel should be shown. */\n isOpen: boolean;\n /** Index of the keyboard-active suggestion, or -1 when none is active. */\n activeIndex: number;\n /** The keyboard-active suggestion, or null. */\n activeItem: SearchHit | null;\n /** Open the panel (if there is anything to show). */\n open: () => void;\n /** Close the panel and clear the active item. */\n close: () => void;\n /** Clear the query, suggestions, and close the panel. */\n clear: () => void;\n /** Select a suggestion (fires `onSelect` and closes the panel). */\n selectItem: (item: SearchHit) => void;\n\n /** Props for a `<label>` associated with the input. */\n getLabelProps: () => { id: string; htmlFor: string };\n /** Props for the `<input>` (combobox). Pass-through props are merged, yours win. */\n getInputProps: (userProps?: InputHTMLAttributes<HTMLInputElement>) => AutocompleteInputProps;\n /** Props for the suggestions container (listbox). */\n getListboxProps: (userProps?: HTMLAttributes<HTMLElement>) => AutocompleteListboxProps;\n /** Props for a single suggestion (option). */\n getItemProps: (params: {\n item: SearchHit;\n index: number;\n userProps?: HTMLAttributes<HTMLElement>;\n }) => AutocompleteItemProps;\n}\n\nexport function useAutocomplete(options: UseAutocompleteOptions): UseAutocompleteReturn {\n const {\n performSearch,\n queryBy,\n baseFilterBy,\n filters,\n locale,\n perPage = DEFAULT_PER_PAGE,\n minQueryLength = DEFAULT_MIN_QUERY_LENGTH,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n openOnFocus = false,\n onSelect,\n onSubmit,\n maxTypos,\n minLengthFor1Typo,\n minLengthFor2Typos,\n typoFallbackThreshold,\n prioritizeExactMatch,\n } = options;\n\n const baseId = useId();\n const listboxId = `${baseId}-listbox`;\n const labelId = `${baseId}-label`;\n const inputId = `${baseId}-input`;\n const optionId = useCallback((index: number) => `${baseId}-option-${index}`, [baseId]);\n\n const [query, setQueryState] = useState('');\n const [suggestions, setSuggestions] = useState<SearchHit[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(NO_ACTIVE_INDEX);\n\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const requestIdRef = useRef(0);\n\n const queryByString = useMemo(() => (queryBy?.length ? queryBy.join(',') : undefined), [queryBy]);\n\n // Keep the latest options in a ref so the debounced fetch always sees fresh values\n // without re-creating the callback (which would reset the debounce timer on every render).\n const fetchConfigRef = useRef({\n performSearch,\n queryByString,\n baseFilterBy,\n filters,\n locale,\n perPage,\n maxTypos,\n minLengthFor1Typo,\n minLengthFor2Typos,\n typoFallbackThreshold,\n prioritizeExactMatch,\n });\n fetchConfigRef.current = {\n performSearch,\n queryByString,\n baseFilterBy,\n filters,\n locale,\n perPage,\n maxTypos,\n minLengthFor1Typo,\n minLengthFor2Typos,\n typoFallbackThreshold,\n prioritizeExactMatch,\n };\n\n const runSearch = useCallback((value: string) => {\n const requestId = ++requestIdRef.current;\n const cfg = fetchConfigRef.current;\n setIsLoading(true);\n cfg\n .performSearch({\n page: 0,\n perPage: cfg.perPage,\n queryBy: cfg.queryByString,\n baseFilterBy: cfg.baseFilterBy,\n filters: cfg.filters,\n search: value,\n locale: cfg.locale,\n maxTypos: cfg.maxTypos,\n minLengthFor1Typo: cfg.minLengthFor1Typo,\n minLengthFor2Typos: cfg.minLengthFor2Typos,\n typoFallbackThreshold: cfg.typoFallbackThreshold,\n prioritizeExactMatch: cfg.prioritizeExactMatch,\n })\n .then((result) => {\n // Ignore out-of-order responses (a newer keystroke already fired).\n if (requestId !== requestIdRef.current) return;\n const items = result?.data?.items ?? [];\n setSuggestions(items);\n setActiveIndex(NO_ACTIVE_INDEX);\n setIsOpen(items.length > 0);\n })\n .catch((error) => {\n if (requestId !== requestIdRef.current) return;\n console.error('Autocomplete fetch error:', error);\n setSuggestions([]);\n })\n .finally(() => {\n if (requestId !== requestIdRef.current) return;\n setIsLoading(false);\n });\n }, []);\n\n const setQuery = useCallback(\n (value: string) => {\n setQueryState(value);\n if (debounceRef.current) clearTimeout(debounceRef.current);\n\n if (value.trim().length < minQueryLength) {\n // Below the threshold: cancel any pending request and reset the panel.\n requestIdRef.current++;\n setSuggestions([]);\n setActiveIndex(NO_ACTIVE_INDEX);\n setIsOpen(false);\n setIsLoading(false);\n return;\n }\n\n debounceRef.current = setTimeout(() => runSearch(value), debounceMs);\n },\n [debounceMs, minQueryLength, runSearch]\n );\n\n useEffect(() => {\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n };\n }, []);\n\n const open = useCallback(() => {\n if (suggestions.length > 0) setIsOpen(true);\n }, [suggestions.length]);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setActiveIndex(NO_ACTIVE_INDEX);\n }, []);\n\n const clear = useCallback(() => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n requestIdRef.current++;\n setQueryState('');\n setSuggestions([]);\n setActiveIndex(NO_ACTIVE_INDEX);\n setIsOpen(false);\n setIsLoading(false);\n }, []);\n\n const selectItem = useCallback(\n (item: SearchHit) => {\n close();\n onSelect?.(item);\n },\n [close, onSelect]\n );\n\n const activeItem = activeIndex >= 0 ? (suggestions[activeIndex] ?? null) : null;\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n const count = suggestions.length;\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault();\n if (!isOpen) {\n open();\n return;\n }\n if (count === 0) return;\n // Cycle: -1 (input) → 0 → … → last → -1.\n setActiveIndex((prev) => (prev + 1 > count - 1 ? NO_ACTIVE_INDEX : prev + 1));\n break;\n }\n case 'ArrowUp': {\n event.preventDefault();\n if (!isOpen) {\n open();\n return;\n }\n if (count === 0) return;\n setActiveIndex((prev) => (prev <= NO_ACTIVE_INDEX ? count - 1 : prev - 1));\n break;\n }\n case 'Home': {\n if (!isOpen || count === 0) return;\n event.preventDefault();\n setActiveIndex(0);\n break;\n }\n case 'End': {\n if (!isOpen || count === 0) return;\n event.preventDefault();\n setActiveIndex(count - 1);\n break;\n }\n case 'Enter': {\n if (isOpen && activeIndex >= 0 && suggestions[activeIndex]) {\n event.preventDefault();\n selectItem(suggestions[activeIndex]);\n } else {\n // No active suggestion → submit the raw query.\n close();\n onSubmit?.(query);\n }\n break;\n }\n case 'Escape': {\n if (isOpen) {\n event.preventDefault();\n close();\n }\n break;\n }\n default:\n break;\n }\n },\n [activeIndex, close, isOpen, onSubmit, open, query, selectItem, suggestions]\n );\n\n const getLabelProps = useCallback(() => ({ id: labelId, htmlFor: inputId }), [labelId, inputId]);\n\n const getInputProps = useCallback(\n (userProps: InputHTMLAttributes<HTMLInputElement> = {}): AutocompleteInputProps => ({\n ...userProps,\n id: userProps.id ?? inputId,\n role: 'combobox',\n 'aria-autocomplete': 'list',\n 'aria-expanded': isOpen,\n 'aria-controls': listboxId,\n 'aria-activedescendant': activeIndex >= 0 ? optionId(activeIndex) : undefined,\n 'aria-labelledby': userProps['aria-labelledby'] ?? labelId,\n autoComplete: 'off',\n value: query,\n onChange: (event) => {\n setQuery(event.target.value);\n userProps.onChange?.(event);\n },\n onKeyDown: (event) => {\n handleKeyDown(event);\n userProps.onKeyDown?.(event);\n },\n onFocus: (event: FocusEvent<HTMLInputElement>) => {\n if (openOnFocus) open();\n userProps.onFocus?.(event);\n },\n onBlur: (event: FocusEvent<HTMLInputElement>) => {\n close();\n userProps.onBlur?.(event);\n },\n }),\n [\n activeIndex,\n close,\n handleKeyDown,\n inputId,\n isOpen,\n labelId,\n listboxId,\n open,\n openOnFocus,\n optionId,\n query,\n setQuery,\n ]\n );\n\n const getListboxProps = useCallback(\n (userProps: HTMLAttributes<HTMLElement> = {}): AutocompleteListboxProps => ({\n ...userProps,\n id: listboxId,\n role: 'listbox',\n 'aria-labelledby': userProps['aria-labelledby'] ?? labelId,\n }),\n [labelId, listboxId]\n );\n\n const getItemProps = useCallback(\n ({\n item,\n index,\n userProps = {},\n }: {\n item: SearchHit;\n index: number;\n userProps?: HTMLAttributes<HTMLElement>;\n }): AutocompleteItemProps => ({\n ...userProps,\n id: optionId(index),\n role: 'option',\n 'aria-selected': index === activeIndex,\n onMouseMove: (event) => {\n if (index !== activeIndex) setActiveIndex(index);\n userProps.onMouseMove?.(event);\n },\n // Prevent the input from blurring (which would close the panel) before the click lands.\n onMouseDown: (event: MouseEvent<HTMLElement>) => {\n event.preventDefault();\n userProps.onMouseDown?.(event);\n },\n onClick: (event) => {\n selectItem(item);\n userProps.onClick?.(event);\n },\n }),\n [activeIndex, optionId, selectItem]\n );\n\n return {\n query,\n setQuery,\n suggestions,\n isLoading,\n isOpen,\n activeIndex,\n activeItem,\n open,\n close,\n clear,\n selectItem,\n getLabelProps,\n getInputProps,\n getListboxProps,\n getItemProps,\n };\n}\n","import { useMemo } from 'react';\n\nexport const DOTS = '...';\n\ninterface UsePaginationParams {\n currentPage: number;\n totalCount: number;\n perPage: number;\n siblingCount?: number;\n}\n\nconst range = (start: number, end: number) => {\n const length = end - start + 1;\n return Array.from({ length }, (_, idx) => idx + start);\n};\n\nexport const usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }: UsePaginationParams) =>\n useMemo(() => {\n const totalPageCount = Math.ceil(totalCount / perPage);\n const totalPageNumbers = siblingCount + 5;\n\n if (totalPageNumbers >= totalPageCount) {\n return range(1, totalPageCount);\n }\n\n const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);\n const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPageCount);\n\n const shouldShowLeftDots = leftSiblingIndex > 2;\n const shouldShowRightDots = rightSiblingIndex < totalPageCount - 2;\n\n const firstPageIndex = 1;\n const lastPageIndex = totalPageCount;\n\n if (!shouldShowLeftDots && shouldShowRightDots) {\n const leftItemCount = 2 + 2 * siblingCount;\n const leftRange = range(1, leftItemCount);\n return [...leftRange, DOTS, totalPageCount];\n }\n\n if (shouldShowLeftDots && !shouldShowRightDots) {\n const rightItemCount = 2 + 2 * siblingCount;\n const rightRange = range(totalPageCount - rightItemCount + 1, totalPageCount);\n return [firstPageIndex, DOTS, ...rightRange];\n }\n\n if (shouldShowLeftDots && shouldShowRightDots) {\n const middleRange = range(leftSiblingIndex, rightSiblingIndex);\n return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];\n }\n }, [totalCount, perPage, siblingCount, currentPage]);\n","import { useMemo, useCallback } from 'react';\nimport { useSearch } from './useSearch';\nimport { usePagination, DOTS } from './usePagination';\n\nexport interface UseSearchPaginationReturn {\n pages: (number | string)[];\n currentPage: number;\n hasPrev: boolean;\n hasNext: boolean;\n goToPage: (page: number) => void;\n goToPrev: () => void;\n goToNext: () => void;\n isLoading: boolean;\n}\n\nexport function useSearchPagination(siblingCount?: number): UseSearchPaginationReturn {\n const { page, pageSize, results, setPage, isLoading } = useSearch();\n\n const pages = usePagination({\n currentPage: page,\n totalCount: results.total,\n perPage: pageSize,\n siblingCount,\n }) || [];\n\n const lastPage = useMemo(\n () => Number(pages[pages.length - 1]) || 0,\n [pages]\n );\n\n const goToPage = useCallback(\n (p: number) => { if (!isLoading) setPage(p); },\n [isLoading, setPage]\n );\n\n const goToPrev = useCallback(\n () => { if (page > 0 && !isLoading) setPage(page - 1); },\n [page, isLoading, setPage]\n );\n\n const goToNext = useCallback(\n () => { if (page < lastPage && !isLoading) setPage(page + 1); },\n [page, lastPage, isLoading, setPage]\n );\n\n return {\n pages,\n currentPage: page,\n hasPrev: page > 0,\n hasNext: page < lastPage,\n goToPage,\n goToPrev,\n goToNext,\n isLoading,\n };\n}\n\nexport { DOTS };\n","import { createContext, useContext, type FC, type ReactNode } from 'react';\nimport type { SearchHit, SearchItemConfig } from '../types';\n\nexport type UrlResolver = (hit: SearchHit) => string | undefined;\n\nconst NOOP_RESOLVER: UrlResolver = () => undefined;\n\n// Set by the user to override URL resolution for the entire subtree.\nconst UserUrlResolverContext = createContext<UrlResolver | null>(null);\n\n// Set by SearchList to provide config-driven defaults within its subtree.\nconst DefaultUrlResolverContext = createContext<UrlResolver>(NOOP_RESOLVER);\n\n/** Wrap your search tree to override URL building for all hits. */\nexport const SearchItemUrlResolverProvider: FC<{ resolver: UrlResolver; children: ReactNode }> = ({\n resolver,\n children,\n}) => (\n <UserUrlResolverContext.Provider value={resolver}>\n {children}\n </UserUrlResolverContext.Provider>\n);\n\n/** Used internally by SearchList to register the Canvas-config-driven default. */\nexport const SearchItemDefaultUrlResolverProvider: FC<{ resolver: UrlResolver; children: ReactNode }> = ({\n resolver,\n children,\n}) => (\n <DefaultUrlResolverContext.Provider value={resolver}>\n {children}\n </DefaultUrlResolverContext.Provider>\n);\n\n/**\n * Always returns a resolver. User-provided resolver (SearchItemUrlResolverProvider)\n * takes priority over the default registered by SearchList.\n */\nexport function useUrlResolver(): UrlResolver {\n const user = useContext(UserUrlResolverContext);\n const def = useContext(DefaultUrlResolverContext);\n return user ?? def;\n}\n\nexport interface UrlResolverOptions {\n /** nodeId → resolved locale-specific path (from project map). */\n pathsByNodeId?: Record<string, string>;\n /** Current locale, used as fallback for legacy :locale token in urlTemplate. */\n locale?: string;\n}\n\n/**\n * Builds the default URL resolver from Canvas SearchItem configs.\n * Exported so custom resolvers can compose with it.\n */\nfunction resolveField<T = unknown>(obj: SearchHit, path: string): T | undefined {\n return path\n .split(\".\")\n .reduce<any>((current, key) => {\n if (current == null) {\n return undefined;\n }\n\n // If current value is an array, use first item\n if (Array.isArray(current)) {\n current = current[0];\n }\n\n return current?.[key];\n }, obj);\n\n}\n\nexport function createDefaultUrlResolver(\n configs: SearchItemConfig[],\n options: UrlResolverOptions = {}\n): UrlResolver {\n const { pathsByNodeId = {}, locale } = options;\n\n return (hit: SearchHit): string | undefined => {\n const hitSource = typeof hit.source === 'string' ? hit.source : '';\n const hitType = typeof hit.type === 'string' ? hit.type : '';\n const config = configs.find(c => c.source === hitSource && c.type === hitType);\n if (config) {\n const template =\n (config.nodeId ? pathsByNodeId[config.nodeId] : undefined) ?? config.urlTemplate;\n\n if (template) {\n return template.replace(/:([a-zA-Z0-9_-]+)/g, (_, token) => {\n if (token === 'locale') return locale ?? '';\n const fieldKey = config.tokenMapping?.[token];\n if (!fieldKey) return '';\n const val = resolveField(hit, fieldKey);\n return Array.isArray(val) ? String(val[0] ?? '') : String(val ?? '');\n });\n }\n }\n\n if (hitSource === 'composition' && typeof hit.path === 'string') {\n return hit.path;\n }\n\n return undefined;\n };\n}\n"],"mappings":"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","names":["value","_a","_b","import_react","import_react","_a","import_react","import_react","import_react","import_jsx_runtime","_a"]}