Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "189 kB"
"maxSize": "189.25 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export type AutocompleteIndexConfig<TItem extends BaseHit> = {
getURL?: (item: TItem) => string;
onSelect?: (params: {
item: TItem;
getQuery: () => string;
getURL: () => string;
query: string;
setQuery: (query: string) => void;
url?: string;
}) => void;
};

Expand Down Expand Up @@ -48,6 +48,7 @@ type UsePropGetters<TItem extends BaseHit> = (params: {
}>;
indicesConfig: Array<AutocompleteIndexConfig<TItem>>;
onRefine: (query: string) => void;
onSelect: NonNullable<AutocompleteIndexConfig<TItem>['onSelect']>;
}) => {
getInputProps: GetInputProps;
getItemProps: GetItemProps;
Expand All @@ -66,6 +67,7 @@ export function createAutocompletePropGetters({
indices,
indicesConfig,
onRefine,
onSelect: globalOnSelect,
}: Parameters<UsePropGetters<TItem>>[0]): ReturnType<UsePropGetters<TItem>> {
const getElementId = createGetElementId(useId());
const rootRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -117,12 +119,13 @@ export function createAutocompletePropGetters({
if (actualActiveDescendant && items.has(actualActiveDescendant)) {
const {
item,
config: { onSelect, getQuery, getURL },
config: { onSelect: indexOnSelect, getQuery, getURL },
} = items.get(actualActiveDescendant)!;
onSelect?.({
const actualOnSelect = indexOnSelect ?? globalOnSelect;
actualOnSelect({
item,
getQuery: () => getQuery?.(item) ?? '',
getURL: () => getURL?.(item) ?? '',
query: getQuery?.(item) ?? '',
url: getURL?.(item),
setQuery: (query) => onRefine(query),
});
setActiveDescendant(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {
import type { PreparedTemplateProps } from '../../lib/templating';
import type {
BaseHit,
IndexUiState,
IndexWidget,
Renderer,
RendererOptions,
Expand Down Expand Up @@ -87,16 +88,15 @@ type RendererParams<TItem extends BaseHit> = {
instanceId: number;
containerNode: HTMLElement;
indicesConfig: Array<IndexConfig<TItem>>;
cssClasses: AutocompleteCSSClasses;
renderState: {
indexTemplateProps: Array<
PreparedTemplateProps<NonNullable<IndexConfig<TItem>['templates']>>
>;
isolatedIndex: IndexWidget | undefined;
targetIndex: IndexWidget | undefined;
};
templates: AutocompleteTemplates<TItem>;
};
} & Pick<AutocompleteWidgetParams<TItem>, 'getSearchPageURL' | 'onSelect'> &
Required<Pick<AutocompleteWidgetParams<TItem>, 'cssClasses' | 'templates'>>;

const createRenderer = <TItem extends BaseHit>(
params: RendererParams<TItem>
Expand Down Expand Up @@ -133,20 +133,33 @@ const createRenderer = <TItem extends BaseHit>(

type AutocompleteWrapperProps<TItem extends BaseHit> = Pick<
RendererParams<TItem>,
'indicesConfig' | 'cssClasses' | 'templates' | 'renderState'
| 'indicesConfig'
| 'getSearchPageURL'
| 'onSelect'
| 'cssClasses'
| 'templates'
| 'renderState'
> &
Pick<AutocompleteRenderState, 'indices' | 'refine'> &
RendererOptions<Partial<AutocompleteWidgetParams<TItem>>>;

function AutocompleteWrapper<TItem extends BaseHit>({
indicesConfig,
indices,
getSearchPageURL,
onSelect: userOnSelect,
refine,
cssClasses,
renderState,
instantSearchInstance,
}: AutocompleteWrapperProps<TItem>) {
const { isolatedIndex, targetIndex } = renderState;
const isSearchPage =
targetIndex
?.getWidgets()
.some(({ $$type }) =>
['ais.hits', 'ais.infiniteHits'].includes($$type)
) ?? false;
const { getInputProps, getItemProps, getPanelProps, getRootProps } =
usePropGetters({
indices,
Expand All @@ -161,6 +174,23 @@ function AutocompleteWrapper<TItem extends BaseHit>({
[isolatedIndex!.getIndexId()]: { query },
}));
},
onSelect:
userOnSelect ??
(({ query, setQuery, url }) => {
if (url) {
window.location.href = url;
return;
}

if (!isSearchPage && typeof getSearchPageURL !== 'undefined') {
const indexUiState =
instantSearchInstance.getUiState()[targetIndex!.getIndexId()];
Comment on lines +186 to +187
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a possibility this can cause an indexing error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targetIndex is at least the mainIndex (the parent of the isolated index of an autocomplete), so it should not cause an error. It just is typed as potentially undefined because the renderState needs to be declared before we have access to instantSearchInstance.

window.location.href = getSearchPageURL({ ...indexUiState, query });
return;
}

setQuery(query);
}),
});

const query =
Expand Down Expand Up @@ -263,10 +293,14 @@ type AutocompleteWidgetParams<TItem extends BaseHit> = {
showSuggestions?: Partial<
Pick<
IndexConfig<{ query: string }>,
'indexName' | 'templates' | 'cssClasses'
'indexName' | 'getURL' | 'templates' | 'cssClasses'
>
>;

getSearchPageURL?: (nextUiState: IndexUiState) => string;

onSelect?: AutocompleteIndexConfig<TItem>['onSelect'];

/**
* Templates to use for the widget.
*/
Expand All @@ -292,6 +326,8 @@ export function EXPERIMENTAL_autocomplete<TItem extends BaseHit = BaseHit>(
escapeHTML,
indices = [],
showSuggestions,
getSearchPageURL,
onSelect,
templates = {},
cssClasses: userCssClasses = {},
} = widgetParams || {};
Expand Down Expand Up @@ -328,9 +364,7 @@ export function EXPERIMENTAL_autocomplete<TItem extends BaseHit = BaseHit>(
),
},
getQuery: (item) => item.query,
onSelect({ getQuery, setQuery }) {
setQuery(getQuery());
},
getURL: showSuggestions.getURL as unknown as IndexConfig<TItem>['getURL'],
});
}

Expand All @@ -339,6 +373,8 @@ export function EXPERIMENTAL_autocomplete<TItem extends BaseHit = BaseHit>(
instanceId,
containerNode,
indicesConfig,
getSearchPageURL,
onSelect,
cssClasses,
renderState: {
indexTemplateProps: [],
Expand Down
50 changes: 43 additions & 7 deletions packages/react-instantsearch/src/widgets/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import React, {
useRef,
useState,
} from 'react';
import { Index, useAutocomplete, useSearchBox } from 'react-instantsearch-core';
import {
Index,
useAutocomplete,
useInstantSearch,
useSearchBox,
} from 'react-instantsearch-core';

import { AutocompleteSearch } from '../components/AutocompleteSearch';

Expand All @@ -25,7 +30,7 @@ import type {
Pragma,
AutocompleteClassNames,
} from 'instantsearch-ui-components';
import type { BaseHit, Hit } from 'instantsearch.js';
import type { BaseHit, Hit, IndexUiState } from 'instantsearch.js';
import type { ComponentProps } from 'react';

const Autocomplete = createAutocompleteComponent({
Expand Down Expand Up @@ -71,9 +76,11 @@ export type AutocompleteProps<TItem extends BaseHit> = ComponentProps<'div'> & {
showSuggestions?: Partial<
Pick<
IndexConfig<{ query: string }>,
'indexName' | 'itemComponent' | 'classNames'
'indexName' | 'getURL' | 'itemComponent' | 'classNames'
>
>;
getSearchPageURL?: (nextUiState: IndexUiState) => string;
onSelect?: AutocompleteIndexConfig<TItem>['onSelect'];
classNames?: Partial<AutocompleteClassNames>;
};

Expand All @@ -83,13 +90,16 @@ type InnerAutocompleteProps<TItem extends BaseHit> = Omit<
> & {
indicesConfig: Array<IndexConfig<TItem>>;
refineSearchBox: ReturnType<typeof useSearchBox>['refine'];
indexUiState: IndexUiState;
isSearchPage: boolean;
};

export function EXPERIMENTAL_Autocomplete<TItem extends BaseHit = BaseHit>({
indices = [],
showSuggestions,
...props
}: AutocompleteProps<TItem>) {
const { indexUiState, indexRenderState } = useInstantSearch();
const { refine } = useSearchBox(
{},
{ $$type: 'ais.autocomplete', $$widgetType: 'ais.autocomplete' }
Expand All @@ -116,12 +126,17 @@ export function EXPERIMENTAL_Autocomplete<TItem extends BaseHit = BaseHit>({
),
},
getQuery: (item) => item.query,
onSelect: ({ getQuery, setQuery }) => {
setQuery(getQuery());
},
getURL: showSuggestions.getURL as unknown as IndexConfig<TItem>['getURL'],
});
}

const isSearchPage = useMemo(
() =>
typeof indexRenderState.hits !== 'undefined' ||
typeof indexRenderState.infiniteHits !== 'undefined',
[indexRenderState]
);
Comment on lines +133 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to also get this info from the targetIndex in react?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now this is defined in the JS widget, and retrieving the index instance in React is not as easy (useIndexContext() is not publicly exported for instance).

Could be worth having it returned by the connector in a future update though.


return (
<Fragment>
<Index EXPERIMENTAL_isolated>
Expand All @@ -132,6 +147,8 @@ export function EXPERIMENTAL_Autocomplete<TItem extends BaseHit = BaseHit>({
{...props}
indicesConfig={indicesConfig}
refineSearchBox={refine}
indexUiState={indexUiState}
isSearchPage={isSearchPage}
/>
</Index>
</Fragment>
Expand All @@ -141,17 +158,36 @@ export function EXPERIMENTAL_Autocomplete<TItem extends BaseHit = BaseHit>({
function InnerAutocomplete<TItem extends BaseHit = BaseHit>({
indicesConfig,
refineSearchBox,
getSearchPageURL,
onSelect: userOnSelect,
indexUiState,
isSearchPage,
...props
}: InnerAutocompleteProps<TItem>) {
const { indices, refine: refineAutocomplete } = useAutocomplete();
const { getInputProps, getItemProps, getPanelProps, getRootProps } =
usePropGetters<TItem>({
indices,
indicesConfig,
onRefine: (query: string) => {
onRefine: (query) => {
refineAutocomplete(query);
refineSearchBox(query);
},
onSelect:
userOnSelect ??
(({ query, setQuery, url }) => {
if (url) {
window.location.href = url;
return;
}

if (!isSearchPage && typeof getSearchPageURL !== 'undefined') {
window.location.href = getSearchPageURL({ ...indexUiState, query });
return;
}

setQuery(query);
}),
});

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ function Widget<TWidget extends SingleWidget>({
case 'LookingSimilar': {
return <widget.Component objectIDs={['1']} {...props} />;
}
case 'EXPERIMENTAL_Autocomplete': {
// @ts-expect-error - incorrectly expects onSelect from ComponentProps<'div'>
return <widget.Component {...props} />;
}
default: {
return <widget.Component {...props} />;
}
Expand Down