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": "251 kB"
"maxSize": "251.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 @@ -79,6 +79,10 @@ export type UsePropGetters<TItem extends BaseHit> = (params: {
* (e.g., when all sources are empty and there's no custom content to show).
*/
shouldHidePanel?: boolean;
/**
* Whether the input should be focused and the panel open initially.
*/
autoFocus?: boolean;
}) => {
getInputProps: GetInputProps;
getItemProps: GetItemProps;
Expand Down Expand Up @@ -115,11 +119,12 @@ export function createAutocompletePropGetters({
placeholder,
isDetached = false,
shouldHidePanel = false,
autoFocus = false,
}: Parameters<UsePropGetters<TItem>>[0]): ReturnType<UsePropGetters<TItem>> {
const getElementId = createGetElementId(useId());
const inputRef = useRef<HTMLInputElement>(null);
const rootRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [isOpen, setIsOpen] = useState(autoFocus);
const [activeDescendant, setActiveDescendant] = useState<
string | undefined
>(undefined);
Expand Down Expand Up @@ -207,6 +212,7 @@ export function createAutocompletePropGetters({
id: getElementId('input'),
ref: inputRef,
role: 'combobox',
autoFocus,
'aria-autocomplete': 'list',
'aria-expanded': isOpen,
'aria-haspopup': 'grid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ type RendererParams<TItem extends BaseHit> = {
| 'showQuerySuggestions'
| 'showPromptSuggestions'
| 'placeholder'
| 'autofocus'
> & {
showRecent:
| Exclude<AutocompleteWidgetParams<TItem>['showRecent'], boolean>
Expand Down Expand Up @@ -352,6 +353,7 @@ type AutocompleteWrapperProps<TItem extends BaseHit> = Pick<
| 'showQuerySuggestions'
| 'showPromptSuggestions'
| 'placeholder'
| 'autofocus'
| 'detachedMediaQuery'
| 'translations'
> &
Expand All @@ -372,6 +374,7 @@ function AutocompleteWrapper<TItem extends BaseHit>({
showPromptSuggestions,
templates,
placeholder,
autofocus,
detachedMediaQuery,
translations,
}: AutocompleteWrapperProps<TItem>) {
Expand Down Expand Up @@ -666,6 +669,7 @@ function AutocompleteWrapper<TItem extends BaseHit>({
placeholder,
isDetached,
shouldHidePanel: shouldHideEmptyPanel,
autoFocus: autofocus,
});

// Open panel and focus input when modal opens
Expand Down Expand Up @@ -1015,6 +1019,11 @@ type AutocompleteWidgetParams<TItem extends BaseHit> = {
*/
placeholder?: string;

/**
* Whether the input should be focused and the panel open initially.
*/
autofocus?: boolean;

/**
* Media query to enable detached (mobile) mode.
* When the media query matches, the autocomplete switches to a full-screen overlay.
Expand Down Expand Up @@ -1053,6 +1062,7 @@ export function EXPERIMENTAL_autocomplete<TItem extends BaseHit = BaseHit>(
transformItems,
cssClasses: userCssClasses = {},
placeholder,
autofocus,
detachedMediaQuery,
translations: userTranslations = {},
} = widgetParams || {};
Expand Down Expand Up @@ -1200,6 +1210,7 @@ export function EXPERIMENTAL_autocomplete<TItem extends BaseHit = BaseHit>(
showQuerySuggestions,
showPromptSuggestions,
placeholder,
autofocus,
detachedMediaQuery,
translations,
renderState: {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-instantsearch-router-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"test:exports": "node ./__tests__/module/is-es-module.mjs && node ./__tests__/module/is-cjs-module.cjs",
"test:e2e": "playwright test",
"test:e2e:saucelabs": "playwright test",
"watch:es": "yarn --silent build:es:base --watch"
"watch:es": "BABEL_ENV=es,rollup BUILD_FORMAT=esm rollup -c rollup.config.mjs --watch"
Copy link
Member Author

Choose a reason for hiding this comment

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

Unrelated to the PR but won't hurt, it fixes watch:es since build:es:base doesn't exist anymore. Let me know if you'd rather I do this in its own PR.

},
"dependencies": {
"@swc/helpers": "0.5.18",
Expand Down
6 changes: 6 additions & 0 deletions packages/react-instantsearch/src/widgets/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@ export type AutocompleteProps<TItem extends BaseHit> = ComponentProps<'div'> & {
searchParameters?: AutocompleteSearchParameters;
classNames?: Partial<AutocompleteClassNames>;
placeholder?: string;
/**
* Whether the input should be focused and the panel open initially.
*/
autoFocus?: boolean;
/**
* Media query to enable detached (mobile) mode.
* When the media query matches, the autocomplete switches to a full-screen overlay.
Expand Down Expand Up @@ -597,6 +601,7 @@ function InnerAutocomplete<TItem extends BaseHit = BaseHit>({
chatRenderState,
transformItems,
placeholder,
autoFocus,
detachedMediaQuery = DEFAULT_DETACHED_MEDIA_QUERY,
translations,
classNames,
Expand Down Expand Up @@ -766,6 +771,7 @@ function InnerAutocomplete<TItem extends BaseHit = BaseHit>({
placeholder,
isDetached,
shouldHidePanel: shouldHideEmptyPanel,
autoFocus,
});

// Open panel and focus input when modal opens
Expand Down
60 changes: 60 additions & 0 deletions tests/common/widgets/autocomplete/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,66 @@ export function createOptionsTests(
expect(input).toHaveFocus();
});

test('focuses the input and opens the panel when autofocus is set', async () => {
const searchClient = createMockedSearchClient(
createMultiSearchResponse(
createSingleSearchResponse({
index: 'indexName',
hits: [{ objectID: '1', name: 'Item 1' }],
})
)
);

await setup({
instantSearchOptions: {
indexName: 'indexName',
searchClient,
},
widgetParams: {
javascript: {
autofocus: true,
indices: [
{
indexName: 'indexName',
templates: {
item: (props) => props.item.name,
},
},
],
},
react: {
autoFocus: true,
indices: [
{
indexName: 'indexName',
itemComponent: (props) => props.item.name,
},
],
},
vue: {},
},
});

await act(async () => {
await wait(0);
});

const input = screen.getByRole('combobox', { name: /submit/i });

try {
// For IS.js, jsdom does not implement autofocus so we test the attribute
expect(input).toHaveAttribute('autofocus');
} catch {
// For React, it doesn't set the `autofocus` attribute but polyfills it
// eslint-disable-next-line jest/no-conditional-expect
expect(input).toHaveFocus();
}

// Panel should be open initially
const panel = document.querySelector('.ais-AutocompletePanel');
expect(panel).not.toHaveAttribute('hidden');
});

test('applies query suggestions when clicking on the fill action button', async () => {
const searchClient = createMockedSearchClient(
createMultiSearchResponse(
Expand Down