@wix/headless-stores 0.0.57 → 0.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/cjs/dist/react/ProductList.js +1 -0
  2. package/cjs/dist/react/ProductListSort.d.ts +14 -0
  3. package/cjs/dist/react/ProductListSort.js +14 -0
  4. package/cjs/dist/react/core/ProductList.d.ts +3 -0
  5. package/cjs/dist/react/core/ProductList.js +2 -0
  6. package/cjs/dist/react/core/ProductListFilters.d.ts +8 -180
  7. package/cjs/dist/react/core/ProductListFilters.js +137 -171
  8. package/cjs/dist/react/core/ProductListPagination.d.ts +0 -192
  9. package/cjs/dist/react/core/ProductListPagination.js +2 -160
  10. package/cjs/dist/react/core/ProductListSort.d.ts +9 -57
  11. package/cjs/dist/react/core/ProductListSort.js +32 -52
  12. package/cjs/dist/services/index.d.ts +2 -2
  13. package/cjs/dist/services/products-list-search-service.d.ts +3 -162
  14. package/cjs/dist/services/products-list-search-service.js +31 -424
  15. package/cjs/dist/services/products-list-service.d.ts +86 -4
  16. package/cjs/dist/services/products-list-service.js +125 -4
  17. package/dist/react/ProductList.js +1 -0
  18. package/dist/react/ProductListSort.d.ts +14 -0
  19. package/dist/react/ProductListSort.js +14 -0
  20. package/dist/react/core/ProductList.d.ts +3 -0
  21. package/dist/react/core/ProductList.js +2 -0
  22. package/dist/react/core/ProductListFilters.d.ts +8 -180
  23. package/dist/react/core/ProductListFilters.js +137 -171
  24. package/dist/react/core/ProductListPagination.d.ts +0 -192
  25. package/dist/react/core/ProductListPagination.js +2 -160
  26. package/dist/react/core/ProductListSort.d.ts +9 -57
  27. package/dist/react/core/ProductListSort.js +32 -52
  28. package/dist/services/index.d.ts +2 -2
  29. package/dist/services/products-list-search-service.d.ts +3 -162
  30. package/dist/services/products-list-search-service.js +31 -424
  31. package/dist/services/products-list-service.d.ts +86 -4
  32. package/dist/services/products-list-service.js +125 -4
  33. package/package.json +4 -4
@@ -1,8 +1,8 @@
1
1
  import { defineService, implementService } from '@wix/services-definitions';
2
2
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
3
- import { productsV3, readOnlyVariantsV3 } from '@wix/stores';
3
+ import { customizationsV3, productsV3, readOnlyVariantsV3 } from '@wix/stores';
4
4
  import { loadCategoriesListServiceConfig } from './categories-list-service.js';
5
- import { parseUrlToSearchOptions, } from './products-list-search-service.js';
5
+ import { InventoryStatusType, parseUrlToSearchOptions, } from './products-list-search-service.js';
6
6
  export const DEFAULT_QUERY_LIMIT = 100;
7
7
  /**
8
8
  * Loads products list service configuration from the Wix Stores API for SSR initialization.
@@ -118,10 +118,13 @@ export const DEFAULT_QUERY_LIMIT = 100;
118
118
  */
