@nosto/autocomplete
Version:
Library designed to simplify the implementation of search autocomplete functionality
342 lines (332 loc) • 12.3 kB
TypeScript
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 '/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 ✕\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 }}€\n </span>\n {% if hit.listPrice and hit.listPrice != hit.price %}\n <span class=\"ns-autocomplete-product-list-price\">\n {{ hit.listPrice }}\n €\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 };