UNPKG

@nosto/autocomplete

Version:

Library designed to simplify the implementation of search autocomplete functionality

342 lines (332 loc) 12.3 kB
import { SearchOptions, SearchQuery, InputSearchQuery, SearchResult } from '@nosto/nosto-js/client'; import * as _nosto_search_js from '@nosto/search-js'; import { HitDecorator, SearchOptions as SearchOptions$1 } from '@nosto/search-js'; export { priceDecorator } from '@nosto/search-js/currencies'; type AutocompleteInstance = { /** * Open the dropdown. */ open(): void; /** * Close the dropdown. */ close(): void; /** * Destroy the autocomplete instance. */ destroy(): void; }; type SearchAutocompleteOptions = Pick<SearchOptions, "isKeyword" | "redirect">; /** * @param config Autocomplete configuration. * @returns Autocomplete instance. * @group Autocomplete * @category Core * @example * ```js * import { autocomplete } from '@nosto/autocomplete'; * * // Basic usage * autocomplete({ * inputSelector: '#search', * dropdownSelector: '#dropdown', * render: (container, state) => { * container.innerHTML = ` * <div> * <h1>${state.query}</h1> * <ul> * ${state.response.products * .map((product) => `<li>${product.name}</li>`) * .join('')} * </ul> * </div> * `; * }, * fetch: { * products: { * fields: ['name', 'url', 'imageUrl', 'price', 'listPrice', 'brand'], * size: 5 * }, * keywords: { * size: 5, * fields: ['keyword', '_highlight.keyword'], * highlight: { * preTag: `<strong>`, * postTag: '</strong>' * } * } * } * }); * * // Liquid template. * import { autocomplete, fromRemoteLiquidTemplate, fromLiquidTemplate, defaultLiquidTemplate } from '@nosto/autocomplete/liquid'; * * autocomplete({ * inputSelector: '#search', * dropdownSelector: '#dropdown', * render: fromRemoteLiquidTemplate('autocomplete.liquid'), * // Or: * render: window.nostoAutocomplete.fromLiquidTemplate(defaultLiquidTemplate), * ... * }); * * // Mustache template. * import { autocomplete, fromRemoteMustacheTemplate, fromMustacheTemplate, defaultMustacheTemplate } from '@nosto/autocomplete/mustache'; * * autocomplete({ * inputSelector: '#search', * dropdownSelector: '#dropdown', * render: fromRemoteMustacheTemplate('autocomplete.mustache'), * // Or: * render: fromMustacheTemplate(defaultMustacheTemplate), * ... * }); * * // React example. * import { autocomplete, Autocomplete } from '@nosto/autocomplete/react'; * import ReactDOM from 'react-dom'; * * let reactRoot; * * autocomplete({ * inputSelector: '#search', * dropdownSelector: '#dropdown', * render: function (container, state) { * if (!reactRoot) { * reactRoot = ReactDOM.createRoot(container); * } * reactRoot.render(<Autocomplete {...state} />); * }, * ... * }); * ``` */ declare function autocomplete$1<State = DefaultState>(config: AutocompleteConfig<State>): AutocompleteInstance; /** * @group Autocomplete * @category Core */ interface GoogleAnalyticsConfig { /** * Path of search page * @default "/search" */ serpPath?: string; /** * Search query url parameter name * @default "query" */ queryParamName?: string; /** * Enable Google Analytics * @default true */ enabled?: boolean; } type Selector = string | Element; /** * @group Autocomplete * @category Core */ interface AutocompleteConfig<State> { /** * The input element to attach the autocomplete to */ inputSelector: Selector; /** * The dropdown element to attach the autocomplete to */ dropdownSelector: Selector | ((input: HTMLInputElement) => Selector); /** * The function to use to render the dropdown */ render: (container: HTMLElement, state: State) => void | PromiseLike<void>; /** * Minimum length of the query before searching */ minQueryLength?: number; /** * The function to use to fetch the search state */ fetch: SearchQuery | ((input: string) => PromiseLike<State>); /** * The function to use to submit the search */ submit?: (query: string, config: AutocompleteConfig<State>, options?: SearchAutocompleteOptions) => void; /** * Enable history */ historyEnabled?: boolean; /** * Max number of history items to show */ historySize?: number; /** * Enable Nosto Analytics */ nostoAnalytics?: boolean; /** * Google Analytics configuration. Set to `false` to disable. */ googleAnalytics?: GoogleAnalyticsConfig | boolean; /** * Decorate each search hit before rendering * * @example * ```ts * import { priceDecorator } from "@nosto/autocomplete" * * autocomplete({ * hitDecorators: [ * priceDecorator({ defaultCurrency: "USD" }) * ] * }) * ``` */ hitDecorators?: HitDecorator[]; /** * Whether to submit the form natively or not */ nativeSubmit?: boolean; /** * A function to call when the user clicks on a search hit to use custom routing * @example * ```ts * autocomplete({ * routingHandler: (url) => { * location.href = url * } * }) * ``` */ routingHandler?: (url: string) => void; } /** * @group Autocomplete * @category Core */ interface DefaultState { /** * The current search query object. */ query?: InputSearchQuery; /** * The current search response. */ response?: SearchResult; /** * The history items. */ history?: { item: string; }[]; } var autocomplete = "{% assign hasKeywords = response.keywords.hits.length > 0 %}\n{% assign hasProducts = response.products.hits.length > 0 %}\n{% assign hasHistory = history.length > 0 %}\n{% assign imagePlaceHolder = 'https://cdn.nosto.com/nosto/9/mock' %}\n\n<div class=\"ns-autocomplete-results\">\n {% if hasKeywords == false and hasProducts == false and hasHistory %}\n <div class=\"ns-autocomplete-history\">\n <div class=\"ns-autocomplete-header\">\n Recently searched\n </div>\n {% for hit in history %}\n <div class=\"ns-autocomplete-history-item\" data-ns-hit=\"{{ hit | json | escape }}\" data-testid=\"history\">\n {{ hit.item }}\n <a\n href=\"#\"\n class=\"ns-autocomplete-history-item-remove\"\n data-ns-remove-history=\"{{hit.item}}\">\n &#x2715;\n </a>\n </div>\n {% endfor %}\n </div>\n <div class=\"ns-autocomplete-history-clear\">\n <button\n type=\"button\"\n class=\"ns-autocomplete-button\"\n data-ns-remove-history=\"all\">\n Clear history\n </button>\n </div>\n {% elsif hasKeywords or hasProducts %}\n {% if hasKeywords %}\n <div class=\"ns-autocomplete-keywords\">\n <div class=\"ns-autocomplete-header\">\n Keywords\n </div>\n {% for hit in response.keywords.hits %}\n <div class=\"ns-autocomplete-keyword\" data-ns-hit=\"{{ hit | json | escape }}\" data-testid=\"keyword\">\n {% if hit._highlight and hit._highlight.keyword %}\n <span>{{ hit._highlight.keyword }}</span>\n {% else %}\n <span>{{ hit.keyword }}</span>\n {% endif %}\n </div>\n {% endfor %}\n </div>\n {% endif %}\n {% if hasProducts %}\n <div class=\"ns-autocomplete-products\">\n <div class=\"ns-autocomplete-header\">\n Products\n </div>\n {% for hit in response.products.hits %}\n <a\n class=\"ns-autocomplete-product\"\n href=\"{{ hit.url }}\"\n data-ns-hit=\"{{ hit | json | escape }}\"\n data-testid=\"product\">\n <img\n class=\"ns-autocomplete-product-image\"\n src=\"{% if hit.imageUrl %}{{ hit.imageUrl }}{% else %}{{ imagePlaceHolder }}{% endif %}\"\n alt=\"{{ hit.name }}\"\n width=\"60\"\n height=\"40\" />\n <div class=\"ns-autocomplete-product-info\">\n {% if hit.brand %}\n <div class=\"ns-autocomplete-product-brand\">\n {{ hit.brand }}\n </div>\n {% endif %}\n <div class=\"ns-autocomplete-product-name\">\n {{ hit.name }}\n </div>\n <div>\n <span>\n {{ hit.price }}&euro;\n </span>\n {% if hit.listPrice and hit.listPrice != hit.price %}\n <span class=\"ns-autocomplete-product-list-price\">\n {{ hit.listPrice }}\n &euro;\n </span>\n {% endif %}\n </div>\n </div>\n </a>\n {% endfor %}\n </div>\n {% endif %}\n <div class=\"ns-autocomplete-submit\">\n <button type=\"submit\" class=\"ns-autocomplete-button\">\n See all search results\n </button>\n </div>\n {% endif %}\n</div>\n"; /** * Render a liquid template into a container * * @param template Liquid template * @returns Render function * @group Autocomplete * @category Liquid * @example * ```js * import { fromLiquidTemplate } from "@nosto/autocomplete/liquid"; * * const render = fromLiquidTemplate(` * <div> * <h1>{{title}}</h1> * <ul> * {% for product in products %} * <li>{{product.name}}</li> * {% endfor %} * </ul> * </div> * `); * * render(document.getElementById("container"), { * title: "My Title", * products: [ * { name: "Product 1" }, * { name: "Product 2" }, * { name: "Product 3" } * ] * }); * ``` */ declare function fromLiquidTemplate<State extends object = DefaultState>(template: string): (container: HTMLElement, state: State) => PromiseLike<void>; /** * * @param query Query object. * @param options Options object. * @returns Promise of search response. * @group Autocomplete * @category Core * @example * ```js * import { search } from "@nosto/autocomplete" * * search({ * query: "shoes", * products: { * fields: ["name", "price"], * facets: ["brand", "category"], * size: 10, * from: 0, * } * }).then((state) => { * console.log(state.response) * }) * ``` */ declare function search(query: SearchQuery, options?: SearchOptions$1): Promise<{ query: SearchQuery; response: _nosto_search_js.DecoratedResult<readonly _nosto_search_js.HitDecorator[]>; }>; /** * Nosto Autocomplete Web Component using Liquid templates. * * This component integrates the Nosto Autocomplete functionality with Liquid templating. * It fetches the configuration from a script tag, compiles the Liquid template, * and initializes the autocomplete with the compiled template as the render function. * If a custom template isn't passed, the component will use the default Liquid template. * * @example * ```html * <nosto-autocomplete> * <form> * <input type="text" id="input" /> * </form> * <div id="results"></div> * <script autocomplete-config type="application/json"> * { * "inputSelector": "#input", * "dropdownSelector": "#results", * "fetch": { * "products": { * "fields": ["name", "url", "imageUrl", "price", "listPrice", "brand"], * "size": 5 * }, * "keywords": { * "size": 5, * "fields": ["keyword", "_highlight.keyword"] * } * } * } * </script> * <script type="text/x-liquid" autocomplete-template> * <h1>{{ query }}</h1> * <ul> * {% for product in products %} * <li>{{ product.name }}</li> * {% endfor %} * </ul> * </script> * </nosto-autocomplete> * ``` */ declare class NostoAutocomplete extends HTMLElement { connectedCallback(): Promise<AutocompleteInstance>; } export { NostoAutocomplete, autocomplete$1 as autocomplete, autocomplete as defaultLiquidTemplate, fromLiquidTemplate, search }; export type { AutocompleteConfig, AutocompleteInstance, DefaultState, GoogleAnalyticsConfig };