119
119
  export async function loadProductsListServiceConfig(input) {
120
120
  let searchOptions;
121
+ const { items: customizations = [] } = await customizationsV3
122
+ .queryCustomizations()
123
+ .find();
121
124
  if (typeof input === 'string') {
122
125
  // URL input - parse it
123
126
  const categoriesListConfig = await loadCategoriesListServiceConfig();
124
- const { searchOptions: parsedOptions } = await parseUrlToSearchOptions(input, categoriesListConfig.categories);
127
+ const { searchOptions: parsedOptions } = await parseUrlToSearchOptions(input, categoriesListConfig.categories, customizations);
125
128
  searchOptions = parsedOptions;
126
129
  }
127
130
  else {
@@ -139,6 +142,7 @@ export async function loadProductsListServiceConfig(input) {
139
142
  searchOptions,
140
143
  pagingMetadata: resultWithFilter.pagingMetadata,
141
144
  aggregations: resultWithoutFilter.aggregationData ?? {},
145
+ customizations,
142
146
  };
143
147
  }
144
148
  /**
@@ -292,6 +296,14 @@ export const ProductListService = implementService.withConfig()(ProductsListServ
292
296
  const productsSignal = signalsService.signal(config.products);
293
297
  const searchOptionsSignal = signalsService.signal(config.searchOptions);
294
298
  const pagingMetadataSignal = signalsService.signal(config.pagingMetadata);
299
+ const minPriceSignal = signalsService.signal(getMinPrice(config.aggregations.results));
300
+ const maxPriceSignal = signalsService.signal(getMaxPrice(config.aggregations.results));
301
+ const availableProductOptionsSignal = signalsService.signal(getAvailableProductOptions(config.aggregations.results, config.customizations));
302
+ const availableInventoryStatusesSignal = signalsService.signal([
303
+ InventoryStatusType.IN_STOCK,
304
+ InventoryStatusType.OUT_OF_STOCK,
305
+ InventoryStatusType.PARTIALLY_OUT_OF_STOCK,
306
+ ]);
295
307
  const aggregationsSignal = signalsService.signal(config.aggregations);
296
308
  const isLoadingSignal = signalsService.signal(false);
297
309
  const errorSignal = signalsService.signal(null);
@@ -331,7 +343,15 @@ export const ProductListService = implementService.withConfig()(ProductsListServ
331
343
  searchOptions: searchOptionsSignal,
332
344
  pagingMetadata: pagingMetadataSignal,
333
345
  aggregations: aggregationsSignal,
334
- setSearchOptions: (searchOptions) => searchOptionsSignal.set(searchOptions),
346
+ /* Metadata for products list */
347
+ minPrice: minPriceSignal,
348
+ maxPrice: maxPriceSignal,
349
+ availableInventoryStatuses: availableInventoryStatusesSignal,
350
+ availableProductOptions: availableProductOptionsSignal,
351
+ /* End of Metadata for products list */
352
+ setSearchOptions: (searchOptions) => {
353
+ searchOptionsSignal.set(searchOptions);
354
+ },
335
355
  setSort: (sort) => {
336
356
  const currentOptions = searchOptionsSignal.peek();
337
357
  searchOptionsSignal.set({
@@ -346,7 +366,108 @@ export const ProductListService = implementService.withConfig()(ProductsListServ
346
366
  filter,
347
367
  });
348
368
  },
369
+ resetFilter: () => {
370
+ const currentOptions = searchOptionsSignal.peek();
371
+ searchOptionsSignal.set({
372
+ ...currentOptions,
373
+ filter: {},
374
+ });
375
+ },
376
+ isFiltered: () => {
377
+ return signalsService.computed(() => {
378
+ const currentOptions = searchOptionsSignal.peek();
379
+ if (!currentOptions.filter)
380
+ return false;
381
+ return (currentOptions.filter !== undefined &&
382
+ Object.keys(currentOptions.filter).length > 0);
383
+ });
384
+ },
349
385
  isLoading: isLoadingSignal,
350
386
  error: errorSignal,
387
+ loadMore: (count) => {
388
+ const currentOptions = searchOptionsSignal.peek();
389
+ searchOptionsSignal.set({
390
+ ...currentOptions,
391
+ cursorPaging: {
392
+ cursor: pagingMetadataSignal.get().cursors?.next,
393
+ limit: currentOptions.cursorPaging?.limit ?? 0 + count,
394
+ },
395
+ });
396
+ },
397
+ hasMoreProducts: signalsService.computed(() => pagingMetadataSignal.get().hasNext ?? false),
351
398
  };
352
399
  });
