<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][README.md]" value="# Autocomplete with InstantSearch.js example

This example shows how to integrate Autocomplete with [InstantSearch.js](https://github.com/algolia/instantsearch.js/).

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;capture.png?raw=true&quot; alt=&quot;A capture of the Autocomplete with InstantSearch.js demo&quot; /&gt;&lt;/p&gt;

## Demo

[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/instantsearch)

## How to run this example locally

### 1. Clone this repository

```sh
git clone git@github.com:algolia/autocomplete.git
```

### 2. Install the dependencies and run the server

```sh
yarn
yarn workspace @algolia/autocomplete-example-instantsearch start
```

Alternatively, you may use npm:

```sh
cd examples/instantsearch
npm install
npm start
```

Open &lt;http://localhost:1234&gt; to see your app.">
<input type="hidden" name="project[files][capture.png]" value="https://pkg.pr.new/template/6112b7e8-8f9a-448e-b750-1bcb1d65f44c">
<input type="hidden" name="project[files][env.ts]" value="// Parcel picks the `source` field of the monorepo packages and thus doesn&#39;t
// apply the Babel config. We therefore need to manually override the constants
// in the app, as well as the React pragmas.
// See https://twitter.com/devongovett/status/1134231234605830144
(global as any).__DEV__ = process.env.NODE_ENV !== &#39;production&#39;;
(global as any).__TEST__ = false;
">
<input type="hidden" name="project[files][favicon.png]" value="https://pkg.pr.new/template/246b8d55-b7cd-48b1-b9b6-db1a2fce5f2c">
<input type="hidden" name="project[files][index.html]" value="&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;

    &lt;link rel=&quot;shortcut icon&quot; href=&quot;favicon.png&quot; type=&quot;image/x-icon&quot; /&gt;
    &lt;link
      rel=&quot;stylesheet&quot;
      href=&quot;https://unpkg.com/instantsearch.css@7/themes/satellite-min.css&quot;
    /&gt;

    &lt;title&gt;InstantSearch | Autocomplete&lt;/title&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;header class=&quot;header&quot;&gt;
      &lt;div class=&quot;header-wrapper wrapper&quot;&gt;
        &lt;nav class=&quot;header-nav&quot;&gt;
          &lt;a href=&quot;/&quot;&gt;Home&lt;/a&gt;
        &lt;/nav&gt;
        &lt;div id=&quot;autocomplete&quot;&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/header&gt;

    &lt;div class=&quot;container wrapper&quot;&gt;
      &lt;div&gt;
        &lt;div id=&quot;categories&quot;&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;img src=&quot;iphone-banner.png&quot; alt=&quot;iPhone banner&quot; class=&quot;banner&quot; /&gt;

        &lt;div id=&quot;hits&quot;&gt;&lt;/div&gt;
        &lt;div id=&quot;pagination&quot;&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;script type=&quot;module&quot; src=&quot;env.ts&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;module&quot; src=&quot;src/app.ts&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
">
<input type="hidden" name="project[files][iphone-banner.png]" value="https://pkg.pr.new/template/de386560-6549-4ea3-8d0b-5acde3e772ad">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;@algolia/autocomplete-example-instantsearch&quot;,&quot;description&quot;:&quot;Autocomplete with InstantSearch example&quot;,&quot;version&quot;:&quot;1.19.8&quot;,&quot;private&quot;:true,&quot;license&quot;:&quot;MIT&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;vite&quot;,&quot;start&quot;:&quot;vite&quot;,&quot;build&quot;:&quot;vite build&quot;},&quot;dependencies&quot;:{&quot;@algolia/autocomplete-js&quot;:&quot;https://pkg.pr.new/algolia/autocomplete/@algolia/autocomplete-js@f9ecdbc&quot;,&quot;@algolia/autocomplete-plugin-query-suggestions&quot;:&quot;https://pkg.pr.new/algolia/autocomplete/@algolia/autocomplete-plugin-query-suggestions@f9ecdbc&quot;,&quot;@algolia/autocomplete-plugin-recent-searches&quot;:&quot;https://pkg.pr.new/algolia/autocomplete/@algolia/autocomplete-plugin-recent-searches@f9ecdbc&quot;,&quot;@algolia/autocomplete-theme-classic&quot;:&quot;https://pkg.pr.new/algolia/autocomplete/@algolia/autocomplete-theme-classic@f9ecdbc&quot;,&quot;@algolia/client-search&quot;:&quot;4.16.0&quot;,&quot;algoliasearch&quot;:&quot;4.16.0&quot;,&quot;instantsearch.js&quot;:&quot;4.73.1&quot;},&quot;devDependencies&quot;:{&quot;vite&quot;:&quot;5.0.7&quot;},&quot;keywords&quot;:[&quot;algolia&quot;,&quot;autocomplete&quot;,&quot;javascript&quot;]}">
<input type="hidden" name="project[files][style.css]" value="* {
  box-sizing: border-box;
}

