diff --git a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx index 099d7d620ee..f50890b2482 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx +++ b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx @@ -34,7 +34,12 @@ import { StorageItems, } from '../../../utils/local-storage-utils'; import {updateBreakpoints} from '../../../utils/replace-breakpoint'; -import {isFocusingOut, once, randomID} from '../../../utils/utils'; +import { + isFocusingOut, + once, + randomID, + spreadProperties, +} from '../../../utils/utils'; import {SearchBoxWrapper} from '../../common/search-box/search-box-wrapper'; import {SearchTextArea} from '../../common/search-box/search-text-area'; import {SubmitButton} from '../../common/search-box/submit-button'; @@ -330,11 +335,11 @@ export class AtomicCommerceSearchBox private get suggestionBindings(): SearchBoxSuggestionsBindings< SearchBox | StandaloneSearchBox > { - return { - ...this.bindings, - ...this.suggestionManager.partialSuggestionBindings, - ...this.partialSuggestionBindings, - }; + return spreadProperties( + this.bindings, + this.suggestionManager.partialSuggestionBindings, + this.partialSuggestionBindings + ); } private get partialSuggestionBindings(): Pick< @@ -345,14 +350,38 @@ export class AtomicCommerceSearchBox | 'numberOfQueries' | 'clearFilters' > { - return { - ...this.bindings, - id: this.id, - isStandalone: () => !!this.redirectionUrl, - searchBoxController: () => this.searchBox, - numberOfQueries: this.numberOfQueries, - clearFilters: this.clearFilters, - }; + return Object.defineProperties( + {...this.bindings}, + { + id: { + get: () => this.id, + enumerable: true, + }, + searchBoxController: { + get: () => this.searchBox, + enumerable: true, + }, + isStandalone: { + get: () => !!this.redirectionUrl, + enumerable: true, + }, + numberOfQueries: { + get: () => this.numberOfQueries, + enumerable: true, + }, + clearFilters: { + get: () => this.clearFilters, + enumerable: true, + }, + } + ) as unknown as Pick< + SearchBoxSuggestionsBindings, + | 'id' + | 'isStandalone' + | 'searchBoxController' + | 'numberOfQueries' + | 'clearFilters' + >; } private get searchBoxOptions(): SearchBoxOptions { diff --git a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx index 9f1c0646997..a05ad2eabae 100644 --- a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx +++ b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx @@ -178,10 +178,10 @@ export class AtomicCommerceSearchBoxInstantProducts content: , onSelect: () => { this.bindings.clearSuggestions(); - this.bindings - .searchBoxController() - .updateText(this.instantProducts.state.query); - this.bindings.searchBoxController().submit(); + this.bindings.searchBoxController.updateText( + this.instantProducts.state.query + ); + this.bindings.searchBoxController.submit(); }, }); } @@ -228,7 +228,7 @@ export class AtomicCommerceSearchBoxInstantProducts private onSuggestedQueryChange() { if ( !this.bindings.getSuggestionElements().length && - !this.bindings.searchBoxController().state.value + !this.bindings.searchBoxController.state.value ) { console.warn( "There doesn't seem to be any query suggestions configured. Make sure to include either an atomic-commerce-search-box-query-suggestions or atomic-commerce-search-box-recent-queries in your search box in order to see some instant products." diff --git a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx index 929cedd1e13..cabf1fc0994 100644 --- a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx +++ b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx @@ -87,16 +87,15 @@ export class AtomicCommerceSearchBoxQuerySuggestions { } private renderItems(): SearchBoxSuggestionElement[] { - const hasQuery = this.bindings.searchBoxController().state.value !== ''; + const hasQuery = this.bindings.searchBoxController.state.value !== ''; const max = hasQuery ? this.maxWithQuery : this.maxWithoutQuery; - return this.bindings - .searchBoxController() - .state.suggestions.slice(0, max) + return this.bindings.searchBoxController.state.suggestions + .slice(0, max) .map((suggestion) => this.renderItem(suggestion)); } private renderItem(suggestion: Suggestion) { - const hasQuery = this.bindings.searchBoxController().state.value !== ''; + const hasQuery = this.bindings.searchBoxController.state.value !== ''; const partialItem = getPartialSearchBoxSuggestionElement( suggestion, this.bindings.i18n @@ -115,9 +114,7 @@ export class AtomicCommerceSearchBoxQuerySuggestions { ), onSelect: () => { - this.bindings - .searchBoxController() - .selectSuggestion(suggestion.rawValue); + this.bindings.searchBoxController.selectSuggestion(suggestion.rawValue); }, }; } diff --git a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx index e788b079d8f..4349bb2839e 100644 --- a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx +++ b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx @@ -123,7 +123,7 @@ export class AtomicCommerceSearchBoxRecentQueries { return []; } - const query = this.bindings.searchBoxController().state.value; + const query = this.bindings.searchBoxController.state.value; const hasQuery = query !== ''; const max = hasQuery ? this.maxWithQuery : this.maxWithoutQuery; const filteredQueries = this.recentQueriesList.state.queries @@ -157,7 +157,7 @@ export class AtomicCommerceSearchBoxRecentQueries { } private renderItem(value: string): SearchBoxSuggestionElement { - const query = this.bindings.searchBoxController().state.value; + const query = this.bindings.searchBoxController.state.value; const partialItem = getPartialRecentQueryElement(value, this.bindings.i18n); return { ...partialItem, @@ -169,9 +169,9 @@ export class AtomicCommerceSearchBoxRecentQueries { ), onSelect: () => { - if (this.bindings.isStandalone()) { - this.bindings.searchBoxController().updateText(value); - this.bindings.searchBoxController().submit(); + if (this.bindings.isStandalone) { + this.bindings.searchBoxController.updateText(value); + this.bindings.searchBoxController.submit(); return; } diff --git a/packages/atomic/src/components/common/suggestions/suggestions-common.ts b/packages/atomic/src/components/common/suggestions/suggestions-common.ts index 5a12198f1fa..de127841a3b 100644 --- a/packages/atomic/src/components/common/suggestions/suggestions-common.ts +++ b/packages/atomic/src/components/common/suggestions/suggestions-common.ts @@ -141,11 +141,11 @@ export type SearchBoxSuggestionsBindings< /** * Whether the search box is [standalone](https://docs.coveo.com/en/atomic/latest/usage/ssb/). */ - isStandalone(): boolean; + isStandalone: boolean; /** * The search box headless controller. */ - searchBoxController(): SearchBoxController; + searchBoxController: SearchBoxController; /** * The number of queries to display when the user interacts with the search box. */ diff --git a/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx b/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx index 90389a5cf40..9b6c7cd144e 100644 --- a/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx +++ b/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx @@ -33,7 +33,12 @@ import { StorageItems, } from '../../../utils/local-storage-utils'; import {updateBreakpoints} from '../../../utils/replace-breakpoint'; -import {isFocusingOut, once, randomID} from '../../../utils/utils'; +import { + isFocusingOut, + once, + randomID, + spreadProperties, +} from '../../../utils/utils'; import {SearchBoxWrapper} from '../../common/search-box/search-box-wrapper'; import {SearchTextArea} from '../../common/search-box/search-text-area'; import {SubmitButton} from '../../common/search-box/submit-button'; @@ -329,11 +334,11 @@ export class AtomicSearchBox implements InitializableComponent { private get suggestionBindings(): SearchBoxSuggestionsBindings< SearchBox | StandaloneSearchBox > { - return { - ...this.bindings, - ...this.suggestionManager.partialSuggestionBindings, - ...this.partialSuggestionBindings, - }; + return spreadProperties( + this.bindings, + this.suggestionManager.partialSuggestionBindings, + this.partialSuggestionBindings + ); } private get partialSuggestionBindings(): Pick< @@ -344,14 +349,38 @@ export class AtomicSearchBox implements InitializableComponent { | 'numberOfQueries' | 'clearFilters' > { - return { - ...this.bindings, - id: this.id, - isStandalone: () => !!this.redirectionUrl, - searchBoxController: () => this.searchBox, - numberOfQueries: this.numberOfQueries, - clearFilters: this.clearFilters, - }; + return Object.defineProperties( + {...this.bindings}, + { + id: { + get: () => this.id, + enumerable: true, + }, + searchBoxController: { + get: () => this.searchBox, + enumerable: true, + }, + isStandalone: { + get: () => !!this.redirectionUrl, + enumerable: true, + }, + numberOfQueries: { + get: () => this.numberOfQueries, + enumerable: true, + }, + clearFilters: { + get: () => this.clearFilters, + enumerable: true, + }, + } + ) as unknown as Pick< + SearchBoxSuggestionsBindings, + | 'id' + | 'isStandalone' + | 'searchBoxController' + | 'numberOfQueries' + | 'clearFilters' + >; } private get searchBoxOptions(): SearchBoxOptions { diff --git a/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-instant-results/atomic-search-box-instant-results.tsx b/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-instant-results/atomic-search-box-instant-results.tsx index 62e2e57dc55..4ebf8b16494 100644 --- a/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-instant-results/atomic-search-box-instant-results.tsx +++ b/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-instant-results/atomic-search-box-instant-results.tsx @@ -180,10 +180,10 @@ export class AtomicSearchBoxInstantResults implements InitializableComponent { content: , onSelect: () => { this.bindings.clearSuggestions(); - this.bindings - .searchBoxController() - .updateText(this.instantResults.state.q); - this.bindings.searchBoxController().submit(); + this.bindings.searchBoxController.updateText( + this.instantResults.state.q + ); + this.bindings.searchBoxController.submit(); }, }); } @@ -225,7 +225,7 @@ export class AtomicSearchBoxInstantResults implements InitializableComponent { private onSuggestedQueryChange() { if ( !this.bindings.getSuggestionElements().length && - !this.bindings.searchBoxController().state.value + !this.bindings.searchBoxController.state.value ) { console.warn( "There doesn't seem to be any query suggestions configured. Make sure to include either an atomic-search-box-query-suggestions or atomic-search-box-recent-queries in your search box in order to see some instant results." diff --git a/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-query-suggestions/atomic-search-box-query-suggestions.tsx b/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-query-suggestions/atomic-search-box-query-suggestions.tsx index 3dbad9a8ca6..107793f3ef4 100644 --- a/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-query-suggestions/atomic-search-box-query-suggestions.tsx +++ b/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-query-suggestions/atomic-search-box-query-suggestions.tsx @@ -92,16 +92,15 @@ export class AtomicSearchBoxQuerySuggestions { } private renderItems(): SearchBoxSuggestionElement[] { - const hasQuery = this.bindings.searchBoxController().state.value !== ''; + const hasQuery = this.bindings.searchBoxController.state.value !== ''; const max = hasQuery ? this.maxWithQuery : this.maxWithoutQuery; - return this.bindings - .searchBoxController() - .state.suggestions.slice(0, max) + return this.bindings.searchBoxController.state.suggestions + .slice(0, max) .map((suggestion) => this.renderItem(suggestion)); } private renderItem(suggestion: Suggestion) { - const hasQuery = this.bindings.searchBoxController().state.value !== ''; + const hasQuery = this.bindings.searchBoxController.state.value !== ''; const partialItem = getPartialSearchBoxSuggestionElement( suggestion, this.bindings.i18n @@ -120,9 +119,7 @@ export class AtomicSearchBoxQuerySuggestions { ), onSelect: () => { - this.bindings - .searchBoxController() - .selectSuggestion(suggestion.rawValue); + this.bindings.searchBoxController.selectSuggestion(suggestion.rawValue); }, }; } diff --git a/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-recent-queries/atomic-search-box-recent-queries.tsx b/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-recent-queries/atomic-search-box-recent-queries.tsx index cd79c6b7a16..b497cfcd7a3 100644 --- a/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-recent-queries/atomic-search-box-recent-queries.tsx +++ b/packages/atomic/src/components/search/search-box-suggestions/atomic-search-box-recent-queries/atomic-search-box-recent-queries.tsx @@ -121,7 +121,7 @@ export class AtomicSearchBoxRecentQueries { return []; } - const query = this.bindings.searchBoxController().state.value; + const query = this.bindings.searchBoxController.state.value; const hasQuery = query !== ''; const max = hasQuery ? this.maxWithQuery : this.maxWithoutQuery; const filteredQueries = this.recentQueriesList.state.queries @@ -155,7 +155,7 @@ export class AtomicSearchBoxRecentQueries { } private renderItem(value: string): SearchBoxSuggestionElement { - const query = this.bindings.searchBoxController().state.value; + const query = this.bindings.searchBoxController.state.value; const partialItem = getPartialRecentQueryElement(value, this.bindings.i18n); return { ...partialItem, @@ -167,9 +167,9 @@ export class AtomicSearchBoxRecentQueries { ), onSelect: () => { - if (this.bindings.isStandalone()) { - this.bindings.searchBoxController().updateText(value); - this.bindings.searchBoxController().submit(); + if (this.bindings.isStandalone) { + this.bindings.searchBoxController.updateText(value); + this.bindings.searchBoxController.submit(); return; } diff --git a/packages/atomic/src/utils/utils.ts b/packages/atomic/src/utils/utils.ts index 342c8e0fd75..794eb77cf27 100644 --- a/packages/atomic/src/utils/utils.ts +++ b/packages/atomic/src/utils/utils.ts @@ -250,3 +250,23 @@ export function aggregate( >{} ); } + +/** + * Similar as a classic spread, but preserve all characteristics of properties (e.g. getter/setter). + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#description + * for an explanation why (spread & assign work similarly). + * @param objects the objects to "spread" together + * @returns the spread result + */ +export function spreadProperties( + ...objects: object[] +) { + const returnObject = {}; + for (const obj of objects) { + Object.defineProperties( + returnObject, + Object.getOwnPropertyDescriptors(obj) + ); + } + return returnObject as Output; +}