400
+ function getMinPrice(aggregationData) {
401
+ const minPriceAggregation = aggregationData.find((data) => data.fieldPath === 'actualPriceRange.minValue.amount');
402
+ if (minPriceAggregation?.scalar?.value) {
403
+ return Number(minPriceAggregation.scalar.value) || 0;
404
+ }
405
+ return 0;
406
+ }
407
+ function getMaxPrice(aggregationData) {
408
+ const maxPriceAggregation = aggregationData.find((data) => data.fieldPath === 'actualPriceRange.maxValue.amount');
409
+ if (maxPriceAggregation?.scalar?.value) {
410
+ return Number(maxPriceAggregation.scalar.value) || 0;
411
+ }
412
+ return 0;
413
+ }
414
+ function getAvailableProductOptions(aggregationData = [], customizations = []) {
415
+ const matchesAggregationName = (name, aggregationNames) => {
416
+ return aggregationNames.some((aggName) => aggName.toLowerCase() === name.toLowerCase());
417
+ };
418
+ const sortChoicesIntelligently = (choices) => {
419
+ return [...choices].sort((a, b) => {
420
+ const aIsNumber = /^\d+$/.test(a.name);
421
+ const bIsNumber = /^\d+$/.test(b.name);
422
+ if (aIsNumber && bIsNumber) {
423
+ return parseInt(a.name) - parseInt(b.name);
424
+ }
425
+ if (aIsNumber && !bIsNumber)
426
+ return -1;
427
+ if (!aIsNumber && bIsNumber)
428
+ return 1;
429
+ return a.name.localeCompare(b.name);
430
+ });
431
+ };
432
+ const optionNames = [];
433
+ const choiceNames = [];
434
+ aggregationData.forEach((result) => {
435
+ if (result.name === 'optionNames' && result.values?.results) {
436
+ optionNames.push(...result.values.results
437
+ .map((item) => item.value)
438
+ .filter((value) => typeof value === 'string'));
439
+ }
440
+ if (result.name === 'choiceNames' && result.values?.results) {
441
+ choiceNames.push(...result.values.results
442
+ .map((item) => item.value)
443
+ .filter((value) => typeof value === 'string'));
444
+ }
445
+ });
446
+ const options = customizations
447
+ .filter((customization) => customization.name &&
448
+ customization._id &&
449
+ customization.customizationType ===
450
+ customizationsV3.CustomizationType.PRODUCT_OPTION &&
451
+ (optionNames.length === 0 ||
452
+ matchesAggregationName(customization.name, optionNames)))
453
+ .map((customization) => {
454
+ const choices = (customization.choicesSettings?.choices || [])
455
+ .filter((choice) => choice._id &&
456
+ choice.name &&
457
+ (choiceNames.length === 0 ||
458
+ matchesAggregationName(choice.name, choiceNames)))
459
+ .map((choice) => ({
460
+ id: choice._id,
461
+ name: choice.name,
462
+ colorCode: choice.colorCode,
463
+ }));
464
+ return {
465
+ id: customization._id,
466
+ name: customization.name,
467
+ choices: sortChoicesIntelligently(choices),
468
+ optionRenderType: customization.customizationRenderType,
469
+ };
470
+ })
471
+ .filter((option) => option.choices.length > 0);
472
+ return options;
473
+ }
@@ -48,6 +48,7 @@ export const Root = React.forwardRef((props, ref) => {
48
48
  count: products?.length || 0,
49
49
  },
50
50
  aggregations: {}, // Empty aggregation data
51
+ customizations: [],
51
52
  };
52
53
  return (_jsx(CoreProductList.Root, { productsListConfig: serviceConfig, productsListSearchConfig: productsListSearchConfig, children: _jsx(RootContent, { children: children, className: className, ref: ref }) }));
53
54
  });
@@ -0,0 +1,14 @@
1
+ import { Sort as SortPrimitive } from '@wix/headless-components/react';
2
+ import { productsV3 } from '@wix/stores';
3
+ import React from 'react';
4
+ export interface ProductListSortProps {
5
+ children?: (props: {
6
+ currentSort: productsV3.V3ProductSearch['sort'];
7
+ sortOptions: SortPrimitive.SortOption[];
8
+ setSort: (sort: productsV3.V3ProductSearch['sort']) => void;
9
+ }) => React.ReactNode;
10
+ className?: string;
11
+ as?: 'select' | 'list';
12
+ asChild?: boolean;
13
+ }
14
+ export declare const ProductListSort: React.ForwardRefExoticComponent<ProductListSortProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Sort as SortPrimitive } from '@wix/headless-components/react';
3
+ import { ProductListSort as ProductListSortPrimitive } from './core/ProductListSort.js';
4
+ import React from 'react';
5
+ export const ProductListSort = React.forwardRef(({ children, className, as, asChild }, ref) => {
6
+ return (_jsx(ProductListSortPrimitive, { children: ({ currentSort, sortOptions, setSort }) => {
7
+ if (asChild && children) {
8
+ return children({ currentSort, sortOptions, setSort });
9
+ }
10
+ return (_jsx(SortPrimitive.Root, { ref: ref, value: currentSort, onChange: (value) => {
11
+ setSort(value);
12
+ }, sortOptions: sortOptions, as: as, className: className }));
13
+ } }));
14
+ });
@@ -1,6 +1,7 @@
1
1
  import { type ProductsListServiceConfig } from '../../services/products-list-service.js';
2
2
  import { productsV3 } from '@wix/stores';
3
3
  import { ProductsListSearchServiceConfig } from '../../services/products-list-search-service.js';
4
+ import { CategoriesListServiceConfig } from '../../services/categories-list-service.js';
4
5
  /**
5
6
  * Props for Root headless component
6
7
  */
@@ -11,6 +12,8 @@ export interface RootProps {
11
12
  productsListConfig: ProductsListServiceConfig;
12
13
  /** Configuration for the ProductListSearch service */
13
14
  productsListSearchConfig?: ProductsListSearchServiceConfig;
15
+ /** Configuration for the CategoriesList service */
16
+ categoriesListConfig?: CategoriesListServiceConfig;
14
17
  }