body {
  background-color: rgb(244, 244, 249);
  color: rgb(65, 65, 65);
  font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, &#39;Roboto&#39;, &#39;Oxygen&#39;,
    &#39;Ubuntu&#39;, &#39;Cantarell&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, &#39;Helvetica Neue&#39;,
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  margin: 0;
  padding: 0;
}

a {
  color: var(--aa-primary-color);
  text-decoration: none;
}

.header {
  background: rgb(252 252 255 / 92%);
  box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.05),
    0 1px 3px 0 rgba(35, 38, 59, 0.15);
  padding: 0.5rem 0;
  position: fixed;
  top: 0;
  width: 100%;
}

.header-wrapper {
  align-items: center;
  display: grid;
  grid-template-columns: 100px 1fr;
}

.header-nav {
  font-weight: 500;
}

.wrapper {
  margin: 0 auto;
  max-width: 1200px;
  padding: 0 1.5rem;
  width: 100%;
}

.container {
  margin-top: 3.5rem;
  padding: 1.5rem;
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr 3fr;
}

.container img {
  max-width: 100%;
}

.banner {
  border-radius: 3px;
  margin-top: 1rem;
}

/* Autocomplete */

.aa-Panel {
  position: fixed;
}

/* InstantSearch */

.ais-Hits {
  margin-top: 1rem;
}

.ais-Hits-list {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr 1fr 1fr;
}

.ais-Hits-item {
  padding: 1rem !important;
}

.hit {
  align-items: center;
  display: grid;
  gap: 1rem;
}

.hit h1 {
  font-size: 1rem;
}

.hit p {
  font-size: 0.8rem;
  opacity: 0.8;
}

.hit-image {
  align-items: center;
  display: flex;
  height: 100px;
  justify-content: center;
}

.hit-image img {
  max-height: 100%;
}

.ais-HierarchicalMenu-item--selected.ais-HierarchicalMenu-item--parent
  &gt; div:first-of-type
  .ais-HierarchicalMenu-label {
  font-weight: bold;
}

.ais-HierarchicalMenu-item--selected:not(.ais-HierarchicalMenu-item--parent)
  .ais-HierarchicalMenu-label {
  font-weight: bold;
}

.ais-Pagination {
  display: flex;
  justify-content: center;
  margin: 2rem 0;
}
">
<input type="hidden" name="project[files][vite.config.mjs]" value="import { defineConfig } from &#39;vite&#39;;

export default defineConfig({
  server: {
    port: 1234,
  },
});
">
<input type="hidden" name="project[files][src/app.ts]" value="import &#39;@algolia/autocomplete-theme-classic&#39;;
import &#39;../style.css&#39;;

import { startAutocomplete } from &#39;./autocomplete&#39;;
import { search } from &#39;./instantsearch&#39;;

