@wix/headless-stores 0.0.37 → 0.0.39

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 (37) hide show
  1. package/cjs/dist/react/Category.d.ts +9 -8
  2. package/cjs/dist/react/CategoryList.d.ts +12 -11
  3. package/cjs/dist/react/Product.d.ts +5 -4
  4. package/cjs/dist/react/ProductListFilters.d.ts +9 -5
  5. package/cjs/dist/react/ProductListFilters.js +14 -5
  6. package/cjs/dist/react/ProductListPagination.d.ts +14 -12
  7. package/cjs/dist/react/ProductListSort.d.ts +6 -4
  8. package/cjs/dist/react/ProductModifiers.d.ts +4 -2
  9. package/cjs/dist/react/ProductVariantSelector.d.ts +5 -4
  10. package/cjs/dist/react/SelectedVariant.d.ts +68 -4
  11. package/cjs/dist/react/SelectedVariant.js +97 -0
  12. package/cjs/dist/react/index.d.ts +0 -1
  13. package/cjs/dist/react/index.js +0 -1
  14. package/cjs/dist/services/products-list-filters-service.d.ts +41 -24
  15. package/cjs/dist/services/products-list-filters-service.js +150 -92
  16. package/cjs/dist/services/products-list-service.js +87 -3
  17. package/dist/react/Category.d.ts +9 -8
  18. package/dist/react/CategoryList.d.ts +12 -11
  19. package/dist/react/Product.d.ts +5 -4
  20. package/dist/react/ProductListFilters.d.ts +9 -5
  21. package/dist/react/ProductListFilters.js +14 -5
  22. package/dist/react/ProductListPagination.d.ts +14 -12
  23. package/dist/react/ProductListSort.d.ts +6 -4
  24. package/dist/react/ProductModifiers.d.ts +4 -2
  25. package/dist/react/ProductVariantSelector.d.ts +5 -4
  26. package/dist/react/SelectedVariant.d.ts +68 -4
  27. package/dist/react/SelectedVariant.js +97 -0
  28. package/dist/react/index.d.ts +0 -1
  29. package/dist/react/index.js +0 -1
  30. package/dist/services/products-list-filters-service.d.ts +41 -24
  31. package/dist/services/products-list-filters-service.js +150 -92
  32. package/dist/services/products-list-service.js +87 -3
  33. package/package.json +2 -2
  34. package/cjs/dist/react/ProductActions.d.ts +0 -70
  35. package/cjs/dist/react/ProductActions.js +0 -104
  36. package/dist/react/ProductActions.d.ts +0 -70
  37. package/dist/react/ProductActions.js +0 -104
@@ -112,10 +112,14 @@ export declare function loadProductsListFiltersServiceConfig(): Promise<Products
112
112
  */