15
18
  /**
16
19
  * Root component that provides both ProductList and ProductListSearch service contexts to its children.
@@ -4,6 +4,7 @@ import { createServicesMap } from '@wix/services-manager';
4
4
  import { ProductListService, ProductsListServiceDefinition, } from '../../services/products-list-service.js';
5
5
  import { ProductService, ProductServiceDefinition, } from '../../services/product-service.js';
6
6
  import { ProductsListSearchService, ProductsListSearchServiceDefinition, } from '../../services/products-list-search-service.js';
7
+ import { CategoriesListService, CategoriesListServiceDefinition, } from '../../services/categories-list-service.js';
7
8
  /**
8
9
  * Root component that provides both ProductList and ProductListSearch service contexts to its children.
9
10
  * This component sets up the necessary services for managing products list state, including search,
@@ -64,6 +65,7 @@ import { ProductsListSearchService, ProductsListSearchServiceDefinition, } from
64
65
  */
65
66
  export function Root(props) {
66
67
  return (_jsx(WixServices, { servicesMap: createServicesMap()
68
+ .addService(CategoriesListServiceDefinition, CategoriesListService, props.categoriesListConfig)
67
69
  .addService(ProductsListServiceDefinition, ProductListService, props.productsListConfig)
68
70
  .addService(ProductsListSearchServiceDefinition, ProductsListSearchService, props.productsListSearchConfig), children: props.children }));
69
71
  }
@@ -1,61 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
- import { type ProductOption, InventoryStatusType } from '../../services/products-list-search-service.js';
3
2
  import { Category } from '@wix/auto_sdk_categories_categories';
4
- /**
5
- * Props for InventoryStatus headless component
6
- */
7
- export interface InventoryStatusProps {
8
- /** Content to display (can be a render function receiving inventory status controls or ReactNode) */
9
- children: ((props: InventoryStatusRenderProps) => ReactNode) | ReactNode;
10
- }
11
- /**
12
- * Render props for InventoryStatus component
13
- */
14
- export interface InventoryStatusRenderProps {
15
- /** Available inventory status options */
16
- availableInventoryStatuses: InventoryStatusType[];
17
- /** Currently selected inventory statuses */
18
- selectedInventoryStatuses: InventoryStatusType[];
19
- /** Function to toggle an inventory status filter */
20
- toggleInventoryStatus: (status: InventoryStatusType) => void;
21
- }
22
- /**
23
- * Headless component for managing inventory status filters
24
- *
25
- * @component
26
- * @example
27
- * ```tsx
28
- * import { ProductList, ProductListFilters } from '@wix/stores/components';
29
- *
30
- * function InventoryStatusFilter() {
31
- * return (
32
- * <ProductList.Root
33
- * productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
34
- * productsListSearchConfig={{ customizations: [] }}
35
- * >
36
- * <ProductListFilters.InventoryStatus>
37
- * {({ availableInventoryStatuses, selectedInventoryStatuses, toggleInventoryStatus }) => (
38
- * <div>
39
- * <h4>Inventory Status:</h4>
40
- * {availableInventoryStatuses.map(status => (
41
- * <label key={status}>
42
- * <input
43
- * type="checkbox"
44
- * checked={selectedInventoryStatuses.includes(status)}
45
- * onChange={() => toggleInventoryStatus(status)}
46
- * />
47
- * {status}
48
- * </label>
49
- * ))}
50
- * </div>
51
- * )}
52
- * </ProductListFilters.InventoryStatus>
53
- * </ProductList.Root>
54
- * );
55
- * }
56
- * ```
57
- */
58
- export declare function InventoryStatus(props: InventoryStatusProps): ReactNode;
3
+ import React from 'react';
59
4
  /**
60
5
  * Props for ResetTrigger headless component
61
6
  */