search.start();
startAutocomplete(search);
">
<input type="hidden" name="project[files][src/autocomplete.tsx]" value="/** @jsxRuntime classic */
/** @jsx h */
import { autocomplete } from &#39;@algolia/autocomplete-js&#39;;
import { createQuerySuggestionsPlugin } from &#39;@algolia/autocomplete-plugin-query-suggestions&#39;;
import { createLocalStorageRecentSearchesPlugin } from &#39;@algolia/autocomplete-plugin-recent-searches&#39;;
import { InstantSearch } from &#39;instantsearch.js&#39;;

import {
  debouncedSetInstantSearchUiState,
  getInstantSearchCurrentCategory,
  getInstantSearchUiState,
  getInstantSearchUrl,
  INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE,
  INSTANT_SEARCH_INDEX_NAME,
  setInstantSearchUiState,
} from &#39;./instantsearch&#39;;
import { isModifierEvent } from &#39;./isModifierEvent&#39;;
import { searchClient } from &#39;./searchClient&#39;;

function onSelect({ setIsOpen, setQuery, event, query, category }) {
  // You want to trigger the default browser behavior if the event is modified.
  if (isModifierEvent(event)) {
    return;
  }

  setQuery(query);
  setIsOpen(false);
  setInstantSearchUiState({
    query,
    hierarchicalMenu: {
      [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category],
    },
  });
}

function getItemUrl({ query, category }) {
  return getInstantSearchUrl({
    query,
    hierarchicalMenu: {
      [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category],
    },
  });
}

function getItemWrapper({ html, children, query, category }) {
  const uiState = {
    query,
    hierarchicalMenu: {
      [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category],
    },
  };

  return html`&lt;a
    class=&quot;aa-ItemLink&quot;
    href=${getInstantSearchUrl(uiState)}
    onClick=${(event) =&gt; {
      if (!isModifierEvent(event)) {
        // Bypass the original link behavior if there&#39;s no event modifier
        // to set the InstantSearch UI state without reloading the page.
        event.preventDefault();
      }
    }}
  &gt;
    ${children}
  &lt;/a&gt;`;
}

const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
  key: &#39;instantsearch&#39;,
  limit: 3,
  transformSource({ source }) {
    return {
      ...source,
      getItemUrl({ item }) {
        return getItemUrl({
          query: item.label,
          category: item.category,
        });
      },
      onSelect({ setIsOpen, setQuery, item, event }) {
        onSelect({
          setQuery,
          setIsOpen,
          event,
          query: item.label,
          category: item.category,
        });
      },
      templates: {
        ...source.templates,
        // Update the default `item` template to wrap it with a link
        // and plug it to the InstantSearch router.
        item(params) {
          const { children } = (source.templates.item(params) as any).props;
          const { item, html } = params;

          return getItemWrapper({
            query: item.label,
            category: item.category,
            html,
            children,
          });
        },
      },
    };
  },
});

const querySuggestionsPluginInCategory = createQuerySuggestionsPlugin({
  searchClient,
  indexName: &#39;instant_search_demo_query_suggestions&#39;,
  getSearchParams() {
    const currentCategory = getInstantSearchCurrentCategory();

    return recentSearchesPlugin.data!.getAlgoliaSearchParams({
      hitsPerPage: 3,
      facetFilters: [
        `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:${currentCategory}`,
      ],
    });
  },
  transformSource({ source }) {
    const currentCategory = getInstantSearchCurrentCategory();

    return {
      ...source,
      sourceId: &#39;querySuggestionsPluginInCategory&#39;,
      getItemUrl({ item }) {
        return getItemUrl({
          query: item.query,
          category: currentCategory,
        });
      },
      onSelect({ setIsOpen, setQuery, event, item }) {
        onSelect({
          setQuery,
          setIsOpen,
          event,
          query: item.query,
          category: currentCategory,
        });
      },
      getItems(params) {
        if (!currentCategory) {
          return [];
        }

        return source.getItems(params);
      },
      templates: {
        ...source.templates,
        header({ html }) {
          return html`
            &lt;span class=&quot;aa-SourceHeaderTitle&quot;&gt;In ${currentCategory}&lt;/span&gt;
            &lt;div class=&quot;aa-SourceHeaderLine&quot; /&gt;
          `;
        },
        item(params) {
          const { children } = (source.templates.item(params) as any).props;
          const { item, html } = params;

          return getItemWrapper({
            query: item.query,
            category: currentCategory,
            html,
            children,
          });
        },
      },
    };
  },
});

