From f95738c29383b9e4965d5e00828937c44e27165a Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Fri, 20 Dec 2024 13:44:01 +0100 Subject: [PATCH 1/2] fix(state): change behaviour of preserveSharedStateOnUnmount With preserveSharedStateOnUnmount behavior, UI State is the source of truth, when a widget gets removed, the UI State is evaluated again and the parameters result out of that. [FX-3192] Not done in this PR yet: - changed the signature of dispose (no more helper and state) - implement a similar behavior for recommend BREAKING CHANGE: The option `future.preserveSharedStateOnUnmount` is removed and now behaves as if it was set to `true` --- examples/react/next/pages/index.tsx | 3 - .../e2e/__snapshots__/templates.test.js.snap | 5 - .../templates/InstantSearch.js/src/app.js.hbs | 234 ++++++------------ .../React InstantSearch/src/App.tsx.hbs | 4 +- .../Vue InstantSearch with Vue 3/src/App.vue | 1 - .../instantsearch-core/src/instantsearch.ts | 17 +- .../src/types/instantsearch.ts | 13 +- .../widgets/__tests__/index-widget.test.ts | 122 +-------- .../src/widgets/index-widget.ts | 27 +- 9 files changed, 102 insertions(+), 324 deletions(-) diff --git a/examples/react/next/pages/index.tsx b/examples/react/next/pages/index.tsx index d307acb0c9..f536e8d36f 100644 --- a/examples/react/next/pages/index.tsx +++ b/examples/react/next/pages/index.tsx @@ -67,9 +67,6 @@ export default function HomePage({ serverState, url }: HomePageProps) { }), }} insights={true} - future={{ - preserveSharedStateOnUnmount: true, - }} >
diff --git a/packages/create-instantsearch-app/e2e/__snapshots__/templates.test.js.snap b/packages/create-instantsearch-app/e2e/__snapshots__/templates.test.js.snap index 00b5a1ea97..cb4b172763 100644 --- a/packages/create-instantsearch-app/e2e/__snapshots__/templates.test.js.snap +++ b/packages/create-instantsearch-app/e2e/__snapshots__/templates.test.js.snap @@ -1900,7 +1900,6 @@ const searchClient = algoliasearch('appId', 'apiKey'); const search = instantsearch({ indexName: 'indexName', searchClient, - future: { preserveSharedStateOnUnmount: true }, insights: true, }); @@ -4125,8 +4124,6 @@ import './App.css'; const searchClient = algoliasearch('appId', 'apiKey'); -const future = { preserveSharedStateOnUnmount: true }; - export function App() { return (
@@ -4146,7 +4143,6 @@ export function App() { @@ -5218,7 +5214,6 @@ export default { data() { return { searchClient: algoliasearch('appId', 'apiKey'), - future: { preserveSharedStateOnUnmount: true }, }; }, }; diff --git a/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs b/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs index 76bb02773e..f539b4578a 100644 --- a/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs +++ b/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs @@ -1,177 +1,105 @@ const { algoliasearch, instantsearch } = window; {{#if flags.autocomplete}} -const { autocomplete } = window['@algolia/autocomplete-js']; -const { createLocalStorageRecentSearchesPlugin } = window[ - '@algolia/autocomplete-plugin-recent-searches' -]; -const { createQuerySuggestionsPlugin } = window[ - '@algolia/autocomplete-plugin-query-suggestions' -]; + const { autocomplete } = window['@algolia/autocomplete-js']; const { + createLocalStorageRecentSearchesPlugin } = window[ + '@algolia/autocomplete-plugin-recent-searches' ]; const { + createQuerySuggestionsPlugin } = window[ + '@algolia/autocomplete-plugin-query-suggestions' ]; {{/if}} -const searchClient = algoliasearch('{{appId}}', '{{apiKey}}'); - -const search = instantsearch({ - indexName: '{{indexName}}', - searchClient, - future: { preserveSharedStateOnUnmount: true }, - {{#if flags.insights}}insights: true,{{/if}} +const searchClient = algoliasearch('{{appId}}', '{{apiKey}}'); const search = +instantsearch({ indexName: '{{indexName}}', searchClient, +{{#if flags.insights}}insights: true,{{/if}} }); {{#if flags.autocomplete}} -const virtualSearchBox = instantsearch.connectors.connectSearchBox(() => {}); + const virtualSearchBox = instantsearch.connectors.connectSearchBox(() => {}); {{/if}} search.addWidgets([ - {{#unless flags.autocomplete}} - instantsearch.widgets.searchBox({ - container: '#searchbox', - {{#if searchPlaceholder}} +{{#unless flags.autocomplete}} + instantsearch.widgets.searchBox({ container: '#searchbox', + {{#if searchPlaceholder}} placeholder: '{{searchPlaceholder}}', - {{/if}} + {{/if}} }), - {{else}} +{{else}} virtualSearchBox({}), - {{/unless}} - instantsearch.widgets.hits({ - container: '#hits', - {{#if attributesToDisplay}} - templates: { - item: (hit, { html, components }) => html` -
- {{#if imageAttribute}} - ${ - {{/if}} -
-

${components.Highlight({hit, attribute: "{{attributesToDisplay.[0]}}"})}

- {{#each attributesToDisplay}} - {{#unless @first}} -

${components.Highlight({hit, attribute: "{{this}}"})}

- {{/unless}} - {{/each}} -
-
-`, - }, +{{/unless}} +instantsearch.widgets.hits({ container: '#hits', +{{#if attributesToDisplay}} + templates: { item: (hit, { html, components }) => html` +
+ {{#if imageAttribute}} + ${ {{/if}} - }), - instantsearch.widgets.configure({ - hitsPerPage: 8, - }), - {{#if flags.dynamicWidgets}} - instantsearch.widgets.dynamicWidgets({ - container: '#dynamic-widgets', - fallbackWidget({ container, attribute }) { - return instantsearch.widgets.panel({ templates: { header: () => attribute } })( - instantsearch.widgets.refinementList - )({ - container, - attribute, - }); - }, - widgets: [ - {{#each attributesForFaceting}} - container => - instantsearch.widgets.panel({ - templates: { header: () => '{{this}}' }, - })(instantsearch.widgets.refinementList)({ - container, - attribute: '{{this}}', - }), +
+

${components.Highlight({hit, attribute: "{{attributesToDisplay.0}}"})}

+ {{#each attributesToDisplay}} + {{#unless @first}} +

${components.Highlight({hit, attribute: "{{this}}"})}

+ {{/unless}} {{/each}} - ], - }), - {{else}} +
+
+ `, }, +{{/if}} +}), instantsearch.widgets.configure({ hitsPerPage: 8, }), +{{#if flags.dynamicWidgets}} + instantsearch.widgets.dynamicWidgets({ container: '#dynamic-widgets', + fallbackWidget({ container, attribute }) { return + instantsearch.widgets.panel({ templates: { header: () => attribute } })( + instantsearch.widgets.refinementList )({ container, attribute, }); }, widgets: + [ {{#each attributesForFaceting}} - instantsearch.widgets.panel({ - templates: { header: () => '{{this}}' }, - })(instantsearch.widgets.refinementList)({ - container: '#{{this}}-list', - attribute: '{{this}}', - }), + container => instantsearch.widgets.panel({ templates: { header: () => '{{this}}' + }, })(instantsearch.widgets.refinementList)({ container, attribute: '{{this}}', + }), {{/each}} - {{/if}} - instantsearch.widgets.pagination({ - container: '#pagination', - }), -]); - + ], }), +{{else}} + {{#each attributesForFaceting}} + instantsearch.widgets.panel({ templates: { header: () => '{{this}}' }, + })(instantsearch.widgets.refinementList)({ container: '#{{this}}-list', + attribute: '{{this}}', }), + {{/each}} +{{/if}} +instantsearch.widgets.pagination({ container: '#pagination', }), ]); search.start(); {{#if flags.autocomplete}} -const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ - key: 'instantsearch', - limit: 3, - transformSource({ source }) { - return { - ...source, - onSelect({ setIsOpen, setQuery, item, event }) { - onSelect({ setQuery, setIsOpen, event, query: item.label }); - }, - }; - }, -}); - -const querySuggestionsPlugin = createQuerySuggestionsPlugin({ - searchClient, - indexName: '{{querySuggestionsIndexName}}', - getSearchParams() { - return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6 }); - }, - transformSource({ source }) { - return { - ...source, - sourceId: 'querySuggestionsPlugin', - onSelect({ setIsOpen, setQuery, event, item }) { - onSelect({ setQuery, setIsOpen, event, query: item.query }); - }, - getItems(params) { - if (!params.state.query) { - return []; - } - - return source.getItems(params); - }, - }; - }, -}); - -autocomplete({ - container: '#searchbox', + const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: + 'instantsearch', limit: 3, transformSource({ source }) { return { ...source, + onSelect({ setIsOpen, setQuery, item, event }) { onSelect({ setQuery, + setIsOpen, event, query: item.label }); }, }; }, }); const + querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, + indexName: '{{querySuggestionsIndexName}}', getSearchParams() { return + recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6 }); }, + transformSource({ source }) { return { ...source, sourceId: + 'querySuggestionsPlugin', onSelect({ setIsOpen, setQuery, event, item }) { + onSelect({ setQuery, setIsOpen, event, query: item.query }); }, + getItems(params) { if (!params.state.query) { return []; } return + source.getItems(params); }, }; }, }); autocomplete({ container: '#searchbox', {{#if searchPlaceholder}} placeholder: '{{searchPlaceholder}}', {{/if}} - openOnFocus: true, - detachedMediaQuery: 'none', - onSubmit({ state }) { - setInstantSearchUiState({ query: state.query }); - }, - plugins: [recentSearchesPlugin, querySuggestionsPlugin], -}); - -function setInstantSearchUiState(indexUiState) { - search.mainIndex.setIndexUiState({ page: 1, ...indexUiState }); -} - -function onSelect({ setIsOpen, setQuery, event, query }) { - if (isModifierEvent(event)) { - return; - } - - setQuery(query); - setIsOpen(false); - setInstantSearchUiState({ query }); -} - -function isModifierEvent(event) { - const isMiddleClick = event.button === 1; - - return ( - isMiddleClick || - event.altKey || - event.ctrlKey || - event.metaKey || - event.shiftKey - ); -} + openOnFocus: true, detachedMediaQuery: 'none', onSubmit({ state }) { + setInstantSearchUiState({ query: state.query }); }, plugins: + [recentSearchesPlugin, querySuggestionsPlugin], }); function + setInstantSearchUiState(indexUiState) { search.mainIndex.setIndexUiState({ + page: 1, ...indexUiState }); } function onSelect({ setIsOpen, setQuery, event, + query }) { if (isModifierEvent(event)) { return; } setQuery(query); + setIsOpen(false); setInstantSearchUiState({ query }); } function + isModifierEvent(event) { const isMiddleClick = event.button === 1; return ( + isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || + event.shiftKey ); } {{/if}} diff --git a/packages/create-instantsearch-app/src/templates/React InstantSearch/src/App.tsx.hbs b/packages/create-instantsearch-app/src/templates/React InstantSearch/src/App.tsx.hbs index ecec31408e..d7a96f538e 100644 --- a/packages/create-instantsearch-app/src/templates/React InstantSearch/src/App.tsx.hbs +++ b/packages/create-instantsearch-app/src/templates/React InstantSearch/src/App.tsx.hbs @@ -31,8 +31,6 @@ const searchClient = algoliasearch( '{{apiKey}}' ); -const future = { preserveSharedStateOnUnmount: true }; - export function App() { return (
@@ -49,7 +47,7 @@ export function App() {
- +
diff --git a/packages/create-instantsearch-app/src/templates/Vue InstantSearch with Vue 3/src/App.vue b/packages/create-instantsearch-app/src/templates/Vue InstantSearch with Vue 3/src/App.vue index 77676ddea9..c19dd6ec2b 100644 --- a/packages/create-instantsearch-app/src/templates/Vue InstantSearch with Vue 3/src/App.vue +++ b/packages/create-instantsearch-app/src/templates/Vue InstantSearch with Vue 3/src/App.vue @@ -91,7 +91,6 @@ export default { data() { return { searchClient: algoliasearch('{{appId}}', '{{apiKey}}'), - future: { preserveSharedStateOnUnmount: true }, }; }, }; diff --git a/packages/instantsearch-core/src/instantsearch.ts b/packages/instantsearch-core/src/instantsearch.ts index 05074efd4e..a05914ab11 100644 --- a/packages/instantsearch-core/src/instantsearch.ts +++ b/packages/instantsearch-core/src/instantsearch.ts @@ -50,9 +50,7 @@ function defaultCreateURL() { export const INSTANTSEARCH_FUTURE_DEFAULTS: Required< InstantSearchOptions['future'] -> = { - preserveSharedStateOnUnmount: false, -}; +> = {}; /** * The actual implementation of the InstantSearch. This is @@ -151,19 +149,6 @@ See ${createDocumentationLink({ })}` ); - if (__DEV__ && options.future?.preserveSharedStateOnUnmount === undefined) { - // eslint-disable-next-line no-console - console.info(`Starting from the next major version, InstantSearch will change how widgets state is preserved when they are removed. InstantSearch will keep the state of unmounted widgets to be usable by other widgets with the same attribute. - -We recommend setting \`future.preserveSharedStateOnUnmount\` to true to adopt this change today. -To stay with the current behaviour and remove this warning, set the option to false. - -See documentation: ${createDocumentationLink({ - name: 'instantsearch', - })}#widget-param-future - `); - } - this.client = searchClient; this.future = future; this.indexName = indexName; diff --git a/packages/instantsearch-core/src/types/instantsearch.ts b/packages/instantsearch-core/src/types/instantsearch.ts index c03d426a94..2454c28e2e 100644 --- a/packages/instantsearch-core/src/types/instantsearch.ts +++ b/packages/instantsearch-core/src/types/instantsearch.ts @@ -90,18 +90,7 @@ export type InstantSearchOptions< * @default false */ insights?: InsightsProps | boolean; - future?: { - /** - * Changes the way `dispose` is used in InstantSearch lifecycle. - * - * If `false` (by default), each widget unmounting will remove its state as well, even if there are multiple widgets reading that UI State. - * - * If `true`, each widget unmounting will only remove its own state if it's the last of its type. This allows for dynamically adding and removing widgets without losing their state. - * - * @default false - */ - preserveSharedStateOnUnmount?: boolean; // @MAJOR remove option, only keep the "true" behaviour - }; + future?: Record; }; export type InstantSearchStatus = 'idle' | 'loading' | 'stalled' | 'error'; diff --git a/packages/instantsearch-core/src/widgets/__tests__/index-widget.test.ts b/packages/instantsearch-core/src/widgets/__tests__/index-widget.test.ts index 4884a84f38..88fed1a071 100644 --- a/packages/instantsearch-core/src/widgets/__tests__/index-widget.test.ts +++ b/packages/instantsearch-core/src/widgets/__tests__/index-widget.test.ts @@ -603,11 +603,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge hitsPerPage: 5, }); - instance.addWidgets([ - configureTopLevel, - configureSubLevel, - virtualSearchBox({}), - ]); + const searchBox = virtualSearchBox({}); + + instance.addWidgets([configureTopLevel, configureSubLevel, searchBox]); instance.init( createIndexInitOptions({ @@ -628,12 +626,12 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge }) ); - instance.removeWidgets([configureSubLevel]); + instance.removeWidgets([searchBox]); expect(instance.getHelper()!.state).toEqual( new SearchParameters({ index: 'indexName', - query: 'Apple iPhone', + hitsPerPage: 5, distinct: true, }) ); @@ -646,111 +644,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge ).toHaveBeenCalledTimes(2); }); - it('cleans shared refinements when `preserveSharedStateOnUnmount` is unset', () => { + it('preserves shared refinements', () => { const instance = index({ indexName: 'indexName' }); - const instantSearchInstance = createInstantSearch(); - - const refinementList1 = virtualRefinementList({ - attribute: 'brand', - }); - - const refinementList2 = virtualRefinementList({ - attribute: 'brand', - }); - - instance.addWidgets([refinementList1, refinementList2]); - - instance.init( - createIndexInitOptions({ - instantSearchInstance, - parent: null, - }) - ); - - // Simulate a state change - instance.getHelper()!.addDisjunctiveFacetRefinement('brand', 'Apple'); - - expect(instance.getHelper()!.state).toEqual( - new SearchParameters({ - index: 'indexName', - maxValuesPerFacet: 10, - disjunctiveFacets: ['brand'], - disjunctiveFacetsRefinements: { - brand: ['Apple'], - }, - }) - ); - - instance.removeWidgets([refinementList2]); - - expect(instance.getHelper()!.state).toEqual( - new SearchParameters({ - index: 'indexName', - maxValuesPerFacet: 10, - disjunctiveFacets: ['brand'], - disjunctiveFacetsRefinements: { - brand: [], - }, - }) - ); - }); - - it('cleans shared refinements when `preserveSharedStateOnUnmount` is false', () => { - const instance = index({ indexName: 'indexName' }); - const instantSearchInstance = createInstantSearch({ - future: { preserveSharedStateOnUnmount: false }, - }); - - const refinementList1 = virtualRefinementList({ - attribute: 'brand', - }); - - const refinementList2 = virtualRefinementList({ - attribute: 'brand', - }); - - instance.addWidgets([refinementList1, refinementList2]); - - instance.init( - createIndexInitOptions({ - instantSearchInstance, - parent: null, - }) - ); - - // Simulate a state change - instance.getHelper()!.addDisjunctiveFacetRefinement('brand', 'Apple'); - - expect(instance.getHelper()!.state).toEqual( - new SearchParameters({ - index: 'indexName', - maxValuesPerFacet: 10, - disjunctiveFacets: ['brand'], - disjunctiveFacetsRefinements: { - brand: ['Apple'], - }, - }) - ); - - instance.removeWidgets([refinementList2]); - - expect(instance.getHelper()!.state).toEqual( - new SearchParameters({ - index: 'indexName', - maxValuesPerFacet: 10, - disjunctiveFacets: ['brand'], - disjunctiveFacetsRefinements: { - brand: [], - }, - }) - ); - }); - - it('preserves shared refinements when `preserveSharedStateOnUnmount` is true', () => { - const instance = index({ indexName: 'indexName' }); - const instantSearchInstance = createInstantSearch({ - future: { preserveSharedStateOnUnmount: true }, - }); + const instantSearchInstance = createInstantSearch({}); const refinementList1 = virtualRefinementList({ attribute: 'brand', @@ -816,13 +712,15 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge expect(widget.dispose).toHaveBeenCalledTimes(0); }); + const stateBefore = instance.getHelper()!.state; + instance.removeWidgets(widgets); widgets.forEach((widget) => { expect(widget.dispose).toHaveBeenCalledTimes(1); expect(widget.dispose).toHaveBeenCalledWith({ helper: instance.getHelper(), - state: instance.getHelper()!.state, + state: stateBefore, recommendState: instance.getHelper()!.recommendState, parent: instance, }); diff --git a/packages/instantsearch-core/src/widgets/index-widget.ts b/packages/instantsearch-core/src/widgets/index-widget.ts index 6251e44086..8b4fbcfc3f 100644 --- a/packages/instantsearch-core/src/widgets/index-widget.ts +++ b/packages/instantsearch-core/src/widgets/index-widget.ts @@ -438,8 +438,9 @@ export const index = (widgetParams: IndexWidgetParams): IndexWidget => { }); if (localInstantSearchInstance && Boolean(widgets.length)) { - const { cleanedSearchState, cleanedRecommendState } = widgets.reduce( + const { cleanedRecommendState } = widgets.reduce( (states, widget) => { + // @MAJOR remove the "cleanup" part of the dispose method // the `dispose` method exists at this point we already assert it const next = widget.dispose!({ helper: helper!, @@ -462,24 +463,12 @@ export const index = (widgetParams: IndexWidgetParams): IndexWidget => { } ); - const newState = localInstantSearchInstance.future - .preserveSharedStateOnUnmount - ? getLocalWidgetsSearchParameters(localWidgets, { - uiState: localUiState, - initialSearchParameters: new algoliasearchHelper.SearchParameters( - { - index: this.getIndexName(), - } - ), - }) - : getLocalWidgetsSearchParameters(localWidgets, { - uiState: getLocalWidgetsUiState(localWidgets, { - searchParameters: cleanedSearchState, - helper: helper!, - }), - initialSearchParameters: cleanedSearchState, - }); - + const newState = getLocalWidgetsSearchParameters(localWidgets, { + uiState: localUiState, + initialSearchParameters: new algoliasearchHelper.SearchParameters({ + index: this.getIndexName(), + }), + }); localUiState = getLocalWidgetsUiState(localWidgets, { searchParameters: newState, helper: helper!, From d0288d84b2f12f8259862d8223cc66af672405ad Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Tue, 24 Dec 2024 16:30:28 +0100 Subject: [PATCH 2/2] fix formatting --- .../templates/InstantSearch.js/src/app.js.hbs | 235 ++++++++++++------ 1 file changed, 153 insertions(+), 82 deletions(-) diff --git a/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs b/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs index f539b4578a..b4b95bc614 100644 --- a/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs +++ b/packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs @@ -1,105 +1,176 @@ const { algoliasearch, instantsearch } = window; {{#if flags.autocomplete}} - const { autocomplete } = window['@algolia/autocomplete-js']; const { - createLocalStorageRecentSearchesPlugin } = window[ - '@algolia/autocomplete-plugin-recent-searches' ]; const { - createQuerySuggestionsPlugin } = window[ - '@algolia/autocomplete-plugin-query-suggestions' ]; +const { autocomplete } = window['@algolia/autocomplete-js']; +const { createLocalStorageRecentSearchesPlugin } = window[ + '@algolia/autocomplete-plugin-recent-searches' +]; +const { createQuerySuggestionsPlugin } = window[ + '@algolia/autocomplete-plugin-query-suggestions' +]; {{/if}} -const searchClient = algoliasearch('{{appId}}', '{{apiKey}}'); const search = -instantsearch({ indexName: '{{indexName}}', searchClient, -{{#if flags.insights}}insights: true,{{/if}} +const searchClient = algoliasearch('{{appId}}', '{{apiKey}}'); + +const search = instantsearch({ + indexName: '{{indexName}}', + searchClient, + {{#if flags.insights}}insights: true,{{/if}} }); {{#if flags.autocomplete}} - const virtualSearchBox = instantsearch.connectors.connectSearchBox(() => {}); +const virtualSearchBox = instantsearch.connectors.connectSearchBox(() => {}); {{/if}} search.addWidgets([ -{{#unless flags.autocomplete}} - instantsearch.widgets.searchBox({ container: '#searchbox', - {{#if searchPlaceholder}} + {{#unless flags.autocomplete}} + instantsearch.widgets.searchBox({ + container: '#searchbox', + {{#if searchPlaceholder}} placeholder: '{{searchPlaceholder}}', - {{/if}} + {{/if}} }), -{{else}} + {{else}} virtualSearchBox({}), -{{/unless}} -instantsearch.widgets.hits({ container: '#hits', -{{#if attributesToDisplay}} - templates: { item: (hit, { html, components }) => html` -
- {{#if imageAttribute}} - ${ + {{/unless}} + instantsearch.widgets.hits({ + container: '#hits', + {{#if attributesToDisplay}} + templates: { + item: (hit, { html, components }) => html` +
+ {{#if imageAttribute}} + ${ + {{/if}} +
+

${components.Highlight({hit, attribute: "{{attributesToDisplay.[0]}}"})}

+ {{#each attributesToDisplay}} + {{#unless @first}} +

${components.Highlight({hit, attribute: "{{this}}"})}

+ {{/unless}} + {{/each}} +
+
+`, + }, {{/if}} -
-

${components.Highlight({hit, attribute: "{{attributesToDisplay.0}}"})}

- {{#each attributesToDisplay}} - {{#unless @first}} -

${components.Highlight({hit, attribute: "{{this}}"})}

- {{/unless}} + }), + instantsearch.widgets.configure({ + hitsPerPage: 8, + }), + {{#if flags.dynamicWidgets}} + instantsearch.widgets.dynamicWidgets({ + container: '#dynamic-widgets', + fallbackWidget({ container, attribute }) { + return instantsearch.widgets.panel({ templates: { header: () => attribute } })( + instantsearch.widgets.refinementList + )({ + container, + attribute, + }); + }, + widgets: [ + {{#each attributesForFaceting}} + container => + instantsearch.widgets.panel({ + templates: { header: () => '{{this}}' }, + })(instantsearch.widgets.refinementList)({ + container, + attribute: '{{this}}', + }), {{/each}} -
-
- `, }, -{{/if}} -}), instantsearch.widgets.configure({ hitsPerPage: 8, }), -{{#if flags.dynamicWidgets}} - instantsearch.widgets.dynamicWidgets({ container: '#dynamic-widgets', - fallbackWidget({ container, attribute }) { return - instantsearch.widgets.panel({ templates: { header: () => attribute } })( - instantsearch.widgets.refinementList )({ container, attribute, }); }, widgets: - [ - {{#each attributesForFaceting}} - container => instantsearch.widgets.panel({ templates: { header: () => '{{this}}' - }, })(instantsearch.widgets.refinementList)({ container, attribute: '{{this}}', - }), - {{/each}} - ], }), -{{else}} + ], + }), + {{else}} {{#each attributesForFaceting}} - instantsearch.widgets.panel({ templates: { header: () => '{{this}}' }, - })(instantsearch.widgets.refinementList)({ container: '#{{this}}-list', - attribute: '{{this}}', }), + instantsearch.widgets.panel({ + templates: { header: () => '{{this}}' }, + })(instantsearch.widgets.refinementList)({ + container: '#{{this}}-list', + attribute: '{{this}}', + }), {{/each}} -{{/if}} -instantsearch.widgets.pagination({ container: '#pagination', }), ]); + {{/if}} + instantsearch.widgets.pagination({ + container: '#pagination', + }), +]); + search.start(); {{#if flags.autocomplete}} - const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: - 'instantsearch', limit: 3, transformSource({ source }) { return { ...source, - onSelect({ setIsOpen, setQuery, item, event }) { onSelect({ setQuery, - setIsOpen, event, query: item.label }); }, }; }, }); const - querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, - indexName: '{{querySuggestionsIndexName}}', getSearchParams() { return - recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6 }); }, - transformSource({ source }) { return { ...source, sourceId: - 'querySuggestionsPlugin', onSelect({ setIsOpen, setQuery, event, item }) { - onSelect({ setQuery, setIsOpen, event, query: item.query }); }, - getItems(params) { if (!params.state.query) { return []; } return - source.getItems(params); }, }; }, }); autocomplete({ container: '#searchbox', +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'instantsearch', + limit: 3, + transformSource({ source }) { + return { + ...source, + onSelect({ setIsOpen, setQuery, item, event }) { + onSelect({ setQuery, setIsOpen, event, query: item.label }); + }, + }; + }, +}); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: '{{querySuggestionsIndexName}}', + getSearchParams() { + return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6 }); + }, + transformSource({ source }) { + return { + ...source, + sourceId: 'querySuggestionsPlugin', + onSelect({ setIsOpen, setQuery, event, item }) { + onSelect({ setQuery, setIsOpen, event, query: item.query }); + }, + getItems(params) { + if (!params.state.query) { + return []; + } + + return source.getItems(params); + }, + }; + }, +}); + +autocomplete({ + container: '#searchbox', {{#if searchPlaceholder}} placeholder: '{{searchPlaceholder}}', {{/if}} - openOnFocus: true, detachedMediaQuery: 'none', onSubmit({ state }) { - setInstantSearchUiState({ query: state.query }); }, plugins: - [recentSearchesPlugin, querySuggestionsPlugin], }); function - setInstantSearchUiState(indexUiState) { search.mainIndex.setIndexUiState({ - page: 1, ...indexUiState }); } function onSelect({ setIsOpen, setQuery, event, - query }) { if (isModifierEvent(event)) { return; } setQuery(query); - setIsOpen(false); setInstantSearchUiState({ query }); } function - isModifierEvent(event) { const isMiddleClick = event.button === 1; return ( - isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || - event.shiftKey ); } -{{/if}} + openOnFocus: true, + detachedMediaQuery: 'none', + onSubmit({ state }) { + setInstantSearchUiState({ query: state.query }); + }, + plugins: [recentSearchesPlugin, querySuggestionsPlugin], +}); + +function setInstantSearchUiState(indexUiState) { + search.mainIndex.setIndexUiState({ page: 1, ...indexUiState }); +} + +function onSelect({ setIsOpen, setQuery, event, query }) { + if (isModifierEvent(event)) { + return; + } + + setQuery(query); + setIsOpen(false); + setInstantSearchUiState({ query }); +} + +function isModifierEvent(event) { + const isMiddleClick = event.button === 1; + + return ( + isMiddleClick || + event.altKey || + event.ctrlKey || + event.metaKey || + event.shiftKey + ); +} +{{/if}} \ No newline at end of file