@@ -103,136 +48,19 @@ export interface ResetTriggerRenderProps {
103
48
  * ```
104
49
  */
105
50
  export declare function ResetTrigger(props: ResetTriggerProps): ReactNode;
106
- /**
107
- * Props for PriceRange headless component
108
- */
109
- export interface PriceRangeProps {
110
- /** Content to display (can be a render function receiving price range controls or ReactNode) */
111
- children: ((props: PriceRangeRenderProps) => ReactNode) | ReactNode;
112
- }
113
- /**
114
- * Render props for PriceRange component
115
- */
116
- export interface PriceRangeRenderProps {
117
- /** Current minimum price filter value */
118
- selectedMinPrice: number;
119
- /** Current maximum price filter value */
120
- selectedMaxPrice: number;
121
- /** Catalog minimum price */
122
- availableMinPrice: number;
123
- /** Catalog maximum price */
124
- availableMaxPrice: number;
125
- /** Function to update the minimum price filter */
126
- setSelectedMinPrice: (minPrice: number) => void;
127
- /** Function to update the maximum price filter */
128
- setSelectedMaxPrice: (maxPrice: number) => void;
129
- }
130
- /**
131
- * Headless component for managing price range filters (combined min/max)
132
- *
133
- * @component
134
- * @example
135
- * ```tsx
136
- * import { ProductList, ProductListFilters } from '@wix/stores/components';
137
- *
138
- * function PriceRangeFilter() {
139
- * return (
140
- * <ProductList.Root
141
- * productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
142
- * productsListSearchConfig={{ customizations: [] }}
143
- * >
144
- * <ProductListFilters.PriceRange>
145
- * {({ minPrice, maxPrice, setSelectedMinPrice, setSelectedMaxPrice }) => (
146
- * <div className="price-range">
147
- * <h4>Price Range:</h4>
148
- * <div className="price-inputs">
149
- * <input
150
- * type="number"
151
- * value={minPrice}
152
- * onChange={(e) => setSelectedMinPrice(Number(e.target.value))}
153
- * placeholder="Min"
154
- * />
155
- * <span>to</span>
156
- * <input
157
- * type="number"
158
- * value={maxPrice}
159
- * onChange={(e) => setSelectedMaxPrice(Number(e.target.value))}
160
- * placeholder="Max"
161
- * />
162
- * </div>
163
- * </div>
164
- * )}
165
- * </ProductListFilters.PriceRange>
166
- * </ProductList.Root>
167
- * );
168
- * }
169
- * ```
170
- */
171
- export declare function PriceRange(props: PriceRangeProps): ReactNode;
172
51
  export interface CategoryFilterRenderProps {
173
52
  selectedCategory: Category | null;
174
- setSelectedCategory: (category: Category | null) => void;
53
+ setSelectedCategory: (category: Category) => void;
175
54
  }
176
55
  export interface CategoryFilterProps {
177
56
  /** Content to display (can be a render function receiving category data or ReactNode) */
178
57
  children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
179
58
  }
180
59
  export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
181
- /**
182
- * Props for ProductOptions headless component
183
- */
184
- export interface ProductOptionsProps {
185
- /** Content to display (can be a render function receiving product option data or ReactNode) */
186
- children: ((props: ProductOptionRenderProps) => ReactNode) | ReactNode;
60
+ interface FilterProps {
61
+ children: ReactNode;
62
+ asChild?: boolean;
63
+ className?: string;
187
64
  }
188
- /**
189
- * Render props for ProductOption component
190
- */
191
- export interface ProductOptionRenderProps {
192
- /** Product option data */
193
- option: ProductOption;
194
- /** Currently selected choice IDs for this option */
195
- selectedChoices: string[];
196
- /** Function to toggle a choice selection */
197
- toggleChoice: (choiceId: string) => void;
198
- }
199
- /**
200
- * Headless component that renders content for each product option in the list.
201
- * Maps over all available product options and provides each option through a render prop.
202
- * Only renders when options are available (not loading, no error, and has options).
203
- * This follows the same collection pattern as ProductList.ItemContent and CategoryList.ItemContent.
204
- *
205
- * @component
206
- * @example
207
- * ```tsx
208
- * import { ProductList, ProductListFilters } from '@wix/stores/components';
209
- *
210
- * function ProductOptionsFilter() {
211
- * return (
212
- * <ProductList.Root
213
- * productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
214
- * productsListSearchConfig={{ customizations: [] }}
215
- * >
216
- * <ProductListFilters.ProductOptions>
217
- * {({ option, selectedChoices, toggleChoice }) => (
218
- * <div key={option.id}>
219
- * <h4>{option.name}</h4>
220
- * {option.choices.map(choice => (
221
- * <label key={choice.id}>
222
- * <input
223
- * type="checkbox"
224
- * checked={selectedChoices.includes(choice.id)}
225
- * onChange={() => toggleChoice(choice.id)}
226
- * />
227
- * {choice.name}
228
- * </label>
229
- * ))}
230
- * </div>
231
- * )}
232
- * </ProductListFilters.ProductOptions>
233
- * </ProductList.Root>
234
- * );
235
- * }
236
- * ```
237
- */
238
- export declare function ProductOptions(props: ProductOptionsProps): import("react/jsx-runtime").JSX.Element | null;
65
+ export declare const Filter: React.ForwardRefExoticComponent<FilterProps & React.RefAttributes<HTMLDivElement>>;
66
+ export {};