const querySuggestionsPlugin = createQuerySuggestionsPlugin({
  searchClient,
  indexName: &#39;instant_search_demo_query_suggestions&#39;,
  getSearchParams() {
    const currentCategory = getInstantSearchCurrentCategory();

    if (!currentCategory) {
      return recentSearchesPlugin.data!.getAlgoliaSearchParams({
        hitsPerPage: 6,
      });
    }

    return recentSearchesPlugin.data!.getAlgoliaSearchParams({
      hitsPerPage: 3,
      facetFilters: [
        `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:-${currentCategory}`,
      ],
    });
  },
  categoryAttribute: [
    INSTANT_SEARCH_INDEX_NAME,
    &#39;facets&#39;,
    &#39;exact_matches&#39;,
    INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE,
  ],
  transformSource({ source }) {
    const currentCategory = getInstantSearchCurrentCategory();

    return {
      ...source,
      sourceId: &#39;querySuggestionsPlugin&#39;,
      getItemUrl({ item }) {
        return getItemUrl({
          query: item.query,
          category: item.__autocomplete_qsCategory,
        });
      },
      onSelect({ setIsOpen, setQuery, event, item }) {
        onSelect({
          setQuery,
          setIsOpen,
          event,
          query: item.query,
          category: item.__autocomplete_qsCategory,
        });
      },
      getItems(params) {
        if (!params.state.query) {
          return [];
        }

        return source.getItems(params);
      },
      templates: {
        ...source.templates,
        header({ html }) {
          if (!currentCategory) {
            return null;
          }

          return html`
            &lt;span class=&quot;aa-SourceHeaderTitle&quot;&gt;In other categories&lt;/span&gt;
            &lt;div class=&quot;aa-SourceHeaderLine&quot; /&gt;
          `;
        },
        item(params) {
          const { children } = (source.templates.item(params) as any).props;
          const { item, html } = params;

          return getItemWrapper({
            query: item.query,
            category: item.__autocomplete_qsCategory,
            html,
            children,
          });
        },
      },
    };
  },
});

const searchPageState = getInstantSearchUiState();

export function startAutocomplete(searchInstance: InstantSearch) {
  let skipInstantSearchStateUpdate = false;

  const { setQuery } = autocomplete({
    container: &#39;#autocomplete&#39;,
    placeholder: &#39;Search for products&#39;,
    insights: true,
    openOnFocus: true,
    plugins: [
      recentSearchesPlugin,
      querySuggestionsPluginInCategory,
      querySuggestionsPlugin,
    ],
    detachedMediaQuery: &#39;none&#39;,
    initialState: {
      query: searchPageState.query || &#39;&#39;,
    },
    navigator: {
      navigate() {
        // We don&#39;t navigate to a new page because we leverage the InstantSearch
        // UI state API.
      },
    },
    onSubmit({ state }) {
      setInstantSearchUiState({ query: state.query });
    },
    onReset() {
      setInstantSearchUiState({
        query: &#39;&#39;,
        hierarchicalMenu: {
          [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [],
        },
      });
    },
    onStateChange({ prevState, state }) {
      if (!skipInstantSearchStateUpdate &amp;&amp; prevState.query !== state.query) {
        debouncedSetInstantSearchUiState({ query: state.query });
      }
      skipInstantSearchStateUpdate = false;
    },
  });

  window.addEventListener(&#39;popstate&#39;, () =&gt; {
    skipInstantSearchStateUpdate = true;
    setQuery(
      (searchInstance.helper &amp;&amp; searchInstance.helper.state.query) || &#39;&#39;
    );
  });
}
">
<input type="hidden" name="project[files][src/debounce.ts]" value="export function debounce&lt;TParams&gt;(
  fn: (...params: TParams[]) =&gt; void,
  time: number
) {
  let timerId: ReturnType&lt;typeof setTimeout&gt; | undefined = undefined;

  return function (...args: TParams[]) {
    if (timerId) {
      clearTimeout(timerId);
    }

    timerId = setTimeout(() =&gt; fn(...args), time);
  };
}
">
<input type="hidden" name="project[files][src/instantsearch.ts]" value="import instantsearch from &#39;instantsearch.js&#39;;
import { connectSearchBox } from &#39;instantsearch.js/es/connectors&#39;;
import historyRouter from &#39;instantsearch.js/es/lib/routers/history&#39;;
import {
  configure,
  hierarchicalMenu,
  hits,
  pagination,
  panel,
} from &#39;instantsearch.js/es/widgets&#39;;

