Skip to content
Draft
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
8 changes: 4 additions & 4 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@
},
{
"path": "packages/vue-instantsearch/vue2/umd/index.js",
"maxSize": "106 kB"
"maxSize": "110.5 kB"
},
{
"path": "packages/vue-instantsearch/vue3/umd/index.js",
"maxSize": "106 kB"
"maxSize": "110.5 kB"
},
{
"path": "packages/vue-instantsearch/vue2/cjs/index.js",
"maxSize": "23.5 kB"
"maxSize": "25 kB"
},
{
"path": "packages/vue-instantsearch/vue3/cjs/index.js",
"maxSize": "24 kB"
"maxSize": "25.5 kB"
},
{
"path": "./packages/instantsearch.css/themes/algolia.css",
Expand Down
22 changes: 20 additions & 2 deletions packages/vue-instantsearch/src/__tests__/common-widgets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
AisRelatedProducts,
AisChat,
AisChatTrigger,
AisAutocomplete,
} from '../instantsearch';
import { renderCompat } from '../util/vue-compat';

Expand Down Expand Up @@ -649,8 +650,20 @@ const testSetups = {

await nextTick();
},
createAutocompleteWidgetTests() {
throw new Error('Autocomplete is not supported in Vue InstantSearch');
async createAutocompleteWidgetTests({ instantSearchOptions, widgetParams }) {
mountApp(
{
render: renderCompat((h) =>
h(AisInstantSearch, { props: instantSearchOptions }, [
h(AisAutocomplete, { key: 'autocomplete', props: widgetParams }),
h(GlobalErrorSwallower, { key: 'errors' }),
])
),
},
document.body.appendChild(document.createElement('div'))
);

await nextTick();
},
createFilterSuggestionsWidgetTests() {
throw new Error('FilterSuggestions is not supported in Vue InstantSearch');
Expand Down Expand Up @@ -713,6 +726,11 @@ const testOptions = {
createChatWidgetTests: {
skippedTests: { 'Chat widget common tests': true },
},
// The rich AisAutocomplete widget is implemented and covered by component
// tests. The shared common suite stays skipped until its `vue` widgetParams
// variant is extended to carry `indices`/`showQuerySuggestions`/templates
// (it's currently `{ requiresSearch?: boolean }`, so most tests can't pass
// Vue the config they assert on). Tracked as a follow-up.
createAutocompleteWidgetTests: {
skippedTests: { 'Autocomplete widget common tests': true },
},
Expand Down
82 changes: 82 additions & 0 deletions packages/vue-instantsearch/src/components/Autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createSuitMixin } from '../mixins/suit';
import { getScopedSlot, renderCompat } from '../util/vue-compat';

import AutocompleteHeadless from './AutocompleteHeadless.vue';
import AutocompleteWidget from './AutocompleteWidget';

/**
* `AisAutocomplete` dispatches between two implementations, so the rich
* autocomplete widget is the default while the legacy headless slot API stays
* backward-compatible:
*
* - When a default (scoped) slot is provided, it renders the legacy headless
* component unchanged — existing custom-rendering usages keep working with
* the same `{ refine, currentRefinement, indices }` slot props.
* - Otherwise it renders the new rich Autocomplete widget (search box + panel +
* indices).
*
* The two can't be a single widget because they register different widget
* graphs (the headless component wraps `connectAutocomplete` at the page
* scope; the rich widget owns an isolated `<AisIndex>` subtree).
*/
export default {
name: 'AisAutocomplete',
mixins: [createSuitMixin({ name: 'Autocomplete' })],
props: {
// Legacy headless props
escapeHTML: { type: Boolean, required: false, default: undefined },
// Shared
requiresSearch: { type: Boolean, required: false, default: undefined },
// Rich widget props
indices: { type: Array, required: false, default: undefined },
showQuerySuggestions: {
type: Object,
required: false,
default: undefined,
},
searchParameters: { type: Object, required: false, default: undefined },
autofocus: { type: Boolean, required: false, default: undefined },
placeholder: { type: String, required: false, default: undefined },
transformItems: { type: Function, required: false, default: undefined },
},
computed: {
hasDefaultSlot() {
return Boolean(
getScopedSlot(this, 'default') || (this.$slots && this.$slots.default)
);
},
// Proxy the inner widget so widget introspection (and the conventions
// test) can read it off `AisAutocomplete`.
widget() {
return this.$refs.inner && this.$refs.inner.widget;
},
},
render: renderCompat(function (h) {
if (this.hasDefaultSlot) {
return h(AutocompleteHeadless, {
props: {
escapeHTML: this.escapeHTML,
requiresSearch: this.requiresSearch,
},
// `$scopedSlots` is Vue 2; `$slots` is Vue 3. `renderCompat`'s `h`
// normalizes the `scopedSlots` data into the right shape per version.
scopedSlots: this.$scopedSlots || this.$slots,
ref: 'inner',
});
}

return h(AutocompleteWidget, {
props: {
indices: this.indices,
showQuerySuggestions: this.showQuerySuggestions,
searchParameters: this.searchParameters,
requiresSearch: this.requiresSearch,
autofocus: this.autofocus,
placeholder: this.placeholder,
transformItems: this.transformItems,
classNames: this.classNames,
},
ref: 'inner',
});
}),
};
Loading
Loading