113
113
  export declare const ProductsListFiltersServiceDefinition: string & {
114
114
  __api: {
115
- /** Reactive signal containing the minimum price filter value */
116
- minPrice: Signal<number>;
117
- /** Reactive signal containing the maximum price filter value */
118
- maxPrice: Signal<number>;
115
+ /** Reactive signal containing the user's selected minimum price filter value */
116
+ selectedMinPrice: Signal<number>;
117
+ /** Reactive signal containing the user's selected maximum price filter value */
118
+ selectedMaxPrice: Signal<number>;
119
+ /** Reactive signal containing the catalog minimum price (for UI bounds) */
120
+ availableMinPrice: Signal<number>;
121
+ /** Reactive signal containing the catalog maximum price (for UI bounds) */
122
+ availableMaxPrice: Signal<number>;
119
123
  /** Reactive signal containing available inventory status options */
120
124
  availableInventoryStatuses: Signal<InventoryStatusType[]>;
121
125
  /** Reactive signal containing selected inventory status filters */
@@ -125,9 +129,9 @@ export declare const ProductsListFiltersServiceDefinition: string & {
125
129
  /** Reactive signal containing selected product option filters */
126
130
  selectedProductOptions: Signal<Record<string, string[]>>;
127
131
  /** Function to set the minimum price filter */
128
- setMinPrice: (minPrice: number) => void;
132
+ setSelectedMinPrice: (minPrice: number) => void;
129
133
  /** Function to set the maximum price filter */
130
- setMaxPrice: (maxPrice: number) => void;
134
+ setSelectedMaxPrice: (maxPrice: number) => void;
131
135
  /** Function to toggle an inventory status filter */
132
136
  toggleInventoryStatus: (status: InventoryStatusType) => void;
133
137
  /** Function to toggle a product option choice filter */
@@ -140,10 +144,14 @@ export declare const ProductsListFiltersServiceDefinition: string & {
140
144
  __config: {};
141
145
  isServiceDefinition?: boolean;
142
146
  } & {
143
- /** Reactive signal containing the minimum price filter value */
144
- minPrice: Signal<number>;
145
- /** Reactive signal containing the maximum price filter value */
146
- maxPrice: Signal<number>;
147
+ /** Reactive signal containing the user's selected minimum price filter value */
148
+ selectedMinPrice: Signal<number>;
149
+ /** Reactive signal containing the user's selected maximum price filter value */
150
+ selectedMaxPrice: Signal<number>;
151
+ /** Reactive signal containing the catalog minimum price (for UI bounds) */
152
+ availableMinPrice: Signal<number>;
153
+ /** Reactive signal containing the catalog maximum price (for UI bounds) */
154
+ availableMaxPrice: Signal<number>;
147
155
  /** Reactive signal containing available inventory status options */
148
156
  availableInventoryStatuses: Signal<InventoryStatusType[]>;
149
157
  /** Reactive signal containing selected inventory status filters */
@@ -153,9 +161,9 @@ export declare const ProductsListFiltersServiceDefinition: string & {
153
161
  /** Reactive signal containing selected product option filters */
154
162
  selectedProductOptions: Signal<Record<string, string[]>>;
155
163
  /** Function to set the minimum price filter */
156
- setMinPrice: (minPrice: number) => void;
164
+ setSelectedMinPrice: (minPrice: number) => void;
157
165
  /** Function to set the maximum price filter */
158
- setMaxPrice: (maxPrice: number) => void;
166
+ setSelectedMaxPrice: (maxPrice: number) => void;
159
167
  /** Function to toggle an inventory status filter */
160
168
  toggleInventoryStatus: (status: InventoryStatusType) => void;
161
169
  /** Function to toggle a product option choice filter */
@@ -233,14 +241,19 @@ export declare const ProductsListFiltersServiceDefinition: string & {
233
241
  * </div>
234
242
  * );
235
243
  * }
244
+ * }
236
245
  * ```
237
246
  */
238
247
  export declare const ProductsListFiltersService: import("@wix/services-definitions").ServiceFactory<string & {
239
248
  __api: {
240
- /** Reactive signal containing the minimum price filter value */
241
- minPrice: Signal<number>;
242
- /** Reactive signal containing the maximum price filter value */
243
- maxPrice: Signal<number>;
249
+ /** Reactive signal containing the user's selected minimum price filter value */
250
+ selectedMinPrice: Signal<number>;
251
+ /** Reactive signal containing the user's selected maximum price filter value */
252
+ selectedMaxPrice: Signal<number>;
253
+ /** Reactive signal containing the catalog minimum price (for UI bounds) */
254
+ availableMinPrice: Signal<number>;
255
+ /** Reactive signal containing the catalog maximum price (for UI bounds) */
256
+ availableMaxPrice: Signal<number>;
244
257
  /** Reactive signal containing available inventory status options */
245
258
  availableInventoryStatuses: Signal<InventoryStatusType[]>;
246
259
  /** Reactive signal containing selected inventory status filters */
@@ -250,9 +263,9 @@ export declare const ProductsListFiltersService: import("@wix/services-definitio
250
263
  /** Reactive signal containing selected product option filters */
251
264
  selectedProductOptions: Signal<Record<string, string[]>>;
252
265
  /** Function to set the minimum price filter */
253
- setMinPrice: (minPrice: number) => void;
266
+ setSelectedMinPrice: (minPrice: number) => void;
254
267
  /** Function to set the maximum price filter */
255
- setMaxPrice: (maxPrice: number) => void;
268
+ setSelectedMaxPrice: (maxPrice: number) => void;
256
269
  /** Function to toggle an inventory status filter */
257
270
  toggleInventoryStatus: (status: InventoryStatusType) => void;
258
271
  /** Function to toggle a product option choice filter */
@@ -265,10 +278,14 @@ export declare const ProductsListFiltersService: import("@wix/services-definitio
265
278
  __config: {};
266
279
  isServiceDefinition?: boolean;
267
280
  } & {
268
- /** Reactive signal containing the minimum price filter value */
269
- minPrice: Signal<number>;
270
- /** Reactive signal containing the maximum price filter value */
271
- maxPrice: Signal<number>;
281
+ /** Reactive signal containing the user's selected minimum price filter value */
282
+ selectedMinPrice: Signal<number>;
283
+ /** Reactive signal containing the user's selected maximum price filter value */
284
+ selectedMaxPrice: Signal<number>;
285
+ /** Reactive signal containing the catalog minimum price (for UI bounds) */
286
+ availableMinPrice: Signal<number>;
287
+ /** Reactive signal containing the catalog maximum price (for UI bounds) */
288
+ availableMaxPrice: Signal<number>;
272
289
  /** Reactive signal containing available inventory status options */
273
290
  availableInventoryStatuses: Signal<InventoryStatusType[]>;
274
291
  /** Reactive signal containing selected inventory status filters */
@@ -278,9 +295,9 @@ export declare const ProductsListFiltersService: import("@wix/services-definitio
278
295
  /** Reactive signal containing selected product option filters */
279
296
  selectedProductOptions: Signal<Record<string, string[]>>;
280
297
  /** Function to set the minimum price filter */
281
- setMinPrice: (minPrice: number) => void;
298
+ setSelectedMinPrice: (minPrice: number) => void;
282
299
  /** Function to set the maximum price filter */
283
- setMaxPrice: (maxPrice: number) => void;
300
+ setSelectedMaxPrice: (maxPrice: number) => void;
284
301
  /** Function to toggle an inventory status filter */
285
302
  toggleInventoryStatus: (status: InventoryStatusType) => void;
286
303
  /** Function to toggle a product option choice filter */
@@ -4,6 +4,7 @@ import { SignalsServiceDefinition, } from "@wix/services-definitions/core-servic
4
4
  import { productsV3 } from "@wix/stores";
5
5
  import { ProductsListServiceDefinition } from "./products-list-service.js";
6
6
  import { customizationsV3 } from "@wix/stores";
7
+ const PRICE_FILTER_DEBOUNCE_TIME = 300;
7
8
  /**
8
9
  * Enumeration of inventory status types available for filtering.
9
10
  * Maps to the Wix Stores API inventory availability statuses.
@@ -151,6 +152,7 @@ export const ProductsListFiltersServiceDefinition = defineService("products-list
151
152
  * </div>
152
153
  * );
153
154
  * }
155
+ * }
154
156
  * ```
155
157
  */
156
158
  export const ProductsListFiltersService = implementService.withConfig()(ProductsListFiltersServiceDefinition, ({ getService, config }) => {
@@ -159,115 +161,72 @@ export const ProductsListFiltersService = implementService.withConfig()(Products
159
161
  const productsListService = getService(ProductsListServiceDefinition);
160
162
  const { customizations } = config;
161
163
  const aggregationData = productsListService.aggregations.get()?.results;
162
- // TODO: use the aggregations to get the available inventory statuses
163
- // and the available price ranges
164
- // and the available product options
165
- // and the available product choices
166
- const minPriceSignal = signalsService.signal(getMinPrice(productsListService.searchOptions.get()));
167
- const maxPriceSignal = signalsService.signal(getMaxPrice(productsListService.searchOptions.get()));
164
+ const currentSearchOptions = productsListService.searchOptions.get();
165
+ // Get the full catalog price range from initial aggregation data (before any filters)
166
+ const catalogPriceRange = getCatalogPriceRange(aggregationData || []);
167
+ // Initialize signals with user's current filter selections (or 0 if no filters)
168
+ const userFilterMinPriceSignal = signalsService.signal(getSelectedMinPrice(currentSearchOptions) ?? catalogPriceRange.minPrice);
169
+ const userFilterMaxPriceSignal = signalsService.signal(getSelectedMaxPrice(currentSearchOptions) ?? catalogPriceRange.maxPrice);
170
+ // Separate signals for the available catalog range (for UI bounds)
171
+ const catalogMinPriceSignal = signalsService.signal(catalogPriceRange.minPrice);
172
+ const catalogMaxPriceSignal = signalsService.signal(catalogPriceRange.maxPrice);
168
173
  const availableInventoryStatusesSignal = signalsService.signal([
169
174
  InventoryStatusType.IN_STOCK,
170
175
  InventoryStatusType.OUT_OF_STOCK,
171
176
  InventoryStatusType.PARTIALLY_OUT_OF_STOCK,
172
177
  ]);
173
- const selectedInventoryStatusesSignal = signalsService.signal(getSelectedInventoryStatuses(productsListService.searchOptions.get()));
178
+ const selectedInventoryStatusesSignal = signalsService.signal(getSelectedInventoryStatuses(currentSearchOptions));
174
179
  // TODO: Get product options from aggregations data
175
180
  const availableProductOptionsSignal = signalsService.signal(getAvailableProductOptions(aggregationData, customizations));
176
- const selectedProductOptionsSignal = signalsService.signal(getSelectedProductOptions(productsListService.searchOptions.get()));
181
+ const selectedProductOptionsSignal = signalsService.signal(getSelectedProductOptions(currentSearchOptions));
177
182
  const isFilteredSignal = signalsService.signal(false);
183
+ // Debounce timeout IDs for price filters
184
+ let minPriceTimeoutId = null;
185
+ let maxPriceTimeoutId = null;
178
186
  if (typeof window !== "undefined") {
179
187
  signalsService.effect(() => {
180
188
  // CRITICAL: Read the signals FIRST to establish dependencies, even on first run
181
- const minPrice = minPriceSignal.get();
182
- const maxPrice = maxPriceSignal.get();
189
+ const minPrice = userFilterMinPriceSignal.get();
190
+ const maxPrice = userFilterMaxPriceSignal.get();
183
191
  const selectedInventoryStatuses = selectedInventoryStatusesSignal.get();
184
192
  const selectedProductOptions = selectedProductOptionsSignal.get();
185
193
  if (firstRun) {
186
194
  firstRun = false;
187
195
  return;
188
196
  }
189
- isFilteredSignal.set(true);
190
- // Build new search options with updated price filters
191
- const newSearchOptions = {
192
- ...productsListService.searchOptions.peek(),
193
- };
194
- delete newSearchOptions.cursorPaging?.cursor;
195
- // Initialize filter if it doesn't exist
196
- if (!newSearchOptions.filter) {
197
- newSearchOptions.filter = {};
198
- }
199
- else {
200
- // Copy existing filter to avoid mutation
201
- newSearchOptions.filter = { ...newSearchOptions.filter };
202
- }
203
- // Remove existing price filters
204
- delete newSearchOptions.filter["actualPriceRange.minValue.amount"];
205
- delete newSearchOptions.filter["actualPriceRange.maxValue.amount"];
206
- // Remove existing inventory filter
207
- delete newSearchOptions.filter["inventory.availabilityStatus"];
208
- // Remove existing product option filters
209
- // First, find and remove any existing option filters
210
- Object.keys(newSearchOptions.filter).forEach((key) => {
211
- if (key.startsWith("options.")) {
212
- delete newSearchOptions.filter[key];
213
- }
214
- });
215
- // Add new price filters if they have valid values
216
- if (minPrice > 0) {
217
- newSearchOptions.filter["actualPriceRange.minValue.amount"] = { $gte: minPrice };
218
- }
219
- if (maxPrice > 0) {
220
- newSearchOptions.filter["actualPriceRange.maxValue.amount"] = { $lte: maxPrice };
221
- }
222
- // Add new inventory filter if there are selected statuses
223
- if (selectedInventoryStatuses.length > 0) {
224
- if (selectedInventoryStatuses.length === 1) {
225
- newSearchOptions.filter["inventory.availabilityStatus"] =
226
- selectedInventoryStatuses[0];
227
- }
228
- else {
229
- newSearchOptions.filter["inventory.availabilityStatus"] =
230
- {
231
- $in: selectedInventoryStatuses,
232
- };
233
- }
234
- }
235
- // Add new product option filters if there are selected options
236
- if (selectedProductOptions &&
237
- Object.keys(selectedProductOptions).length > 0) {
238
- for (const [optionId, choiceIds] of Object.entries(selectedProductOptions)) {
239
- if (choiceIds && choiceIds.length > 0) {
240
- // Handle inventory filter separately
241
- if (optionId === "inventory-filter") {
242
- newSearchOptions.filter["inventory.availabilityStatus"] = {
243
- $in: choiceIds,
244
- };
245
- }
246
- else {
247
- // Regular product options filter
248
- newSearchOptions.filter["options.choicesSettings.choices.choiceId"] = {
249
- $hasSome: choiceIds,
250
- };
251
- }
252
- }
253
- }
254
- }
255
- // Use callback to update search options
256
- productsListService.setSearchOptions(newSearchOptions);
197
+ doFirstRunInit(isFilteredSignal, productsListService, minPrice, maxPrice, selectedInventoryStatuses, selectedProductOptions);
257
198
  });
258
199
  }
259
200
  return {
260
- minPrice: minPriceSignal,
261
- maxPrice: maxPriceSignal,
201
+ selectedMinPrice: userFilterMinPriceSignal,
202
+ selectedMaxPrice: userFilterMaxPriceSignal,
203
+ availableMinPrice: catalogMinPriceSignal,
204
+ availableMaxPrice: catalogMaxPriceSignal,
262
205
  availableInventoryStatuses: availableInventoryStatusesSignal,
263
206
  selectedInventoryStatuses: selectedInventoryStatusesSignal,
264
207
  availableProductOptions: availableProductOptionsSignal,
265
208
  selectedProductOptions: selectedProductOptionsSignal,
266
- setMinPrice: (minPrice) => {
267
- minPriceSignal.set(minPrice);
209
+ setSelectedMinPrice: (minPrice) => {
210
+ // Clear any existing timeout
211
+ if (minPriceTimeoutId) {
212
+ clearTimeout(minPriceTimeoutId);
213
+ }
214
+ // Set new debounced timeout
215
+ minPriceTimeoutId = setTimeout(() => {
216
+ userFilterMinPriceSignal.set(minPrice);
217
+ minPriceTimeoutId = null;
218
+ }, PRICE_FILTER_DEBOUNCE_TIME);
268
219
  },
269
- setMaxPrice: (maxPrice) => {
270
- maxPriceSignal.set(maxPrice);
220
+ setSelectedMaxPrice: (maxPrice) => {
221
+ // Clear any existing timeout
222
+ if (maxPriceTimeoutId) {
223
+ clearTimeout(maxPriceTimeoutId);
224
+ }
225
+ // Set new debounced timeout
226
+ maxPriceTimeoutId = setTimeout(() => {
227
+ userFilterMaxPriceSignal.set(maxPrice);
228
+ maxPriceTimeoutId = null;
229
+ }, PRICE_FILTER_DEBOUNCE_TIME);
271
230
  },
272
231
  toggleInventoryStatus: (status) => {
273
232
  const current = selectedInventoryStatusesSignal.get();
@@ -308,16 +267,100 @@ export const ProductsListFiltersService = implementService.withConfig()(Products
308
267
  },
309
268
  isFiltered: isFilteredSignal,
310
269
  reset: () => {
311
- // TODO: reset the filters to the original values from the aggregation data
312
- minPriceSignal.set(0);
313
- maxPriceSignal.set(0);
270
+ // Reset user selections but keep catalog bounds intact
271
+ userFilterMinPriceSignal.set(catalogMinPriceSignal.get());
272
+ userFilterMaxPriceSignal.set(catalogMaxPriceSignal.get());
314
273
  selectedInventoryStatusesSignal.set([]);
315
274
  selectedProductOptionsSignal.set({});
316
275
  isFilteredSignal.set(false);
317
276
  },
318
277
  };
319
278
  });
320
- function getMinPrice(searchOptions) {
279
+ function doFirstRunInit(isFilteredSignal, productsListService, minPrice, maxPrice, selectedInventoryStatuses, selectedProductOptions) {
280
+ isFilteredSignal.set(true);
281
+ // Build new search options with updated price filters
282
+ const newSearchOptions = {
283
+ ...productsListService.searchOptions.peek(),
284
+ };
285
+ delete newSearchOptions.cursorPaging?.cursor;
286
+ // Initialize filter if it doesn't exist
287
+ if (!newSearchOptions.filter) {
288
+ newSearchOptions.filter = {};
289
+ }
290
+ else {
291
+ // Copy existing filter to avoid mutation
292
+ newSearchOptions.filter = { ...newSearchOptions.filter };
293
+ }
294
+ // Remove existing price filters
295
+ delete newSearchOptions.filter["actualPriceRange.minValue.amount"];
296
+ delete newSearchOptions.filter["actualPriceRange.maxValue.amount"];
297
+ // Remove existing inventory filter
298
+ delete newSearchOptions.filter["inventory.availabilityStatus"];
299
+ // Remove existing product option filters
300
+ // First, find and remove any existing option filters
301
+ Object.keys(newSearchOptions.filter).forEach((key) => {
302
+ if (key.startsWith("options.")) {
303
+ delete newSearchOptions.filter[key];
304
+ }
305
+ });
306
+ // Add new price filters if they have valid values
307
+ if (minPrice > 0) {
308
+ newSearchOptions.filter["actualPriceRange.minValue.amount"] = {
309
+ $gte: minPrice,
310
+ };
311
+ }
312
+ if (maxPrice > 0) {
313
+ newSearchOptions.filter["actualPriceRange.maxValue.amount"] = {
314
+ $lte: maxPrice,
315
+ };
316
+ }
317
+ // Add new inventory filter if there are selected statuses
318
+ if (selectedInventoryStatuses.length > 0) {
319
+ if (selectedInventoryStatuses.length === 1) {
320
+ newSearchOptions.filter["inventory.availabilityStatus"] =
321
+ selectedInventoryStatuses[0];
322
+ }
323
+ else {
324
+ newSearchOptions.filter["inventory.availabilityStatus"] = {
325
+ $in: selectedInventoryStatuses,
326
+ };
327
+ }
328
+ }
329
+ // Add new product option filters if there are selected options
330
+ if (selectedProductOptions &&
331
+ Object.keys(selectedProductOptions).length > 0) {
332
+ for (const [optionId, choiceIds] of Object.entries(selectedProductOptions)) {
333
+ if (choiceIds && choiceIds.length > 0) {
334
+ // Handle inventory filter separately
335
+ if (optionId === "inventory-filter") {
336
+ newSearchOptions.filter["inventory.availabilityStatus"] = {
337
+ $in: choiceIds,
338
+ };
339
+ }
340
+ else {
341
+ // Regular product options filter
342
+ newSearchOptions.filter["options.choicesSettings.choices.choiceId"] = {
343
+ $hasSome: choiceIds,
344
+ };
345
+ }
346
+ }
347
+ }
348
+ }
349
+ // Use callback to update search options
350
+ productsListService.setSearchOptions(newSearchOptions);
351
+ }
352
+ /**
353
+ * Gets the full catalog price range from aggregation data (before any filters applied)
354
+ */
355
+ function getCatalogPriceRange(aggregationData) {
356
+ const minPrice = getMinPrice(aggregationData);
357
+ const maxPrice = getMaxPrice(aggregationData);
358
+ return { minPrice, maxPrice };
359
+ }
360
+ /**
361
+ * Gets the user's currently selected minimum price filter from search options
362
+ */
363
+ function getSelectedMinPrice(searchOptions) {
321
364
  const filter = searchOptions.filter;
322
365
  if (!filter)
323
366
  return 0;
@@ -325,11 +368,13 @@ function getMinPrice(searchOptions) {
325
368
  if (typeof minPriceFilter === "object" &&
326
369
  minPriceFilter !== null &&
327
370
  "$gte" in minPriceFilter) {
328
- return Number(minPriceFilter.$gte) || 0;
371
+ return Number(minPriceFilter.$gte);
329
372
  }
330
- return 0;
331
373
  }
332
- function getMaxPrice(searchOptions) {
374
+ /**
375
+ * Gets the user's currently selected maximum price filter from search options
376
+ */
377
+ function getSelectedMaxPrice(searchOptions) {
333
378
  const filter = searchOptions.filter;
334
379
  if (!filter)
335
380
  return 0;
@@ -337,7 +382,20 @@ function getMaxPrice(searchOptions) {
337
382
  if (typeof maxPriceFilter === "object" &&
338
383
  maxPriceFilter !== null &&
339
384
  "$lte" in maxPriceFilter) {
340
- return Number(maxPriceFilter.$lte) || 0;
385
+ return Number(maxPriceFilter.$lte);
386
+ }
387
+ }
388
+ function getMinPrice(aggregationData) {
389
+ const minPriceAggregation = aggregationData.find((data) => data.fieldPath === "actualPriceRange.minValue.amount");
390
+ if (minPriceAggregation?.scalar?.value) {
391
+ return Number(minPriceAggregation.scalar.value) || 0;
392
+ }
393
+ return 0;
394
+ }
395
+ function getMaxPrice(aggregationData) {
396
+ const maxPriceAggregation = aggregationData.find((data) => data.fieldPath === "actualPriceRange.maxValue.amount");
397
+ if (maxPriceAggregation?.scalar?.value) {
398
+ return Number(maxPriceAggregation.scalar.value) || 0;
341
399
  }
342
400
  return 0;
343
401
  }
@@ -1,6 +1,6 @@
1
1
  import { defineService, implementService } from "@wix/services-definitions";
2
2
  import { SignalsServiceDefinition, } from "@wix/services-definitions/core-services/signals";
3
- import { productsV3 } from "@wix/stores";
3
+ import { productsV3, readOnlyVariantsV3 } from "@wix/stores";
4
4
  /**
5
5
  * Loads products list service configuration from the Wix Stores API for SSR initialization.
6
6
  * This function is designed to be used during Server-Side Rendering (SSR) to preload
@@ -83,7 +83,7 @@ import { productsV3 } from "@wix/stores";
83
83
  * ```
84
84
  */
85
85
  export async function loadProductsListServiceConfig(searchOptions) {
86
- const result = await productsV3.searchProducts(searchOptions);
86
+ const result = await fetchProducts(searchOptions);
87
87
  return {
88
88
  products: result.products ?? [],
89
89
  searchOptions,
@@ -91,6 +91,90 @@ export async function loadProductsListServiceConfig(searchOptions) {
91
91
  aggregations: result.aggregationData,
92
92
  };
93
93
  }
94
+ /**
95
+ * Fetches products and their missing variants in one optimized request.
96
+ * This function wraps the standard searchProducts call and automatically
97
+ * fetches missing variant data for all products that need it.
98
+ *
99
+ * @param searchOptions - The search options for querying products
100
+ * @returns Promise that resolves to the search result with complete variant data
101
+ */
102
+ const fetchProducts = async (searchOptions) => {
103
+ const result = await productsV3.searchProducts(searchOptions);
104
+ // Fetch missing variants for all products in one batch request
105
+ if (result.products) {
106
+ result.products = await fetchMissingVariants(result.products);
107
+ }
108
+ return result;
109
+ };
110
+ /**
111
+ * Fetches missing variants for all products in one batch request.
112
+ * This function identifies products that need variant data and fetches
113
+ * all variants efficiently using the readOnlyVariantsV3 API.
114
+ *
115
+ * @param products - Array of products that may need variant data
116
+ * @returns Promise that resolves to products with complete variant information
117
+ */
118
+ const fetchMissingVariants = async (products) => {
119
+ // Find products that need variants (both single and multi-variant products)
120
+ const productsNeedingVariants = products.filter((product) => !product.variantsInfo?.variants &&
121
+ product.variantSummary?.variantCount &&
122
+ product.variantSummary.variantCount > 0);
123
+ if (productsNeedingVariants.length === 0) {
124
+ return products;
125
+ }
126
+ try {
127
+ const productIds = productsNeedingVariants
128
+ .map((p) => p._id)
129
+ .filter(Boolean);
130
+ if (productIds.length === 0) {
131
+ return products;
132
+ }
133
+ const items = [];
134
+ const res = await readOnlyVariantsV3
135
+ .queryVariants({})
136
+ .in("productData.productId", productIds)
137
+ .limit(100)
138
+ .find();
139
+ items.push(...res.items);
140
+ let nextRes = res;
141
+ while (nextRes.hasNext()) {
142
+ nextRes = await nextRes.next();
143
+ items.push(...nextRes.items);
144
+ }
145
+ const variantsByProductId = new Map();
146
+ items.forEach((item) => {
147
+ const productId = item.productData?.productId;
148
+ if (productId) {
149
+ if (!variantsByProductId.has(productId)) {
150
+ variantsByProductId.set(productId, []);
151
+ }
152
+ variantsByProductId.get(productId).push({
153
+ ...item,
154
+ choices: item.optionChoices,
155
+ });
156
+ }
157
+ });
158
+ // Update products with their variants
159
+ return products.map((product) => {
160
+ const variants = variantsByProductId.get(product._id || "");
161
+ if (variants && variants.length > 0) {
162
+ return {
163
+ ...product,
164
+ variantsInfo: {
165
+ ...product.variantsInfo,
166
+ variants,
167
+ },
168
+ };
169
+ }
170
+ return product;
171
+ });
172
+ }
173
+ catch (error) {
174
+ console.error("Failed to fetch missing variants:", error);
175
+ return products;
176
+ }
177
+ };
94
178
  /**
95
179
  * Service definition for the Products List service.
96
180
  * This defines the reactive API contract for managing a list of products with search, pagination, and filtering capabilities.
@@ -179,7 +263,7 @@ export const ProductListService = implementService.withConfig()(ProductsListServ
179
263
  },
180
264
  }
181
265
  : searchOptions;
182
- const result = await productsV3.searchProducts(affectiveSearchOptions);
266
+ const result = await fetchProducts(affectiveSearchOptions);
183
267
  productsSignal.set(result.products ?? []);
184
268
  pagingMetadataSignal.set(result.pagingMetadata);
185
269
  }
@@ -1,5 +1,8 @@
1
- import type { PropsWithChildren, ReactNode } from "react";
2
1
  import { type CategoryServiceConfig } from "../services/category-service.js";
2
+ export interface RootProps {
3
+ children: React.ReactNode;
4
+ categoryServiceConfig: CategoryServiceConfig;
5
+ }
3
6
  /**
4
7
  * Root component that provides the Category service context to its children.
5
8
  * This component sets up the necessary services for managing category state.
@@ -24,15 +27,13 @@ import { type CategoryServiceConfig } from "../services/category-service.js";
24
27
  * }
25
28
  * ```
26
29
  */
27
- export declare function Root(props: PropsWithChildren<{
28
- categoryServiceConfig: CategoryServiceConfig;
29
- }>): import("react/jsx-runtime").JSX.Element;
30
+ export declare function Root(props: RootProps): React.ReactNode;
30
31
  /**
31
32
  * Props for Name headless component
32
33
  */
33
34
  export interface NameProps {
34
35
  /** Content to display (can be a render function receiving name data or ReactNode) */
35
- children: ((props: NameRenderProps) => ReactNode) | ReactNode;
36
+ children: ((props: NameRenderProps) => React.ReactNode) | React.ReactNode;
36
37
  }
37
38
  /**
38
39
  * Render props for Name component
@@ -60,13 +61,13 @@ export interface NameRenderProps {
60
61
  * }
61
62
  * ```
62
63
  */
63
- export declare function Name(props: NameProps): ReactNode;
64
+ export declare function Name(props: NameProps): import("react").ReactNode;
64
65
  /**
65
66
  * Props for Slug headless component
66
67
  */
67
68
  export interface SlugProps {
68
69
  /** Content to display (can be a render function receiving slug data or ReactNode) */
69
- children: ((props: SlugRenderProps) => ReactNode) | ReactNode;
70
+ children: ((props: SlugRenderProps) => React.ReactNode) | React.ReactNode;
70
71
  }
71
72
  /**
72
73
  * Render props for Slug component
@@ -94,4 +95,4 @@ export interface SlugRenderProps {
94
95
  * }
95
96
  * ```
96
97
  */
97
- export declare function Slug(props: SlugProps): ReactNode;
98
+ export declare function Slug(props: SlugProps): import("react").ReactNode;