import { debounce } from &#39;../src/debounce&#39;;
import { searchClient } from &#39;../src/searchClient&#39;;

export const INSTANT_SEARCH_INDEX_NAME = &#39;instant_search&#39;;
export const INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE =
  &#39;hierarchicalCategories.lvl0&#39;;

const instantSearchRouter = historyRouter();

export const search = instantsearch({
  searchClient,
  indexName: INSTANT_SEARCH_INDEX_NAME,
  routing: instantSearchRouter,
});
const virtualSearchBox = connectSearchBox(() =&gt; {});
const hierarchicalMenuWithHeader = panel({
  templates: { header: &#39;Categories&#39; },
})(hierarchicalMenu);

search.addWidgets([
  configure({
    attributesToSnippet: [&#39;name:7&#39;, &#39;description:15&#39;],
    snippetEllipsisText: &#39;…&#39;,
  }),
  // Mount a virtual search box to manipulate InstantSearch&#39;s `query` UI
  // state parameter.
  virtualSearchBox(),
  hierarchicalMenuWithHeader({
    container: &#39;#categories&#39;,
    attributes: [
      INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE,
      &#39;hierarchicalCategories.lvl1&#39;,
    ],
  }),
  hits({
    container: &#39;#hits&#39;,
    transformItems(items) {
      return items.map((item) =&gt; ({
        ...item,
        category: item.categories[0],
        comments: (item.popularity % 100).toLocaleString(),
        sale: item.free_shipping,
        // eslint-disable-next-line @typescript-eslint/camelcase
        sale_price: item.free_shipping
          ? (item.price - item.price / 10).toFixed(2)
          : item.price.toString(),
      }));
    },
    templates: {
      item: (hit, { html, components }) =&gt; html`
        &lt;article class=&quot;hit&quot;&gt;
          &lt;div class=&quot;hit-image&quot;&gt;
            &lt;img src=&quot;${hit.image}&quot; alt=&quot;${hit.name}&quot; /&gt;
          &lt;/div&gt;
          &lt;div&gt;
            &lt;h1&gt;${components.Snippet({ hit, attribute: &#39;name&#39; })}&lt;/h1&gt;
            &lt;div&gt;
              By &lt;strong&gt;${hit.brand}&lt;/strong&gt; in
              &lt;strong&gt;${hit.category}&lt;/strong&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div
            style=&quot;
              display: grid;
              grid-auto-flow: column;
              justify-content: start;
              align-items: center;
              gap: 8px;
            &quot;
          &gt;
            ${hit.rating &gt; 0 &amp;&amp;
            html`
              &lt;div
                style=&quot;
                  display: grid;
                  grid-auto-flow: column;
                  justify-content: start;
                  align-items: center;
                  gap: 4px;
                &quot;
              &gt;
                &lt;svg
                  width=&quot;16&quot;
                  height=&quot;16&quot;
                  viewBox=&quot;0 0 24 24&quot;
                  fill=&quot;#e2a400&quot;
                  stroke=&quot;#e2a400&quot;
                  stroke-width=&quot;1&quot;
                  stroke-linecap=&quot;round&quot;
                  stroke-linejoin=&quot;round&quot;
                &gt;
                  &lt;polygon
                    points=&quot;12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2&quot;
                  /&gt;
                &lt;/svg&gt;
                ${hit.rating}
              &lt;/div&gt;
            `}

            &lt;div
              style=&quot;
                display: grid;
                grid-auto-flow: column;
                justify-content: start;
                align-items: center;
                gap: 4px;
              &quot;
            &gt;
              &lt;svg
                width=&quot;16&quot;
                height=&quot;16&quot;
                viewBox=&quot;0 0 24 24&quot;
                fill=&quot;none&quot;
                stroke=&quot;currentColor&quot;
                stroke-width=&quot;2&quot;
                stroke-linecap=&quot;round&quot;
                stroke-linejoin=&quot;round&quot;
                style=&quot;
                  position: relative;
                  top: 1px;
                &quot;
              &gt;
                &lt;path
                  d=&quot;M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z&quot;
                &gt;&lt;/path&gt;
              &lt;/svg&gt;
              &lt;span&gt;${hit.comments}&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/article&gt;
      `,
    },
  }),
  pagination({
    container: &#39;#pagination&#39;,
    padding: 2,
    showFirst: false,
    showLast: false,
  }),
]);

// Set the InstantSearch index UI state from external events.
export function setInstantSearchUiState(indexUiState) {
  search.setUiState((uiState) =&gt; ({
    ...uiState,
    [INSTANT_SEARCH_INDEX_NAME]: {
      ...uiState[INSTANT_SEARCH_INDEX_NAME],
      // We reset the page when the search state changes.
      page: 1,
      ...indexUiState,
    },
  }));
}

export const debouncedSetInstantSearchUiState = debounce(
  setInstantSearchUiState,
  500
);

// Get the current category from InstantSearch.
export function getInstantSearchCurrentCategory() {
  const indexUiState = search.getUiState()[INSTANT_SEARCH_INDEX_NAME];
  const hierarchicalMenuUiState = indexUiState &amp;&amp; indexUiState.hierarchicalMenu;
  const currentCategories =
    hierarchicalMenuUiState &amp;&amp;
    hierarchicalMenuUiState[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE];

  return currentCategories &amp;&amp; currentCategories[0];
}

// Build URLs that InstantSearch understands.
export function getInstantSearchUrl(indexUiState) {
  return search.createURL({ [INSTANT_SEARCH_INDEX_NAME]: indexUiState });
}

// Return the InstantSearch index UI state.
export function getInstantSearchUiState() {
  const uiState = instantSearchRouter.read();

  return (uiState &amp;&amp; uiState[INSTANT_SEARCH_INDEX_NAME]) || {};
}
">
<input type="hidden" name="project[files][src/isModifierEvent.ts]" value="/**
 * Detect when an event is modified with a special key to let the browser
 * trigger its default behavior.
 */
export function isModifierEvent&lt;TEvent extends MouseEvent | KeyboardEvent&gt;(
  event: TEvent
): boolean {
  const isMiddleClick = (event as MouseEvent).button === 1;

  return (
    isMiddleClick ||
    event.altKey ||
    event.ctrlKey ||
    event.metaKey ||
    event.shiftKey
  );
}
">
<input type="hidden" name="project[files][src/searchClient.ts]" value="import algoliasearch from &#39;algoliasearch/lite&#39;;

export const searchClient = algoliasearch(
  &#39;latency&#39;,
  &#39;6be0576ff61c053d5f9a3225e2a90f76&#39;
);
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="@algolia/autocomplete-example-instantsearch">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>