@samline/autocomplete 1.0.0 → 2.0.0
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/CHANGELOG.md +29 -0
- package/LICENSE.md +1 -1
- package/README.md +208 -32
- package/dist/browser/global.global.js +505 -274
- package/dist/browser/global.global.js.map +1 -1
- package/dist/browser/index.cjs +663 -0
- package/dist/browser/index.cjs.map +1 -0
- package/dist/browser/index.d.cts +31 -0
- package/dist/browser/index.d.ts +31 -0
- package/dist/browser/index.js +24 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chunk-4IPBCQDZ.js +218 -0
- package/dist/chunk-4IPBCQDZ.js.map +1 -0
- package/dist/chunk-5UZVFHB4.js +70 -0
- package/dist/chunk-5UZVFHB4.js.map +1 -0
- package/dist/chunk-AD7T42HJ.js +1 -0
- package/dist/chunk-AD7T42HJ.js.map +1 -0
- package/dist/chunk-PPDQPLPE.js +1 -0
- package/dist/chunk-PPDQPLPE.js.map +1 -0
- package/dist/chunk-U5JYDGKW.js +403 -0
- package/dist/chunk-U5JYDGKW.js.map +1 -0
- package/dist/core/index.cjs +181 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +31 -0
- package/dist/core/index.d.ts +31 -0
- package/dist/core/index.js +22 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +527 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -69
- package/dist/index.d.ts +20 -69
- package/dist/index.js +54 -405
- package/dist/index.js.map +1 -1
- package/dist/render-B38UXYCT.d.ts +7 -0
- package/dist/render-WD69VMUs.d.cts +7 -0
- package/dist/styles.css +19 -9
- package/dist/types-CPvjhqjb.d.cts +118 -0
- package/dist/types-CPvjhqjb.d.ts +118 -0
- package/dist/vanilla/index.cjs +598 -0
- package/dist/vanilla/index.cjs.map +1 -0
- package/dist/vanilla/index.d.cts +11 -0
- package/dist/vanilla/index.d.ts +11 -0
- package/dist/vanilla/index.js +13 -0
- package/dist/vanilla/index.js.map +1 -0
- package/package.json +19 -4
- package/dist/browser/global.d.ts +0 -61
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/browser/index.ts","../../src/constants.ts","../../src/core/cache.ts","../../src/core/source.ts","../../src/core/state.ts","../../src/core/text.ts","../../src/vanilla/dom.ts","../../src/vanilla/render.ts","../../src/browser/registry.ts"],"sourcesContent":["export {\n Autocomplete,\n Autocomplete as default,\n available,\n browser,\n destroyAutocomplete,\n destroyAutocompletes,\n getAutocomplete,\n getAutocompletes,\n newAutocomplete,\n} from './registry'\nexport type { AutocompleteApi, AutocompleteAvailable, NewAutocompleteInput } from './registry'\nexport type { AutocompleteController, AutocompleteOptions, AutocompleteTarget } from '../core/types'\n","export const DEFAULT_QUERY_PARAM = 'q'\nexport const DEFAULT_MIN_LENGTH = 2\nexport const DEFAULT_DEBOUNCE = 250\nexport const DEFAULT_MAX_RESULTS = 8\n\n/** Cached queries kept per instance when `cache` is enabled. */\nexport const DEFAULT_CACHE_SIZE = 50\n/** Milliseconds a cached query stays fresh; five minutes. */\nexport const DEFAULT_CACHE_TTL = 300_000\n\nexport const POPUP_ATTRIBUTE = 'data-autocomplete-popup'\nexport const LIST_ATTRIBUTE = 'data-autocomplete-list'\nexport const OPTION_ATTRIBUTE = 'data-autocomplete-option'\nexport const STATUS_ATTRIBUTE = 'data-autocomplete-status'\nexport const LIVE_ATTRIBUTE = 'data-autocomplete-live'\nexport const HIGHLIGHT_ATTRIBUTE = 'data-autocomplete-highlight'\n","import { DEFAULT_CACHE_SIZE, DEFAULT_CACHE_TTL } from '../constants'\nimport type { AutocompleteCache, AutocompleteCacheOptions } from './types'\n\ninterface CacheEntry<T> {\n value: readonly T[]\n expires: number\n}\n\n/** Least-recently-used cache of suggestions keyed by query. */\nexport const createCache = <T>(\n { max = DEFAULT_CACHE_SIZE, ttl = DEFAULT_CACHE_TTL }: AutocompleteCacheOptions = {},\n now: () => number = Date.now\n): AutocompleteCache<T> => {\n const entries = new Map<string, CacheEntry<T>>()\n\n const get = (key: string): readonly T[] | undefined => {\n const entry = entries.get(key)\n if (!entry) return undefined\n\n entries.delete(key)\n if (entry.expires <= now()) return undefined\n\n // Re-insert so the entry becomes the most recently used.\n entries.set(key, entry)\n return entry.value\n }\n\n const set = (key: string, value: readonly T[]): void => {\n entries.delete(key)\n entries.set(key, { value, expires: now() + ttl })\n\n while (entries.size > max) {\n const oldest = entries.keys().next()\n if (oldest.done) break\n entries.delete(oldest.value)\n }\n }\n\n return {\n get,\n set,\n clear: () => entries.clear(),\n get size() {\n return entries.size\n },\n }\n}\n","import { DEFAULT_QUERY_PARAM } from '../constants'\nimport type {\n AutocompleteEndpointOptions,\n AutocompleteParser,\n AutocompleteResponseMapper,\n AutocompleteSourceFunction,\n} from './types'\n\nexport const parseJsonResponse: AutocompleteParser = async response => response.json()\n\nconst invalidPayloadError = (): TypeError =>\n new TypeError(\n 'Autocomplete response does not contain an array. Configure mapResponse(payload, response, query).'\n )\n\n/** Accepts an array, or an object with a `suggestions`, `items` or `data` array. */\nexport const mapCommonResponse: AutocompleteResponseMapper<unknown> = payload => {\n if (Array.isArray(payload)) return payload\n if (!payload || typeof payload !== 'object') throw invalidPayloadError()\n\n const record = payload as Record<string, unknown>\n if (Array.isArray(record.suggestions)) return record.suggestions\n if (Array.isArray(record.items)) return record.items\n if (Array.isArray(record.data)) return record.data\n\n throw invalidPayloadError()\n}\n\n/** Builds a source that fetches suggestions from an HTTP endpoint. */\nexport const fromEndpoint = <T>({\n endpoint,\n queryParam = DEFAULT_QUERY_PARAM,\n requestInit = {},\n parseResponse = parseJsonResponse,\n mapResponse = mapCommonResponse as AutocompleteResponseMapper<T>,\n}: AutocompleteEndpointOptions<T>): AutocompleteSourceFunction<T> => {\n if (!endpoint) throw new TypeError('Autocomplete requires an endpoint.')\n\n return async (query, { signal, input }) => {\n const resolved = typeof endpoint === 'function' ? endpoint(query, input) : endpoint\n const url = new URL(resolved, document.baseURI)\n if (queryParam !== null) url.searchParams.set(queryParam, query)\n\n const init = typeof requestInit === 'function' ? requestInit(query, input) : requestInit\n const response = await fetch(url, { ...init, signal })\n if (!response.ok) throw new Error(`Autocomplete request failed with HTTP ${response.status}.`)\n\n const payload = await parseResponse(response, query)\n const result = await mapResponse(payload, response, query)\n if (!Array.isArray(result))\n throw new TypeError('Autocomplete mapResponse must return an array.')\n\n return result\n }\n}\n","import type {\n AutocompleteMessage,\n AutocompleteMessageContext,\n AutocompleteMessages,\n AutocompleteOptions,\n} from './types'\n\nexport const DEFAULT_MESSAGES: AutocompleteMessages = {\n loading: 'Searching...',\n empty: 'No results',\n error: 'Search failed',\n results: ({ count }) => (count === 1 ? '1 result available' : `${count} results available`),\n}\n\nexport const resolveMessage = (\n message: AutocompleteMessage,\n context: AutocompleteMessageContext\n): string => (typeof message === 'function' ? message(context) : message)\n\nconst readField = (suggestion: unknown, keys: readonly string[]): string => {\n if (typeof suggestion === 'string') return suggestion\n if (!suggestion || typeof suggestion !== 'object') return String(suggestion ?? '')\n\n const record = suggestion as Record<string, unknown>\n const key = keys.find(name => record[name] !== undefined && record[name] !== null)\n return key === undefined ? '' : String(record[key])\n}\n\nexport const getDefaultLabel = (suggestion: unknown): string =>\n readField(suggestion, ['label', 'value', 'title', 'name'])\n\nexport const getDefaultValue = (suggestion: unknown): string =>\n readField(suggestion, ['value', 'label', 'title', 'name'])\n\nexport const getDefaultId = (suggestion: unknown, index: number): string | number => {\n if (!suggestion || typeof suggestion !== 'object') return index\n return ((suggestion as Record<string, unknown>).id as string | number | undefined) ?? index\n}\n\nconst assertNonNegativeInteger = (value: number | undefined, name: string): void => {\n if (value !== undefined && (!Number.isInteger(value) || value < 0)) {\n throw new TypeError(`Autocomplete ${name} must be a non-negative integer.`)\n }\n}\n\nconst assertPositiveInteger = (value: number | undefined, name: string): void => {\n if (value !== undefined && (!Number.isInteger(value) || value < 1)) {\n throw new TypeError(`Autocomplete ${name} must be an integer greater than zero.`)\n }\n}\n\nexport const validateOptions = <T>(options: AutocompleteOptions<T>): void => {\n const hasSource = options.source !== undefined\n const hasEndpoint = options.endpoint !== undefined\n\n if (hasSource && hasEndpoint) {\n throw new TypeError('Autocomplete accepts either a source or an endpoint, not both.')\n }\n if (!hasSource && !options.endpoint) {\n throw new TypeError('Autocomplete requires a source or an endpoint.')\n }\n if (hasSource && !Array.isArray(options.source) && typeof options.source !== 'function') {\n throw new TypeError('Autocomplete source must be an array or a function.')\n }\n\n assertNonNegativeInteger(options.minLength, 'minLength')\n assertNonNegativeInteger(options.debounce, 'debounce')\n assertPositiveInteger(options.maxResults, 'maxResults')\n\n if (typeof options.cache === 'object') {\n assertPositiveInteger(options.cache.max, 'cache.max')\n assertNonNegativeInteger(options.cache.ttl, 'cache.ttl')\n }\n}\n","import type { AutocompleteHighlightPart } from './types'\n\nexport type AutocompleteNormalizer = (value: string) => string\n\nexport interface FilterSuggestionsOptions<T> {\n getText: (suggestion: T) => string\n normalize?: AutocompleteNormalizer\n}\n\nconst COMBINING_MARKS = /\\p{M}/gu\nconst WHITESPACE = /\\s+/\n\n/** Lowercases and removes diacritics, so `Cañón` and `canon` compare equal. */\nexport const normalizeText: AutocompleteNormalizer = value =>\n value.normalize('NFD').replace(COMBINING_MARKS, '').toLowerCase()\n\nconst getTerms = (query: string, normalize: AutocompleteNormalizer): string[] =>\n Array.from(new Set(normalize(query).split(WHITESPACE).filter(Boolean)))\n\n/** Keeps the suggestions whose text contains every word of the query. */\nexport const filterSuggestions = <T>(\n suggestions: readonly T[],\n query: string,\n { getText, normalize = normalizeText }: FilterSuggestionsOptions<T>\n): T[] => {\n const terms = getTerms(query, normalize)\n if (terms.length === 0) return [...suggestions]\n\n return suggestions.filter(suggestion => {\n const text = normalize(getText(suggestion))\n return terms.every(term => text.includes(term))\n })\n}\n\n/**\n * Splits `text` into parts that match a word of `query` and parts that do\n * not. Matching runs on normalized text but the parts keep the original\n * characters, accents included.\n */\nexport const highlight = (\n text: string,\n query: string,\n normalize: AutocompleteNormalizer = normalizeText\n): AutocompleteHighlightPart[] => {\n const terms = getTerms(query, normalize)\n if (terms.length === 0 || text === '') return text === '' ? [] : [{ text, match: false }]\n\n // Map every normalized code unit back to the original character it came from.\n let normalized = ''\n const starts: number[] = []\n const ends: number[] = []\n let offset = 0\n\n for (const char of text) {\n const piece = normalize(char)\n // A character removed by normalization (a combining accent) belongs to\n // the character before it, so a match keeps its accent.\n if (piece === '' && ends.length > 0) ends[ends.length - 1] = offset + char.length\n normalized += piece\n for (let index = 0; index < piece.length; index += 1) {\n starts.push(offset)\n ends.push(offset + char.length)\n }\n offset += char.length\n }\n\n const ranges: [number, number][] = []\n for (const term of terms) {\n let found = normalized.indexOf(term)\n while (found !== -1) {\n ranges.push([starts[found]!, ends[found + term.length - 1]!])\n found = normalized.indexOf(term, found + 1)\n }\n }\n\n if (ranges.length === 0) return [{ text, match: false }]\n\n ranges.sort((a, b) => a[0] - b[0])\n const merged: [number, number][] = []\n for (const range of ranges) {\n const last = merged[merged.length - 1]\n if (last && range[0] <= last[1]) last[1] = Math.max(last[1], range[1])\n else merged.push([range[0], range[1]])\n }\n\n const parts: AutocompleteHighlightPart[] = []\n let cursor = 0\n for (const [start, end] of merged) {\n if (start > cursor) parts.push({ text: text.slice(cursor, start), match: false })\n parts.push({ text: text.slice(start, end), match: true })\n cursor = end\n }\n if (cursor < text.length) parts.push({ text: text.slice(cursor), match: false })\n\n return parts\n}\n","import { HIGHLIGHT_ATTRIBUTE } from '../constants'\nimport type { AutocompleteHighlightPart, AutocompleteTarget } from '../core/types'\n\nexport const resolveInput = (target: AutocompleteTarget): HTMLInputElement => {\n const element = typeof target === 'string' ? document.querySelector(target) : target\n\n if (!(element instanceof HTMLInputElement)) {\n throw new TypeError('Autocomplete requires an HTMLInputElement or a valid selector.')\n }\n\n return element\n}\n\nexport const sanitizeId = (value: unknown): string => String(value).replace(/[^a-zA-Z0-9_-]/g, '-')\n\nexport const restoreAttribute = (\n element: HTMLElement,\n name: string,\n value: string | null\n): void => {\n if (value === null) element.removeAttribute(name)\n else element.setAttribute(name, value)\n}\n\n/**\n * Appends highlight parts to `element` as text nodes and\n * `<mark data-autocomplete-highlight>` elements. Text is never parsed as HTML.\n */\nexport const renderHighlight = (\n element: HTMLElement,\n parts: readonly AutocompleteHighlightPart[]\n): void => {\n for (const part of parts) {\n if (!part.match) {\n element.append(document.createTextNode(part.text))\n continue\n }\n\n const mark = document.createElement('mark')\n mark.setAttribute(HIGHLIGHT_ATTRIBUTE, '')\n mark.textContent = part.text\n element.append(mark)\n }\n}\n\n// Keeps the live region readable by screen readers but out of the layout,\n// so it works without the optional stylesheet.\nexport const VISUALLY_HIDDEN =\n 'position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0'\n","import {\n DEFAULT_DEBOUNCE,\n DEFAULT_MAX_RESULTS,\n DEFAULT_MIN_LENGTH,\n LIST_ATTRIBUTE,\n LIVE_ATTRIBUTE,\n OPTION_ATTRIBUTE,\n POPUP_ATTRIBUTE,\n STATUS_ATTRIBUTE,\n} from '../constants'\nimport { createCache } from '../core/cache'\nimport { fromEndpoint } from '../core/source'\nimport {\n DEFAULT_MESSAGES,\n getDefaultId,\n getDefaultLabel,\n getDefaultValue,\n resolveMessage,\n validateOptions,\n} from '../core/state'\nimport { filterSuggestions, highlight, normalizeText } from '../core/text'\nimport type {\n AutocompleteController,\n AutocompleteEndpointOptions,\n AutocompleteHighlightPart,\n AutocompleteMessages,\n AutocompleteOptions,\n AutocompleteSourceFunction,\n AutocompleteTarget,\n} from '../core/types'\nimport { renderHighlight, resolveInput, restoreAttribute, sanitizeId, VISUALLY_HIDDEN } from './dom'\n\ninterface NormalizedSuggestion<T> {\n id: string\n label: string\n value: string\n data: T\n}\n\ntype StatusState = 'loading' | 'empty' | 'error'\n\nconst RESTORED_ATTRIBUTES = [\n 'role',\n 'autocomplete',\n 'aria-autocomplete',\n 'aria-controls',\n 'aria-expanded',\n 'aria-activedescendant',\n] as const\n\nlet instanceCount = 0\n\nconst isAbortError = (error: unknown): boolean =>\n error instanceof DOMException && error.name === 'AbortError'\n\n/** Turns an existing text input into an accessible combobox. */\nexport const autocomplete = <T = unknown>(\n target: AutocompleteTarget,\n options: AutocompleteOptions<T>\n): AutocompleteController<T> => {\n validateOptions(options)\n\n const input = resolveInput(target)\n const instanceId = `autocomplete-${++instanceCount}`\n const messages: AutocompleteMessages = { ...DEFAULT_MESSAGES, ...options.messages }\n const localSuggestions = Array.isArray(options.source) ? (options.source as readonly T[]) : null\n const getLabel = options.getLabel ?? (getDefaultLabel as (suggestion: T) => string)\n const getValue = options.getValue ?? (getDefaultValue as (suggestion: T) => string)\n const getId = options.getId ?? (getDefaultId as (suggestion: T, index: number) => string | number)\n const normalize = options.normalize ?? normalizeText\n const minLength = options.minLength ?? (localSuggestions ? 1 : DEFAULT_MIN_LENGTH)\n const debounce = options.debounce ?? (localSuggestions ? 0 : DEFAULT_DEBOUNCE)\n const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS\n const searchOnFocus = options.searchOnFocus ?? true\n const highlightEnabled = options.highlight ?? true\n\n const source: AutocompleteSourceFunction<T> | null = localSuggestions\n ? null\n : typeof options.source === 'function'\n ? options.source\n : fromEndpoint(options as AutocompleteEndpointOptions<T>)\n const cache =\n source && options.cache !== false\n ? createCache<T>(typeof options.cache === 'object' ? options.cache : {})\n : null\n\n const popup = document.createElement('div')\n const list = document.createElement('div')\n const status = document.createElement('div')\n const live = document.createElement('div')\n const originalAttributes = new Map<string, string | null>()\n const label = input.labels?.[0] ?? null\n const originalLabelId = label?.getAttribute('id') ?? null\n\n let suggestions: NormalizedSuggestion<T>[] = []\n let activeIndex = -1\n let selected: T | null = null\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n let abortController: AbortController | null = null\n let requestSequence = 0\n let destroyed = false\n let suppressFocusSearch = false\n\n popup.hidden = true\n popup.setAttribute(POPUP_ATTRIBUTE, '')\n\n list.id = `${instanceId}-listbox`\n list.hidden = true\n list.role = 'listbox'\n list.setAttribute(LIST_ATTRIBUTE, '')\n\n // The live region announces the status; the visible copy is not read twice.\n status.hidden = true\n status.setAttribute(STATUS_ATTRIBUTE, '')\n status.setAttribute('aria-hidden', 'true')\n\n live.role = 'status'\n live.setAttribute('aria-live', 'polite')\n live.setAttribute('aria-atomic', 'true')\n live.setAttribute(LIVE_ATTRIBUTE, '')\n live.style.cssText = VISUALLY_HIDDEN\n\n if (label) {\n if (!label.id) label.id = `${instanceId}-label`\n list.setAttribute('aria-labelledby', label.id)\n }\n\n for (const name of RESTORED_ATTRIBUTES) originalAttributes.set(name, input.getAttribute(name))\n\n input.role = 'combobox'\n input.autocomplete = 'off'\n input.setAttribute('aria-autocomplete', 'list')\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', 'false')\n\n popup.append(list, status)\n input.insertAdjacentElement('afterend', popup)\n popup.insertAdjacentElement('afterend', live)\n\n const dispatch = (name: string, detail: object): void => {\n input.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }))\n }\n\n const announce = (message: string): void => {\n live.textContent = message\n }\n\n const cancel = (): void => {\n clearTimeout(debounceTimer)\n debounceTimer = undefined\n requestSequence += 1\n abortController?.abort()\n abortController = null\n }\n\n const updateActiveOption = (): void => {\n const elements = list.querySelectorAll<HTMLElement>(`[${OPTION_ATTRIBUTE}]`)\n\n elements.forEach((element, index) => {\n const active = index === activeIndex\n element.dataset.active = String(active)\n element.setAttribute('aria-selected', String(active))\n })\n\n const active = elements.item(activeIndex)\n if (!active) {\n input.removeAttribute('aria-activedescendant')\n return\n }\n\n input.setAttribute('aria-activedescendant', active.id)\n active.scrollIntoView({ block: 'nearest' })\n }\n\n const close = (): void => {\n activeIndex = -1\n popup.hidden = true\n list.hidden = true\n status.hidden = true\n input.setAttribute('aria-expanded', 'false')\n input.removeAttribute('aria-activedescendant')\n updateActiveOption()\n }\n\n // Closing is a user decision: drop the pending search and abort the request\n // in flight, otherwise its response would reopen the list.\n const dismiss = (): void => {\n cancel()\n close()\n }\n\n const renderStatus = (state: StatusState, query: string): void => {\n if (destroyed) return\n\n const message = resolveMessage(messages[state], { query, count: 0 })\n list.replaceChildren()\n list.hidden = true\n status.textContent = message\n status.setAttribute(STATUS_ATTRIBUTE, state)\n status.hidden = false\n popup.hidden = false\n input.setAttribute('aria-expanded', 'false')\n input.removeAttribute('aria-activedescendant')\n if (state !== 'loading') announce(message)\n }\n\n const select = (index: number): void => {\n const suggestion = suggestions[index]\n if (!suggestion) return\n\n selected = suggestion.data\n input.value = suggestion.value\n dismiss()\n dispatch('autocomplete:select', {\n suggestion: suggestion.data,\n label: suggestion.label,\n value: suggestion.value,\n })\n options.onSelect?.(suggestion.data, input)\n }\n\n const handleOptionPointerDown = (event: PointerEvent): void => {\n event.preventDefault()\n select(Number((event.currentTarget as HTMLElement).dataset.index))\n\n if (document.activeElement !== input) {\n suppressFocusSearch = true\n input.focus({ preventScroll: true })\n suppressFocusSearch = false\n }\n }\n\n const handleOptionPointerEnter = (event: PointerEvent): void => {\n activeIndex = Number((event.currentTarget as HTMLElement).dataset.index)\n updateActiveOption()\n }\n\n const renderSuggestions = (query: string): void => {\n const fragment = document.createDocumentFragment()\n\n suggestions.forEach((suggestion, index) => {\n const option = document.createElement('div')\n const parts: AutocompleteHighlightPart[] = highlightEnabled\n ? highlight(suggestion.label, query, normalize)\n : [{ text: suggestion.label, match: false }]\n\n option.id = suggestion.id\n option.role = 'option'\n option.setAttribute(OPTION_ATTRIBUTE, '')\n option.dataset.index = String(index)\n option.setAttribute('aria-selected', 'false')\n renderHighlight(option, parts)\n options.renderOption?.(option, suggestion.data, {\n query,\n index,\n label: suggestion.label,\n highlight: parts,\n })\n option.addEventListener('pointerdown', handleOptionPointerDown)\n option.addEventListener('pointerenter', handleOptionPointerEnter)\n fragment.append(option)\n })\n\n list.replaceChildren(fragment)\n list.hidden = false\n status.hidden = true\n popup.hidden = false\n input.setAttribute('aria-expanded', 'true')\n }\n\n const normalizeSuggestion = (suggestion: T, index: number): NormalizedSuggestion<T> => ({\n id: `${instanceId}-option-${sanitizeId(getId(suggestion, index))}`,\n label: String(getLabel(suggestion)),\n value: String(getValue(suggestion)),\n data: suggestion,\n })\n\n const showResults = (query: string, result: readonly T[], cached: boolean): void => {\n if (destroyed) return\n\n suggestions = result.slice(0, maxResults).map(normalizeSuggestion)\n activeIndex = -1\n\n if (suggestions.length === 0) renderStatus('empty', query)\n else {\n renderSuggestions(query)\n announce(resolveMessage(messages.results, { query, count: suggestions.length }))\n }\n\n dispatch('autocomplete:load', {\n query,\n suggestions: suggestions.map(suggestion => suggestion.data),\n cached,\n })\n }\n\n const filterLocal = (items: readonly T[], query: string): T[] => {\n const filter = options.filter\n return filter\n ? items.filter(item => filter(item, query))\n : filterSuggestions(items, query, { getText: getLabel, normalize })\n }\n\n const search = async (query: string = input.value.trim()): Promise<void> => {\n if (destroyed || query.length < minLength) {\n close()\n return\n }\n\n cancel()\n const sequence = ++requestSequence\n\n if (localSuggestions) {\n showResults(query, filterLocal(localSuggestions, query), false)\n return\n }\n\n const hit = cache?.get(query)\n if (hit) {\n showResults(query, hit, true)\n return\n }\n\n const controller = new AbortController()\n abortController = controller\n renderStatus('loading', query)\n dispatch('autocomplete:loadstart', { query })\n\n try {\n const result = await source!(query, { signal: controller.signal, input })\n if (!Array.isArray(result)) throw new TypeError('Autocomplete source must return an array.')\n if (sequence !== requestSequence) return\n\n cache?.set(query, result)\n showResults(query, result, false)\n } catch (error) {\n if (isAbortError(error) || sequence !== requestSequence || destroyed) return\n\n suggestions = []\n activeIndex = -1\n renderStatus('error', query)\n dispatch('autocomplete:error', { query, error })\n options.onError?.(error, input)\n } finally {\n if (sequence === requestSequence) abortController = null\n }\n }\n\n const scheduleSearch = (): void => {\n clearTimeout(debounceTimer)\n const query = input.value.trim()\n\n if (selected !== null) {\n selected = null\n dispatch('autocomplete:clear', { query })\n }\n\n if (query.length < minLength) {\n dismiss()\n return\n }\n\n if (debounce === 0) {\n void search(query)\n return\n }\n\n debounceTimer = setTimeout(() => void search(query), debounce)\n }\n\n const clear = (): void => {\n const hadSelection = selected !== null\n cancel()\n suggestions = []\n activeIndex = -1\n selected = null\n input.value = ''\n close()\n announce('')\n if (hadSelection) dispatch('autocomplete:clear', { query: '' })\n }\n\n const moveActiveOption = (direction: number): void => {\n if (list.hidden || suggestions.length === 0) return\n\n activeIndex =\n activeIndex === -1\n ? direction > 0\n ? 0\n : suggestions.length - 1\n : (activeIndex + direction + suggestions.length) % suggestions.length\n\n updateActiveOption()\n }\n\n const handleInput = (event: Event): void => {\n // Wait for IME composition to finish (compositionend schedules the search).\n if ((event as InputEvent).isComposing) return\n scheduleSearch()\n }\n\n const handleFocus = (): void => {\n if (suppressFocusSearch) return\n if (searchOnFocus && input.value.trim().length >= minLength) scheduleSearch()\n }\n\n const handleKeydown = (event: KeyboardEvent): void => {\n const listOpen = !list.hidden && suggestions.length > 0\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n if (!listOpen) return\n event.preventDefault()\n moveActiveOption(event.key === 'ArrowDown' ? 1 : -1)\n return\n }\n\n if (event.key === 'Enter' && listOpen && activeIndex >= 0) {\n event.preventDefault()\n select(activeIndex)\n return\n }\n\n if ((event.key === 'Home' || event.key === 'End') && listOpen) {\n event.preventDefault()\n activeIndex = event.key === 'Home' ? 0 : suggestions.length - 1\n updateActiveOption()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n dismiss()\n return\n }\n\n if (event.key === 'Tab') dismiss()\n }\n\n const handleDocumentPointerDown = (event: PointerEvent): void => {\n if (event.target !== input && !popup.contains(event.target as Node)) dismiss()\n }\n\n const destroy = (): void => {\n if (destroyed) return\n destroyed = true\n cancel()\n input.removeEventListener('input', handleInput)\n input.removeEventListener('compositionend', scheduleSearch)\n input.removeEventListener('focus', handleFocus)\n input.removeEventListener('keydown', handleKeydown)\n document.removeEventListener('pointerdown', handleDocumentPointerDown)\n popup.remove()\n live.remove()\n cache?.clear()\n\n originalAttributes.forEach((value, name) => restoreAttribute(input, name, value))\n if (label) restoreAttribute(label, 'id', originalLabelId)\n }\n\n input.addEventListener('input', handleInput)\n input.addEventListener('compositionend', scheduleSearch)\n input.addEventListener('focus', handleFocus)\n input.addEventListener('keydown', handleKeydown)\n document.addEventListener('pointerdown', handleDocumentPointerDown)\n\n return {\n element: input,\n popupElement: popup,\n listElement: list,\n statusElement: status,\n liveElement: live,\n options,\n search,\n close: dismiss,\n clear,\n cancel,\n clearCache: () => cache?.clear(),\n getSelected: () => selected,\n destroy,\n }\n}\n\nexport const createAutocomplete = autocomplete\n","import { autocomplete } from '../vanilla/render'\nimport type { AutocompleteController, AutocompleteOptions } from '../core/types'\n\nexport interface NewAutocompleteInput<T = unknown> {\n /** The id of the input to enhance. */\n id: string\n options: AutocompleteOptions<T>\n}\nexport interface AutocompleteAvailable {\n [id: string]: AutocompleteController<unknown>\n}\nexport interface AutocompleteApi {\n autocomplete: typeof autocomplete\n newAutocomplete: <T = unknown>(input: NewAutocompleteInput<T>) => AutocompleteController<T> | null\n getAutocomplete: <T = unknown>(id: string) => AutocompleteController<T> | null\n getAutocompletes: () => Readonly<Record<string, AutocompleteController<unknown>>>\n destroyAutocomplete: (target: string | HTMLInputElement) => void\n destroyAutocompletes: () => void\n available: AutocompleteAvailable\n}\n\nexport const available: AutocompleteAvailable = Object.create(null) as AutocompleteAvailable\n\nconst removeRegistration = (id: string): void => {\n const entry = available[id]\n if (!entry) return\n delete available[id]\n entry.destroy()\n}\n\nexport const newAutocomplete = <T = unknown>({\n id,\n options,\n}: NewAutocompleteInput<T>): AutocompleteController<T> | null => {\n if (!id) {\n console.error('Autocomplete ID is required')\n return null\n }\n const input = document.getElementById(id)\n if (!(input instanceof HTMLInputElement)) {\n console.warn(`Autocomplete input with ID ${id} not found`)\n return null\n }\n removeRegistration(id)\n const controller = autocomplete(input, options)\n // A direct controller.destroy() must not leave a stale registry entry.\n const destroy = controller.destroy\n const registered: AutocompleteController<T> = {\n ...controller,\n destroy: () => {\n if (available[id] === (registered as AutocompleteController<unknown>)) delete available[id]\n destroy()\n },\n }\n available[id] = registered as AutocompleteController<unknown>\n return registered\n}\n\nexport const getAutocomplete = <T = unknown>(id: string): AutocompleteController<T> | null =>\n (available[id] as AutocompleteController<T> | undefined) ?? null\n\nexport const getAutocompletes = (): Readonly<Record<string, AutocompleteController<unknown>>> => ({\n ...available,\n})\n\nexport const destroyAutocomplete = (target: string | HTMLInputElement): void => {\n if (typeof target === 'string') return removeRegistration(target)\n const entry = Object.entries(available).find(([, controller]) => controller.element === target)\n if (entry) removeRegistration(entry[0])\n}\n\nexport const destroyAutocompletes = (): void => Object.keys(available).forEach(removeRegistration)\n\nexport const Autocomplete: AutocompleteApi = {\n autocomplete,\n newAutocomplete,\n getAutocomplete,\n getAutocompletes,\n destroyAutocomplete,\n destroyAutocompletes,\n available,\n}\nexport const browser = Autocomplete\n"],"mappings":"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","names":["active"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { a as autocomplete } from '../render-WD69VMUs.cjs';
|
|
2
|
+
import { k as AutocompleteOptions, b as AutocompleteController } from '../types-CPvjhqjb.cjs';
|
|
3
|
+
export { t as AutocompleteTarget } from '../types-CPvjhqjb.cjs';
|
|
4
|
+
|
|
5
|
+
interface NewAutocompleteInput<T = unknown> {
|
|
6
|
+
/** The id of the input to enhance. */
|
|
7
|
+
id: string;
|
|
8
|
+
options: AutocompleteOptions<T>;
|
|
9
|
+
}
|
|
10
|
+
interface AutocompleteAvailable {
|
|
11
|
+
[id: string]: AutocompleteController<unknown>;
|
|
12
|
+
}
|
|
13
|
+
interface AutocompleteApi {
|
|
14
|
+
autocomplete: typeof autocomplete;
|
|
15
|
+
newAutocomplete: <T = unknown>(input: NewAutocompleteInput<T>) => AutocompleteController<T> | null;
|
|
16
|
+
getAutocomplete: <T = unknown>(id: string) => AutocompleteController<T> | null;
|
|
17
|
+
getAutocompletes: () => Readonly<Record<string, AutocompleteController<unknown>>>;
|
|
18
|
+
destroyAutocomplete: (target: string | HTMLInputElement) => void;
|
|
19
|
+
destroyAutocompletes: () => void;
|
|
20
|
+
available: AutocompleteAvailable;
|
|
21
|
+
}
|
|
22
|
+
declare const available: AutocompleteAvailable;
|
|
23
|
+
declare const newAutocomplete: <T = unknown>({ id, options, }: NewAutocompleteInput<T>) => AutocompleteController<T> | null;
|
|
24
|
+
declare const getAutocomplete: <T = unknown>(id: string) => AutocompleteController<T> | null;
|
|
25
|
+
declare const getAutocompletes: () => Readonly<Record<string, AutocompleteController<unknown>>>;
|
|
26
|
+
declare const destroyAutocomplete: (target: string | HTMLInputElement) => void;
|
|
27
|
+
declare const destroyAutocompletes: () => void;
|
|
28
|
+
declare const Autocomplete: AutocompleteApi;
|
|
29
|
+
declare const browser: AutocompleteApi;
|
|
30
|
+
|
|
31
|
+
export { Autocomplete, type AutocompleteApi, type AutocompleteAvailable, AutocompleteController, AutocompleteOptions, type NewAutocompleteInput, available, browser, Autocomplete as default, destroyAutocomplete, destroyAutocompletes, getAutocomplete, getAutocompletes, newAutocomplete };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { a as autocomplete } from '../render-B38UXYCT.js';
|
|
2
|
+
import { k as AutocompleteOptions, b as AutocompleteController } from '../types-CPvjhqjb.js';
|
|
3
|
+
export { t as AutocompleteTarget } from '../types-CPvjhqjb.js';
|
|
4
|
+
|
|
5
|
+
interface NewAutocompleteInput<T = unknown> {
|
|
6
|
+
/** The id of the input to enhance. */
|
|
7
|
+
id: string;
|
|
8
|
+
options: AutocompleteOptions<T>;
|
|
9
|
+
}
|
|
10
|
+
interface AutocompleteAvailable {
|
|
11
|
+
[id: string]: AutocompleteController<unknown>;
|
|
12
|
+
}
|
|
13
|
+
interface AutocompleteApi {
|
|
14
|
+
autocomplete: typeof autocomplete;
|
|
15
|
+
newAutocomplete: <T = unknown>(input: NewAutocompleteInput<T>) => AutocompleteController<T> | null;
|
|
16
|
+
getAutocomplete: <T = unknown>(id: string) => AutocompleteController<T> | null;
|
|
17
|
+
getAutocompletes: () => Readonly<Record<string, AutocompleteController<unknown>>>;
|
|
18
|
+
destroyAutocomplete: (target: string | HTMLInputElement) => void;
|
|
19
|
+
destroyAutocompletes: () => void;
|
|
20
|
+
available: AutocompleteAvailable;
|
|
21
|
+
}
|
|
22
|
+
declare const available: AutocompleteAvailable;
|
|
23
|
+
declare const newAutocomplete: <T = unknown>({ id, options, }: NewAutocompleteInput<T>) => AutocompleteController<T> | null;
|
|
24
|
+
declare const getAutocomplete: <T = unknown>(id: string) => AutocompleteController<T> | null;
|
|
25
|
+
declare const getAutocompletes: () => Readonly<Record<string, AutocompleteController<unknown>>>;
|
|
26
|
+
declare const destroyAutocomplete: (target: string | HTMLInputElement) => void;
|
|
27
|
+
declare const destroyAutocompletes: () => void;
|
|
28
|
+
declare const Autocomplete: AutocompleteApi;
|
|
29
|
+
declare const browser: AutocompleteApi;
|
|
30
|
+
|
|
31
|
+
export { Autocomplete, type AutocompleteApi, type AutocompleteAvailable, AutocompleteController, AutocompleteOptions, type NewAutocompleteInput, available, browser, Autocomplete as default, destroyAutocomplete, destroyAutocompletes, getAutocomplete, getAutocompletes, newAutocomplete };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Autocomplete,
|
|
3
|
+
available,
|
|
4
|
+
browser,
|
|
5
|
+
destroyAutocomplete,
|
|
6
|
+
destroyAutocompletes,
|
|
7
|
+
getAutocomplete,
|
|
8
|
+
getAutocompletes,
|
|
9
|
+
newAutocomplete
|
|
10
|
+
} from "../chunk-5UZVFHB4.js";
|
|
11
|
+
import "../chunk-U5JYDGKW.js";
|
|
12
|
+
import "../chunk-4IPBCQDZ.js";
|
|
13
|
+
export {
|
|
14
|
+
Autocomplete,
|
|
15
|
+
available,
|
|
16
|
+
browser,
|
|
17
|
+
Autocomplete as default,
|
|
18
|
+
destroyAutocomplete,
|
|
19
|
+
destroyAutocompletes,
|
|
20
|
+
getAutocomplete,
|
|
21
|
+
getAutocompletes,
|
|
22
|
+
newAutocomplete
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var DEFAULT_QUERY_PARAM = "q";
|
|
3
|
+
var DEFAULT_MIN_LENGTH = 2;
|
|
4
|
+
var DEFAULT_DEBOUNCE = 250;
|
|
5
|
+
var DEFAULT_MAX_RESULTS = 8;
|
|
6
|
+
var DEFAULT_CACHE_SIZE = 50;
|
|
7
|
+
var DEFAULT_CACHE_TTL = 3e5;
|
|
8
|
+
var POPUP_ATTRIBUTE = "data-autocomplete-popup";
|
|
9
|
+
var LIST_ATTRIBUTE = "data-autocomplete-list";
|
|
10
|
+
var OPTION_ATTRIBUTE = "data-autocomplete-option";
|
|
11
|
+
var STATUS_ATTRIBUTE = "data-autocomplete-status";
|
|
12
|
+
var LIVE_ATTRIBUTE = "data-autocomplete-live";
|
|
13
|
+
var HIGHLIGHT_ATTRIBUTE = "data-autocomplete-highlight";
|
|
14
|
+
|
|
15
|
+
// src/core/cache.ts
|
|
16
|
+
var createCache = ({ max = DEFAULT_CACHE_SIZE, ttl = DEFAULT_CACHE_TTL } = {}, now = Date.now) => {
|
|
17
|
+
const entries = /* @__PURE__ */ new Map();
|
|
18
|
+
const get = (key) => {
|
|
19
|
+
const entry = entries.get(key);
|
|
20
|
+
if (!entry) return void 0;
|
|
21
|
+
entries.delete(key);
|
|
22
|
+
if (entry.expires <= now()) return void 0;
|
|
23
|
+
entries.set(key, entry);
|
|
24
|
+
return entry.value;
|
|
25
|
+
};
|
|
26
|
+
const set = (key, value) => {
|
|
27
|
+
entries.delete(key);
|
|
28
|
+
entries.set(key, { value, expires: now() + ttl });
|
|
29
|
+
while (entries.size > max) {
|
|
30
|
+
const oldest = entries.keys().next();
|
|
31
|
+
if (oldest.done) break;
|
|
32
|
+
entries.delete(oldest.value);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
get,
|
|
37
|
+
set,
|
|
38
|
+
clear: () => entries.clear(),
|
|
39
|
+
get size() {
|
|
40
|
+
return entries.size;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/core/source.ts
|
|
46
|
+
var parseJsonResponse = async (response) => response.json();
|
|
47
|
+
var invalidPayloadError = () => new TypeError(
|
|
48
|
+
"Autocomplete response does not contain an array. Configure mapResponse(payload, response, query)."
|
|
49
|
+
);
|
|
50
|
+
var mapCommonResponse = (payload) => {
|
|
51
|
+
if (Array.isArray(payload)) return payload;
|
|
52
|
+
if (!payload || typeof payload !== "object") throw invalidPayloadError();
|
|
53
|
+
const record = payload;
|
|
54
|
+
if (Array.isArray(record.suggestions)) return record.suggestions;
|
|
55
|
+
if (Array.isArray(record.items)) return record.items;
|
|
56
|
+
if (Array.isArray(record.data)) return record.data;
|
|
57
|
+
throw invalidPayloadError();
|
|
58
|
+
};
|
|
59
|
+
var fromEndpoint = ({
|
|
60
|
+
endpoint,
|
|
61
|
+
queryParam = DEFAULT_QUERY_PARAM,
|
|
62
|
+
requestInit = {},
|
|
63
|
+
parseResponse = parseJsonResponse,
|
|
64
|
+
mapResponse = mapCommonResponse
|
|
65
|
+
}) => {
|
|
66
|
+
if (!endpoint) throw new TypeError("Autocomplete requires an endpoint.");
|
|
67
|
+
return async (query, { signal, input }) => {
|
|
68
|
+
const resolved = typeof endpoint === "function" ? endpoint(query, input) : endpoint;
|
|
69
|
+
const url = new URL(resolved, document.baseURI);
|
|
70
|
+
if (queryParam !== null) url.searchParams.set(queryParam, query);
|
|
71
|
+
const init = typeof requestInit === "function" ? requestInit(query, input) : requestInit;
|
|
72
|
+
const response = await fetch(url, { ...init, signal });
|
|
73
|
+
if (!response.ok) throw new Error(`Autocomplete request failed with HTTP ${response.status}.`);
|
|
74
|
+
const payload = await parseResponse(response, query);
|
|
75
|
+
const result = await mapResponse(payload, response, query);
|
|
76
|
+
if (!Array.isArray(result))
|
|
77
|
+
throw new TypeError("Autocomplete mapResponse must return an array.");
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/core/state.ts
|
|
83
|
+
var DEFAULT_MESSAGES = {
|
|
84
|
+
loading: "Searching...",
|
|
85
|
+
empty: "No results",
|
|
86
|
+
error: "Search failed",
|
|
87
|
+
results: ({ count }) => count === 1 ? "1 result available" : `${count} results available`
|
|
88
|
+
};
|
|
89
|
+
var resolveMessage = (message, context) => typeof message === "function" ? message(context) : message;
|
|
90
|
+
var readField = (suggestion, keys) => {
|
|
91
|
+
if (typeof suggestion === "string") return suggestion;
|
|
92
|
+
if (!suggestion || typeof suggestion !== "object") return String(suggestion ?? "");
|
|
93
|
+
const record = suggestion;
|
|
94
|
+
const key = keys.find((name) => record[name] !== void 0 && record[name] !== null);
|
|
95
|
+
return key === void 0 ? "" : String(record[key]);
|
|
96
|
+
};
|
|
97
|
+
var getDefaultLabel = (suggestion) => readField(suggestion, ["label", "value", "title", "name"]);
|
|
98
|
+
var getDefaultValue = (suggestion) => readField(suggestion, ["value", "label", "title", "name"]);
|
|
99
|
+
var getDefaultId = (suggestion, index) => {
|
|
100
|
+
if (!suggestion || typeof suggestion !== "object") return index;
|
|
101
|
+
return suggestion.id ?? index;
|
|
102
|
+
};
|
|
103
|
+
var assertNonNegativeInteger = (value, name) => {
|
|
104
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 0)) {
|
|
105
|
+
throw new TypeError(`Autocomplete ${name} must be a non-negative integer.`);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var assertPositiveInteger = (value, name) => {
|
|
109
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|
|
110
|
+
throw new TypeError(`Autocomplete ${name} must be an integer greater than zero.`);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var validateOptions = (options) => {
|
|
114
|
+
const hasSource = options.source !== void 0;
|
|
115
|
+
const hasEndpoint = options.endpoint !== void 0;
|
|
116
|
+
if (hasSource && hasEndpoint) {
|
|
117
|
+
throw new TypeError("Autocomplete accepts either a source or an endpoint, not both.");
|
|
118
|
+
}
|
|
119
|
+
if (!hasSource && !options.endpoint) {
|
|
120
|
+
throw new TypeError("Autocomplete requires a source or an endpoint.");
|
|
121
|
+
}
|
|
122
|
+
if (hasSource && !Array.isArray(options.source) && typeof options.source !== "function") {
|
|
123
|
+
throw new TypeError("Autocomplete source must be an array or a function.");
|
|
124
|
+
}
|
|
125
|
+
assertNonNegativeInteger(options.minLength, "minLength");
|
|
126
|
+
assertNonNegativeInteger(options.debounce, "debounce");
|
|
127
|
+
assertPositiveInteger(options.maxResults, "maxResults");
|
|
128
|
+
if (typeof options.cache === "object") {
|
|
129
|
+
assertPositiveInteger(options.cache.max, "cache.max");
|
|
130
|
+
assertNonNegativeInteger(options.cache.ttl, "cache.ttl");
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/core/text.ts
|
|
135
|
+
var COMBINING_MARKS = /\p{M}/gu;
|
|
136
|
+
var WHITESPACE = /\s+/;
|
|
137
|
+
var normalizeText = (value) => value.normalize("NFD").replace(COMBINING_MARKS, "").toLowerCase();
|
|
138
|
+
var getTerms = (query, normalize) => Array.from(new Set(normalize(query).split(WHITESPACE).filter(Boolean)));
|
|
139
|
+
var filterSuggestions = (suggestions, query, { getText, normalize = normalizeText }) => {
|
|
140
|
+
const terms = getTerms(query, normalize);
|
|
141
|
+
if (terms.length === 0) return [...suggestions];
|
|
142
|
+
return suggestions.filter((suggestion) => {
|
|
143
|
+
const text = normalize(getText(suggestion));
|
|
144
|
+
return terms.every((term) => text.includes(term));
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
var highlight = (text, query, normalize = normalizeText) => {
|
|
148
|
+
const terms = getTerms(query, normalize);
|
|
149
|
+
if (terms.length === 0 || text === "") return text === "" ? [] : [{ text, match: false }];
|
|
150
|
+
let normalized = "";
|
|
151
|
+
const starts = [];
|
|
152
|
+
const ends = [];
|
|
153
|
+
let offset = 0;
|
|
154
|
+
for (const char of text) {
|
|
155
|
+
const piece = normalize(char);
|
|
156
|
+
if (piece === "" && ends.length > 0) ends[ends.length - 1] = offset + char.length;
|
|
157
|
+
normalized += piece;
|
|
158
|
+
for (let index = 0; index < piece.length; index += 1) {
|
|
159
|
+
starts.push(offset);
|
|
160
|
+
ends.push(offset + char.length);
|
|
161
|
+
}
|
|
162
|
+
offset += char.length;
|
|
163
|
+
}
|
|
164
|
+
const ranges = [];
|
|
165
|
+
for (const term of terms) {
|
|
166
|
+
let found = normalized.indexOf(term);
|
|
167
|
+
while (found !== -1) {
|
|
168
|
+
ranges.push([starts[found], ends[found + term.length - 1]]);
|
|
169
|
+
found = normalized.indexOf(term, found + 1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (ranges.length === 0) return [{ text, match: false }];
|
|
173
|
+
ranges.sort((a, b) => a[0] - b[0]);
|
|
174
|
+
const merged = [];
|
|
175
|
+
for (const range of ranges) {
|
|
176
|
+
const last = merged[merged.length - 1];
|
|
177
|
+
if (last && range[0] <= last[1]) last[1] = Math.max(last[1], range[1]);
|
|
178
|
+
else merged.push([range[0], range[1]]);
|
|
179
|
+
}
|
|
180
|
+
const parts = [];
|
|
181
|
+
let cursor = 0;
|
|
182
|
+
for (const [start, end] of merged) {
|
|
183
|
+
if (start > cursor) parts.push({ text: text.slice(cursor, start), match: false });
|
|
184
|
+
parts.push({ text: text.slice(start, end), match: true });
|
|
185
|
+
cursor = end;
|
|
186
|
+
}
|
|
187
|
+
if (cursor < text.length) parts.push({ text: text.slice(cursor), match: false });
|
|
188
|
+
return parts;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
DEFAULT_QUERY_PARAM,
|
|
193
|
+
DEFAULT_MIN_LENGTH,
|
|
194
|
+
DEFAULT_DEBOUNCE,
|
|
195
|
+
DEFAULT_MAX_RESULTS,
|
|
196
|
+
DEFAULT_CACHE_SIZE,
|
|
197
|
+
DEFAULT_CACHE_TTL,
|
|
198
|
+
POPUP_ATTRIBUTE,
|
|
199
|
+
LIST_ATTRIBUTE,
|
|
200
|
+
OPTION_ATTRIBUTE,
|
|
201
|
+
STATUS_ATTRIBUTE,
|
|
202
|
+
LIVE_ATTRIBUTE,
|
|
203
|
+
HIGHLIGHT_ATTRIBUTE,
|
|
204
|
+
createCache,
|
|
205
|
+
parseJsonResponse,
|
|
206
|
+
mapCommonResponse,
|
|
207
|
+
fromEndpoint,
|
|
208
|
+
DEFAULT_MESSAGES,
|
|
209
|
+
resolveMessage,
|
|
210
|
+
getDefaultLabel,
|
|
211
|
+
getDefaultValue,
|
|
212
|
+
getDefaultId,
|
|
213
|
+
validateOptions,
|
|
214
|
+
normalizeText,
|
|
215
|
+
filterSuggestions,
|
|
216
|
+
highlight
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=chunk-4IPBCQDZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/core/cache.ts","../src/core/source.ts","../src/core/state.ts","../src/core/text.ts"],"sourcesContent":["export const DEFAULT_QUERY_PARAM = 'q'\nexport const DEFAULT_MIN_LENGTH = 2\nexport const DEFAULT_DEBOUNCE = 250\nexport const DEFAULT_MAX_RESULTS = 8\n\n/** Cached queries kept per instance when `cache` is enabled. */\nexport const DEFAULT_CACHE_SIZE = 50\n/** Milliseconds a cached query stays fresh; five minutes. */\nexport const DEFAULT_CACHE_TTL = 300_000\n\nexport const POPUP_ATTRIBUTE = 'data-autocomplete-popup'\nexport const LIST_ATTRIBUTE = 'data-autocomplete-list'\nexport const OPTION_ATTRIBUTE = 'data-autocomplete-option'\nexport const STATUS_ATTRIBUTE = 'data-autocomplete-status'\nexport const LIVE_ATTRIBUTE = 'data-autocomplete-live'\nexport const HIGHLIGHT_ATTRIBUTE = 'data-autocomplete-highlight'\n","import { DEFAULT_CACHE_SIZE, DEFAULT_CACHE_TTL } from '../constants'\nimport type { AutocompleteCache, AutocompleteCacheOptions } from './types'\n\ninterface CacheEntry<T> {\n value: readonly T[]\n expires: number\n}\n\n/** Least-recently-used cache of suggestions keyed by query. */\nexport const createCache = <T>(\n { max = DEFAULT_CACHE_SIZE, ttl = DEFAULT_CACHE_TTL }: AutocompleteCacheOptions = {},\n now: () => number = Date.now\n): AutocompleteCache<T> => {\n const entries = new Map<string, CacheEntry<T>>()\n\n const get = (key: string): readonly T[] | undefined => {\n const entry = entries.get(key)\n if (!entry) return undefined\n\n entries.delete(key)\n if (entry.expires <= now()) return undefined\n\n // Re-insert so the entry becomes the most recently used.\n entries.set(key, entry)\n return entry.value\n }\n\n const set = (key: string, value: readonly T[]): void => {\n entries.delete(key)\n entries.set(key, { value, expires: now() + ttl })\n\n while (entries.size > max) {\n const oldest = entries.keys().next()\n if (oldest.done) break\n entries.delete(oldest.value)\n }\n }\n\n return {\n get,\n set,\n clear: () => entries.clear(),\n get size() {\n return entries.size\n },\n }\n}\n","import { DEFAULT_QUERY_PARAM } from '../constants'\nimport type {\n AutocompleteEndpointOptions,\n AutocompleteParser,\n AutocompleteResponseMapper,\n AutocompleteSourceFunction,\n} from './types'\n\nexport const parseJsonResponse: AutocompleteParser = async response => response.json()\n\nconst invalidPayloadError = (): TypeError =>\n new TypeError(\n 'Autocomplete response does not contain an array. Configure mapResponse(payload, response, query).'\n )\n\n/** Accepts an array, or an object with a `suggestions`, `items` or `data` array. */\nexport const mapCommonResponse: AutocompleteResponseMapper<unknown> = payload => {\n if (Array.isArray(payload)) return payload\n if (!payload || typeof payload !== 'object') throw invalidPayloadError()\n\n const record = payload as Record<string, unknown>\n if (Array.isArray(record.suggestions)) return record.suggestions\n if (Array.isArray(record.items)) return record.items\n if (Array.isArray(record.data)) return record.data\n\n throw invalidPayloadError()\n}\n\n/** Builds a source that fetches suggestions from an HTTP endpoint. */\nexport const fromEndpoint = <T>({\n endpoint,\n queryParam = DEFAULT_QUERY_PARAM,\n requestInit = {},\n parseResponse = parseJsonResponse,\n mapResponse = mapCommonResponse as AutocompleteResponseMapper<T>,\n}: AutocompleteEndpointOptions<T>): AutocompleteSourceFunction<T> => {\n if (!endpoint) throw new TypeError('Autocomplete requires an endpoint.')\n\n return async (query, { signal, input }) => {\n const resolved = typeof endpoint === 'function' ? endpoint(query, input) : endpoint\n const url = new URL(resolved, document.baseURI)\n if (queryParam !== null) url.searchParams.set(queryParam, query)\n\n const init = typeof requestInit === 'function' ? requestInit(query, input) : requestInit\n const response = await fetch(url, { ...init, signal })\n if (!response.ok) throw new Error(`Autocomplete request failed with HTTP ${response.status}.`)\n\n const payload = await parseResponse(response, query)\n const result = await mapResponse(payload, response, query)\n if (!Array.isArray(result))\n throw new TypeError('Autocomplete mapResponse must return an array.')\n\n return result\n }\n}\n","import type {\n AutocompleteMessage,\n AutocompleteMessageContext,\n AutocompleteMessages,\n AutocompleteOptions,\n} from './types'\n\nexport const DEFAULT_MESSAGES: AutocompleteMessages = {\n loading: 'Searching...',\n empty: 'No results',\n error: 'Search failed',\n results: ({ count }) => (count === 1 ? '1 result available' : `${count} results available`),\n}\n\nexport const resolveMessage = (\n message: AutocompleteMessage,\n context: AutocompleteMessageContext\n): string => (typeof message === 'function' ? message(context) : message)\n\nconst readField = (suggestion: unknown, keys: readonly string[]): string => {\n if (typeof suggestion === 'string') return suggestion\n if (!suggestion || typeof suggestion !== 'object') return String(suggestion ?? '')\n\n const record = suggestion as Record<string, unknown>\n const key = keys.find(name => record[name] !== undefined && record[name] !== null)\n return key === undefined ? '' : String(record[key])\n}\n\nexport const getDefaultLabel = (suggestion: unknown): string =>\n readField(suggestion, ['label', 'value', 'title', 'name'])\n\nexport const getDefaultValue = (suggestion: unknown): string =>\n readField(suggestion, ['value', 'label', 'title', 'name'])\n\nexport const getDefaultId = (suggestion: unknown, index: number): string | number => {\n if (!suggestion || typeof suggestion !== 'object') return index\n return ((suggestion as Record<string, unknown>).id as string | number | undefined) ?? index\n}\n\nconst assertNonNegativeInteger = (value: number | undefined, name: string): void => {\n if (value !== undefined && (!Number.isInteger(value) || value < 0)) {\n throw new TypeError(`Autocomplete ${name} must be a non-negative integer.`)\n }\n}\n\nconst assertPositiveInteger = (value: number | undefined, name: string): void => {\n if (value !== undefined && (!Number.isInteger(value) || value < 1)) {\n throw new TypeError(`Autocomplete ${name} must be an integer greater than zero.`)\n }\n}\n\nexport const validateOptions = <T>(options: AutocompleteOptions<T>): void => {\n const hasSource = options.source !== undefined\n const hasEndpoint = options.endpoint !== undefined\n\n if (hasSource && hasEndpoint) {\n throw new TypeError('Autocomplete accepts either a source or an endpoint, not both.')\n }\n if (!hasSource && !options.endpoint) {\n throw new TypeError('Autocomplete requires a source or an endpoint.')\n }\n if (hasSource && !Array.isArray(options.source) && typeof options.source !== 'function') {\n throw new TypeError('Autocomplete source must be an array or a function.')\n }\n\n assertNonNegativeInteger(options.minLength, 'minLength')\n assertNonNegativeInteger(options.debounce, 'debounce')\n assertPositiveInteger(options.maxResults, 'maxResults')\n\n if (typeof options.cache === 'object') {\n assertPositiveInteger(options.cache.max, 'cache.max')\n assertNonNegativeInteger(options.cache.ttl, 'cache.ttl')\n }\n}\n","import type { AutocompleteHighlightPart } from './types'\n\nexport type AutocompleteNormalizer = (value: string) => string\n\nexport interface FilterSuggestionsOptions<T> {\n getText: (suggestion: T) => string\n normalize?: AutocompleteNormalizer\n}\n\nconst COMBINING_MARKS = /\\p{M}/gu\nconst WHITESPACE = /\\s+/\n\n/** Lowercases and removes diacritics, so `Cañón` and `canon` compare equal. */\nexport const normalizeText: AutocompleteNormalizer = value =>\n value.normalize('NFD').replace(COMBINING_MARKS, '').toLowerCase()\n\nconst getTerms = (query: string, normalize: AutocompleteNormalizer): string[] =>\n Array.from(new Set(normalize(query).split(WHITESPACE).filter(Boolean)))\n\n/** Keeps the suggestions whose text contains every word of the query. */\nexport const filterSuggestions = <T>(\n suggestions: readonly T[],\n query: string,\n { getText, normalize = normalizeText }: FilterSuggestionsOptions<T>\n): T[] => {\n const terms = getTerms(query, normalize)\n if (terms.length === 0) return [...suggestions]\n\n return suggestions.filter(suggestion => {\n const text = normalize(getText(suggestion))\n return terms.every(term => text.includes(term))\n })\n}\n\n/**\n * Splits `text` into parts that match a word of `query` and parts that do\n * not. Matching runs on normalized text but the parts keep the original\n * characters, accents included.\n */\nexport const highlight = (\n text: string,\n query: string,\n normalize: AutocompleteNormalizer = normalizeText\n): AutocompleteHighlightPart[] => {\n const terms = getTerms(query, normalize)\n if (terms.length === 0 || text === '') return text === '' ? [] : [{ text, match: false }]\n\n // Map every normalized code unit back to the original character it came from.\n let normalized = ''\n const starts: number[] = []\n const ends: number[] = []\n let offset = 0\n\n for (const char of text) {\n const piece = normalize(char)\n // A character removed by normalization (a combining accent) belongs to\n // the character before it, so a match keeps its accent.\n if (piece === '' && ends.length > 0) ends[ends.length - 1] = offset + char.length\n normalized += piece\n for (let index = 0; index < piece.length; index += 1) {\n starts.push(offset)\n ends.push(offset + char.length)\n }\n offset += char.length\n }\n\n const ranges: [number, number][] = []\n for (const term of terms) {\n let found = normalized.indexOf(term)\n while (found !== -1) {\n ranges.push([starts[found]!, ends[found + term.length - 1]!])\n found = normalized.indexOf(term, found + 1)\n }\n }\n\n if (ranges.length === 0) return [{ text, match: false }]\n\n ranges.sort((a, b) => a[0] - b[0])\n const merged: [number, number][] = []\n for (const range of ranges) {\n const last = merged[merged.length - 1]\n if (last && range[0] <= last[1]) last[1] = Math.max(last[1], range[1])\n else merged.push([range[0], range[1]])\n }\n\n const parts: AutocompleteHighlightPart[] = []\n let cursor = 0\n for (const [start, end] of merged) {\n if (start > cursor) parts.push({ text: text.slice(cursor, start), match: false })\n parts.push({ text: text.slice(start, end), match: true })\n cursor = end\n }\n if (cursor < text.length) parts.push({ text: text.slice(cursor), match: false })\n\n return parts\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
autocomplete
|
|
3
|
+
} from "./chunk-U5JYDGKW.js";
|
|
4
|
+
|
|
5
|
+
// src/browser/registry.ts
|
|
6
|
+
var available = /* @__PURE__ */ Object.create(null);
|
|
7
|
+
var removeRegistration = (id) => {
|
|
8
|
+
const entry = available[id];
|
|
9
|
+
if (!entry) return;
|
|
10
|
+
delete available[id];
|
|
11
|
+
entry.destroy();
|
|
12
|
+
};
|
|
13
|
+
var newAutocomplete = ({
|
|
14
|
+
id,
|
|
15
|
+
options
|
|
16
|
+
}) => {
|
|
17
|
+
if (!id) {
|
|
18
|
+
console.error("Autocomplete ID is required");
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const input = document.getElementById(id);
|
|
22
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
23
|
+
console.warn(`Autocomplete input with ID ${id} not found`);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
removeRegistration(id);
|
|
27
|
+
const controller = autocomplete(input, options);
|
|
28
|
+
const destroy = controller.destroy;
|
|
29
|
+
const registered = {
|
|
30
|
+
...controller,
|
|
31
|
+
destroy: () => {
|
|
32
|
+
if (available[id] === registered) delete available[id];
|
|
33
|
+
destroy();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
available[id] = registered;
|
|
37
|
+
return registered;
|
|
38
|
+
};
|
|
39
|
+
var getAutocomplete = (id) => available[id] ?? null;
|
|
40
|
+
var getAutocompletes = () => ({
|
|
41
|
+
...available
|
|
42
|
+
});
|
|
43
|
+
var destroyAutocomplete = (target) => {
|
|
44
|
+
if (typeof target === "string") return removeRegistration(target);
|
|
45
|
+
const entry = Object.entries(available).find(([, controller]) => controller.element === target);
|
|
46
|
+
if (entry) removeRegistration(entry[0]);
|
|
47
|
+
};
|
|
48
|
+
var destroyAutocompletes = () => Object.keys(available).forEach(removeRegistration);
|
|
49
|
+
var Autocomplete = {
|
|
50
|
+
autocomplete,
|
|
51
|
+
newAutocomplete,
|
|
52
|
+
getAutocomplete,
|
|
53
|
+
getAutocompletes,
|
|
54
|
+
destroyAutocomplete,
|
|
55
|
+
destroyAutocompletes,
|
|
56
|
+
available
|
|
57
|
+
};
|
|
58
|
+
var browser = Autocomplete;
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
available,
|
|
62
|
+
newAutocomplete,
|
|
63
|
+
getAutocomplete,
|
|
64
|
+
getAutocompletes,
|
|
65
|
+
destroyAutocomplete,
|
|
66
|
+
destroyAutocompletes,
|
|
67
|
+
Autocomplete,
|
|
68
|
+
browser
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=chunk-5UZVFHB4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/browser/registry.ts"],"sourcesContent":["import { autocomplete } from '../vanilla/render'\nimport type { AutocompleteController, AutocompleteOptions } from '../core/types'\n\nexport interface NewAutocompleteInput<T = unknown> {\n /** The id of the input to enhance. */\n id: string\n options: AutocompleteOptions<T>\n}\nexport interface AutocompleteAvailable {\n [id: string]: AutocompleteController<unknown>\n}\nexport interface AutocompleteApi {\n autocomplete: typeof autocomplete\n newAutocomplete: <T = unknown>(input: NewAutocompleteInput<T>) => AutocompleteController<T> | null\n getAutocomplete: <T = unknown>(id: string) => AutocompleteController<T> | null\n getAutocompletes: () => Readonly<Record<string, AutocompleteController<unknown>>>\n destroyAutocomplete: (target: string | HTMLInputElement) => void\n destroyAutocompletes: () => void\n available: AutocompleteAvailable\n}\n\nexport const available: AutocompleteAvailable = Object.create(null) as AutocompleteAvailable\n\nconst removeRegistration = (id: string): void => {\n const entry = available[id]\n if (!entry) return\n delete available[id]\n entry.destroy()\n}\n\nexport const newAutocomplete = <T = unknown>({\n id,\n options,\n}: NewAutocompleteInput<T>): AutocompleteController<T> | null => {\n if (!id) {\n console.error('Autocomplete ID is required')\n return null\n }\n const input = document.getElementById(id)\n if (!(input instanceof HTMLInputElement)) {\n console.warn(`Autocomplete input with ID ${id} not found`)\n return null\n }\n removeRegistration(id)\n const controller = autocomplete(input, options)\n // A direct controller.destroy() must not leave a stale registry entry.\n const destroy = controller.destroy\n const registered: AutocompleteController<T> = {\n ...controller,\n destroy: () => {\n if (available[id] === (registered as AutocompleteController<unknown>)) delete available[id]\n destroy()\n },\n }\n available[id] = registered as AutocompleteController<unknown>\n return registered\n}\n\nexport const getAutocomplete = <T = unknown>(id: string): AutocompleteController<T> | null =>\n (available[id] as AutocompleteController<T> | undefined) ?? null\n\nexport const getAutocompletes = (): Readonly<Record<string, AutocompleteController<unknown>>> => ({\n ...available,\n})\n\nexport const destroyAutocomplete = (target: string | HTMLInputElement): void => {\n if (typeof target === 'string') return removeRegistration(target)\n const entry = Object.entries(available).find(([, controller]) => controller.element === target)\n if (entry) removeRegistration(entry[0])\n}\n\nexport const destroyAutocompletes = (): void => Object.keys(available).forEach(removeRegistration)\n\nexport const Autocomplete: AutocompleteApi = {\n autocomplete,\n newAutocomplete,\n getAutocomplete,\n getAutocompletes,\n destroyAutocomplete,\n destroyAutocompletes,\n available,\n}\nexport const browser = Autocomplete\n"],"mappings":";;;;;AAqBO,IAAM,YAAmC,uBAAO,OAAO,IAAI;AAElE,IAAM,qBAAqB,CAAC,OAAqB;AAC/C,QAAM,QAAQ,UAAU,EAAE;AAC1B,MAAI,CAAC,MAAO;AACZ,SAAO,UAAU,EAAE;AACnB,QAAM,QAAQ;AAChB;AAEO,IAAM,kBAAkB,CAAc;AAAA,EAC3C;AAAA,EACA;AACF,MAAiE;AAC/D,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,6BAA6B;AAC3C,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,SAAS,eAAe,EAAE;AACxC,MAAI,EAAE,iBAAiB,mBAAmB;AACxC,YAAQ,KAAK,8BAA8B,EAAE,YAAY;AACzD,WAAO;AAAA,EACT;AACA,qBAAmB,EAAE;AACrB,QAAM,aAAa,aAAa,OAAO,OAAO;AAE9C,QAAM,UAAU,WAAW;AAC3B,QAAM,aAAwC;AAAA,IAC5C,GAAG;AAAA,IACH,SAAS,MAAM;AACb,UAAI,UAAU,EAAE,MAAO,WAAgD,QAAO,UAAU,EAAE;AAC1F,cAAQ;AAAA,IACV;AAAA,EACF;AACA,YAAU,EAAE,IAAI;AAChB,SAAO;AACT;AAEO,IAAM,kBAAkB,CAAc,OAC1C,UAAU,EAAE,KAA+C;AAEvD,IAAM,mBAAmB,OAAkE;AAAA,EAChG,GAAG;AACL;AAEO,IAAM,sBAAsB,CAAC,WAA4C;AAC9E,MAAI,OAAO,WAAW,SAAU,QAAO,mBAAmB,MAAM;AAChE,QAAM,QAAQ,OAAO,QAAQ,SAAS,EAAE,KAAK,CAAC,CAAC,EAAE,UAAU,MAAM,WAAW,YAAY,MAAM;AAC9F,MAAI,MAAO,oBAAmB,MAAM,CAAC,CAAC;AACxC;AAEO,IAAM,uBAAuB,MAAY,OAAO,KAAK,SAAS,EAAE,QAAQ,kBAAkB;AAE1F,IAAM,eAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACO,IAAM,UAAU;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-AD7T42HJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-PPDQPLPE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|