@wix/headless-stores 0.0.41 → 0.0.43
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.
- package/cjs/dist/react/ProductList.d.ts +31 -3
- package/cjs/dist/react/ProductList.js +33 -5
- package/cjs/dist/react/ProductListFilters.d.ts +100 -111
- package/cjs/dist/react/ProductListFilters.js +105 -115
- package/cjs/dist/react/ProductListPagination.d.ts +89 -96
- package/cjs/dist/react/ProductListPagination.js +96 -104
- package/cjs/dist/react/ProductListSort.d.ts +26 -57
- package/cjs/dist/react/ProductListSort.js +26 -58
- package/cjs/dist/services/index.d.ts +1 -3
- package/cjs/dist/services/index.js +1 -3
- package/cjs/dist/services/products-list-search-service.d.ts +220 -0
- package/cjs/dist/services/products-list-search-service.js +813 -0
- package/cjs/dist/services/products-list-service.d.ts +28 -11
- package/cjs/dist/services/products-list-service.js +26 -6
- package/dist/react/ProductList.d.ts +31 -3
- package/dist/react/ProductList.js +33 -5
- package/dist/react/ProductListFilters.d.ts +100 -111
- package/dist/react/ProductListFilters.js +105 -115
- package/dist/react/ProductListPagination.d.ts +89 -96
- package/dist/react/ProductListPagination.js +96 -104
- package/dist/react/ProductListSort.d.ts +26 -57
- package/dist/react/ProductListSort.js +26 -58
- package/dist/services/index.d.ts +1 -3
- package/dist/services/index.js +1 -3
- package/dist/services/products-list-search-service.d.ts +220 -0
- package/dist/services/products-list-search-service.js +813 -0
- package/dist/services/products-list-service.d.ts +28 -11
- package/dist/services/products-list-service.js +26 -6
- package/package.json +2 -2
- package/cjs/dist/services/products-list-filters-service.d.ts +0 -309
- package/cjs/dist/services/products-list-filters-service.js +0 -504
- package/cjs/dist/services/products-list-pagination-service.d.ts +0 -186
- package/cjs/dist/services/products-list-pagination-service.js +0 -179
- package/cjs/dist/services/products-list-sort-service.d.ts +0 -117
- package/cjs/dist/services/products-list-sort-service.js +0 -144
- package/dist/services/products-list-filters-service.d.ts +0 -309
- package/dist/services/products-list-filters-service.js +0 -504
- package/dist/services/products-list-pagination-service.d.ts +0 -186
- package/dist/services/products-list-pagination-service.js +0 -179
- package/dist/services/products-list-sort-service.d.ts +0 -117
- package/dist/services/products-list-sort-service.js +0 -144
|
@@ -1,504 +0,0 @@
|
|
|
1
|
-
import { defineService } from "@wix/services-definitions";
|
|
2
|
-
import { implementService } from "@wix/services-definitions";
|
|
3
|
-
import { SignalsServiceDefinition, } from "@wix/services-definitions/core-services/signals";
|
|
4
|
-
import { productsV3 } from "@wix/stores";
|
|
5
|
-
import { ProductsListServiceDefinition } from "./products-list-service.js";
|
|
6
|
-
import { customizationsV3 } from "@wix/stores";
|
|
7
|
-
const PRICE_FILTER_DEBOUNCE_TIME = 300;
|
|
8
|
-
/**
|
|
9
|
-
* Enumeration of inventory status types available for filtering.
|
|
10
|
-
* Maps to the Wix Stores API inventory availability statuses.
|
|
11
|
-
*
|
|
12
|
-
* @enum {string}
|
|
13
|
-
*/
|
|
14
|
-
export var InventoryStatusType;
|
|
15
|
-
(function (InventoryStatusType) {
|
|
16
|
-
/** Product is in stock and available for purchase */
|
|
17
|
-
InventoryStatusType["IN_STOCK"] = "IN_STOCK";
|
|
18
|
-
/** Product is out of stock */
|
|
19
|
-
InventoryStatusType["OUT_OF_STOCK"] = "OUT_OF_STOCK";
|
|
20
|
-
/** Product is partially out of stock (some variants available) */
|
|
21
|
-
InventoryStatusType["PARTIALLY_OUT_OF_STOCK"] = "PARTIALLY_OUT_OF_STOCK";
|
|
22
|
-
})(InventoryStatusType || (InventoryStatusType = {}));
|
|
23
|
-
/**
|
|
24
|
-
* Loads products list filters service configuration from the Wix Stores API for SSR initialization.
|
|
25
|
-
* This function fetches customization data that will be used to build product filter options.
|
|
26
|
-
*
|
|
27
|
-
* @returns {Promise<ProductsListFiltersServiceConfig>} Promise that resolves to the filters configuration
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```astro
|
|
31
|
-
* ---
|
|
32
|
-
* // Astro page example - pages/products.astro
|
|
33
|
-
* import { loadProductsListFiltersServiceConfig } from '@wix/stores/services';
|
|
34
|
-
* import { ProductsListFilters } from '@wix/stores/components';
|
|
35
|
-
*
|
|
36
|
-
* // Load filters configuration during SSR
|
|
37
|
-
* const filtersConfig = await loadProductsListFiltersServiceConfig();
|
|
38
|
-
* ---
|
|
39
|
-
*
|
|
40
|
-
* <ProductsListFilters.Root filtersConfig={filtersConfig}>
|
|
41
|
-
* <ProductsListFilters.MinPrice>
|
|
42
|
-
* {({ minPrice, setMinPrice }) => (
|
|
43
|
-
* <input
|
|
44
|
-
* type="number"
|
|
45
|
-
* value={minPrice}
|
|
46
|
-
* onChange={(e) => setMinPrice(parseFloat(e.target.value))}
|
|
47
|
-
* placeholder="Min Price"
|
|
48
|
-
* />
|
|
49
|
-
* )}
|
|
50
|
-
* </ProductsListFilters.MinPrice>
|
|
51
|
-
* </ProductsListFilters.Root>
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* ```tsx
|
|
56
|
-
* // Next.js page example
|
|
57
|
-
* import { GetServerSideProps } from 'next';
|
|
58
|
-
* import { loadProductsListFiltersServiceConfig } from '@wix/stores/services';
|
|
59
|
-
*
|
|
60
|
-
* export const getServerSideProps: GetServerSideProps = async () => {
|
|
61
|
-
* const filtersConfig = await loadProductsListFiltersServiceConfig();
|
|
62
|
-
*
|
|
63
|
-
* return {
|
|
64
|
-
* props: {
|
|
65
|
-
* filtersConfig,
|
|
66
|
-
* },
|
|
67
|
-
* };
|
|
68
|
-
* };
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
export async function loadProductsListFiltersServiceConfig() {
|
|
72
|
-
const { items: customizations = [] } = await customizationsV3
|
|
73
|
-
.queryCustomizations()
|
|
74
|
-
.find();
|
|
75
|
-
return {
|
|
76
|
-
customizations,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Service definition for the Products List Filters service.
|
|
81
|
-
* This defines the reactive API contract for managing product list filtering capabilities
|
|
82
|
-
* including price, inventory status, and product option filters.
|
|
83
|
-
*
|
|
84
|
-
* @constant
|
|
85
|
-
*/
|
|
86
|
-
export const ProductsListFiltersServiceDefinition = defineService("products-list-filters");
|
|
87
|
-
/**
|
|
88
|
-
* Implementation of the Products List Filters service that manages reactive filtering state.
|
|
89
|
-
* This service provides signals for all filter types (price, inventory, product options) and
|
|
90
|
-
* automatically updates the products list search options when filters change.
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* ```tsx
|
|
94
|
-
* import { ProductsListFiltersService, ProductsListFiltersServiceDefinition } from '@wix/stores/services';
|
|
95
|
-
* import { useService } from '@wix/services-manager-react';
|
|
96
|
-
*
|
|
97
|
-
* function FiltersComponent({ filtersConfig }) {
|
|
98
|
-
* return (
|
|
99
|
-
* <ServiceProvider services={createServicesMap([
|
|
100
|
-
* [ProductsListFiltersServiceDefinition, ProductsListFiltersService.withConfig(filtersConfig)]
|
|
101
|
-
* ])}>
|
|
102
|
-
* <FilterControls />
|
|
103
|
-
* </ServiceProvider>
|
|
104
|
-
* );
|
|
105
|
-
* }
|
|
106
|
-
*
|
|
107
|
-
* function FilterControls() {
|
|
108
|
-
* const filtersService = useService(ProductsListFiltersServiceDefinition);
|
|
109
|
-
* const minPrice = filtersService.minPrice.get();
|
|
110
|
-
* const maxPrice = filtersService.maxPrice.get();
|
|
111
|
-
* const selectedInventoryStatuses = filtersService.selectedInventoryStatuses.get();
|
|
112
|
-
* const availableProductOptions = filtersService.availableProductOptions.get();
|
|
113
|
-
* const isFiltered = filtersService.isFiltered.get();
|
|
114
|
-
*
|
|
115
|
-
* return (
|
|
116
|
-
* <div>
|
|
117
|
-
* <div>
|
|
118
|
-
* <input
|
|
119
|
-
* type="number"
|
|
120
|
-
* value={minPrice}
|
|
121
|
-
* onChange={(e) => filtersService.setMinPrice(parseFloat(e.target.value))}
|
|
122
|
-
* placeholder="Min Price"
|
|
123
|
-
* />
|
|
124
|
-
* <input
|
|
125
|
-
* type="number"
|
|
126
|
-
* value={maxPrice}
|
|
127
|
-
* onChange={(e) => filtersService.setMaxPrice(parseFloat(e.target.value))}
|
|
128
|
-
* placeholder="Max Price"
|
|
129
|
-
* />
|
|
130
|
-
* </div>
|
|
131
|
-
*
|
|
132
|
-
* {availableProductOptions.map(option => (
|
|
133
|
-
* <div key={option.id}>
|
|
134
|
-
* <h4>{option.name}</h4>
|
|
135
|
-
* {option.choices.map(choice => (
|
|
136
|
-
* <label key={choice.id}>
|
|
137
|
-
* <input
|
|
138
|
-
* type="checkbox"
|
|
139
|
-
* onChange={() => filtersService.toggleProductOption(option.id, choice.id)}
|
|
140
|
-
* />
|
|
141
|
-
* {choice.name}
|
|
142
|
-
* </label>
|
|
143
|
-
* ))}
|
|
144
|
-
* </div>
|
|
145
|
-
* ))}
|
|
146
|
-
*
|
|
147
|
-
* {isFiltered && (
|
|
148
|
-
* <button onClick={() => filtersService.reset()}>
|
|
149
|
-
* Clear All Filters
|
|
150
|
-
* </button>
|
|
151
|
-
* )}
|
|
152
|
-
* </div>
|
|
153
|
-
* );
|
|
154
|
-
* }
|
|
155
|
-
* }
|
|
156
|
-
* ```
|
|
157
|
-
*/
|
|
158
|
-
export const ProductsListFiltersService = implementService.withConfig()(ProductsListFiltersServiceDefinition, ({ getService, config }) => {
|
|
159
|
-
let firstRun = true;
|
|
160
|
-
const signalsService = getService(SignalsServiceDefinition);
|
|
161
|
-
const productsListService = getService(ProductsListServiceDefinition);
|
|
162
|
-
const { customizations } = config;
|
|
163
|
-
const aggregationData = productsListService.aggregations.get()?.results;
|
|
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);
|
|
173
|
-
const availableInventoryStatusesSignal = signalsService.signal([
|
|
174
|
-
InventoryStatusType.IN_STOCK,
|
|
175
|
-
InventoryStatusType.OUT_OF_STOCK,
|
|
176
|
-
InventoryStatusType.PARTIALLY_OUT_OF_STOCK,
|
|
177
|
-
]);
|
|
178
|
-
const selectedInventoryStatusesSignal = signalsService.signal(getSelectedInventoryStatuses(currentSearchOptions));
|
|
179
|
-
// TODO: Get product options from aggregations data
|
|
180
|
-
const availableProductOptionsSignal = signalsService.signal(getAvailableProductOptions(aggregationData, customizations));
|
|
181
|
-
const selectedProductOptionsSignal = signalsService.signal(getSelectedProductOptions(currentSearchOptions));
|
|
182
|
-
const isFilteredSignal = signalsService.signal(false);
|
|
183
|
-
// Debounce timeout IDs for price filters
|
|
184
|
-
let minPriceTimeoutId = null;
|
|
185
|
-
let maxPriceTimeoutId = null;
|
|
186
|
-
if (typeof window !== "undefined") {
|
|
187
|
-
signalsService.effect(() => {
|
|
188
|
-
// CRITICAL: Read the signals FIRST to establish dependencies, even on first run
|
|
189
|
-
const minPrice = userFilterMinPriceSignal.get();
|
|
190
|
-
const maxPrice = userFilterMaxPriceSignal.get();
|
|
191
|
-
const selectedInventoryStatuses = selectedInventoryStatusesSignal.get();
|
|
192
|
-
const selectedProductOptions = selectedProductOptionsSignal.get();
|
|
193
|
-
if (firstRun) {
|
|
194
|
-
firstRun = false;
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
doFirstRunInit(isFilteredSignal, productsListService, minPrice, maxPrice, selectedInventoryStatuses, selectedProductOptions);
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
selectedMinPrice: userFilterMinPriceSignal,
|
|
202
|
-
selectedMaxPrice: userFilterMaxPriceSignal,
|
|
203
|
-
availableMinPrice: catalogMinPriceSignal,
|
|
204
|
-
availableMaxPrice: catalogMaxPriceSignal,
|
|
205
|
-
availableInventoryStatuses: availableInventoryStatusesSignal,
|
|
206
|
-
selectedInventoryStatuses: selectedInventoryStatusesSignal,
|
|
207
|
-
availableProductOptions: availableProductOptionsSignal,
|
|
208
|
-
selectedProductOptions: selectedProductOptionsSignal,
|
|
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);
|
|
219
|
-
},
|
|
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);
|
|
230
|
-
},
|
|
231
|
-
toggleInventoryStatus: (status) => {
|
|
232
|
-
const current = selectedInventoryStatusesSignal.get();
|
|
233
|
-
const isSelected = current.includes(status);
|
|
234
|
-
if (isSelected) {
|
|
235
|
-
selectedInventoryStatusesSignal.set(current.filter((s) => s !== status));
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
selectedInventoryStatusesSignal.set([...current, status]);
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
toggleProductOption: (optionId, choiceId) => {
|
|
242
|
-
const current = selectedProductOptionsSignal.get();
|
|
243
|
-
const currentChoices = current[optionId] || [];
|
|
244
|
-
const isSelected = currentChoices.includes(choiceId);
|
|
245
|
-
if (isSelected) {
|
|
246
|
-
// Remove the choice
|
|
247
|
-
const newChoices = currentChoices.filter((id) => id !== choiceId);
|
|
248
|
-
if (newChoices.length === 0) {
|
|
249
|
-
const newOptions = { ...current };
|
|
250
|
-
delete newOptions[optionId];
|
|
251
|
-
selectedProductOptionsSignal.set(newOptions);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
selectedProductOptionsSignal.set({
|
|
255
|
-
...current,
|
|
256
|
-
[optionId]: newChoices,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
// Add the choice
|
|
262
|
-
selectedProductOptionsSignal.set({
|
|
263
|
-
...current,
|
|
264
|
-
[optionId]: [...currentChoices, choiceId],
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
isFiltered: isFilteredSignal,
|
|
269
|
-
reset: () => {
|
|
270
|
-
// Reset user selections but keep catalog bounds intact
|
|
271
|
-
userFilterMinPriceSignal.set(catalogMinPriceSignal.get());
|
|
272
|
-
userFilterMaxPriceSignal.set(catalogMaxPriceSignal.get());
|
|
273
|
-
selectedInventoryStatusesSignal.set([]);
|
|
274
|
-
selectedProductOptionsSignal.set({});
|
|
275
|
-
isFilteredSignal.set(false);
|
|
276
|
-
},
|
|
277
|
-
};
|
|
278
|
-
});
|
|
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) {
|
|
364
|
-
const filter = searchOptions.filter;
|
|
365
|
-
if (!filter)
|
|
366
|
-
return 0;
|
|
367
|
-
const minPriceFilter = filter["actualPriceRange.minValue.amount"];
|
|
368
|
-
if (typeof minPriceFilter === "object" &&
|
|
369
|
-
minPriceFilter !== null &&
|
|
370
|
-
"$gte" in minPriceFilter) {
|
|
371
|
-
return Number(minPriceFilter.$gte);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Gets the user's currently selected maximum price filter from search options
|
|
376
|
-
*/
|
|
377
|
-
function getSelectedMaxPrice(searchOptions) {
|
|
378
|
-
const filter = searchOptions.filter;
|
|
379
|
-
if (!filter)
|
|
380
|
-
return 0;
|
|
381
|
-
const maxPriceFilter = filter["actualPriceRange.maxValue.amount"];
|
|
382
|
-
if (typeof maxPriceFilter === "object" &&
|
|
383
|
-
maxPriceFilter !== null &&
|
|
384
|
-
"$lte" in maxPriceFilter) {
|
|
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;
|
|
399
|
-
}
|
|
400
|
-
return 0;
|
|
401
|
-
}
|
|
402
|
-
function getSelectedInventoryStatuses(searchOptions) {
|
|
403
|
-
const filter = searchOptions.filter;
|
|
404
|
-
if (!filter)
|
|
405
|
-
return [];
|
|
406
|
-
const inventoryFilter = filter["inventory.availabilityStatus"];
|
|
407
|
-
if (typeof inventoryFilter === "string" && inventoryFilter.length > 0) {
|
|
408
|
-
return [inventoryFilter];
|
|
409
|
-
}
|
|
410
|
-
if (typeof inventoryFilter === "object" &&
|
|
411
|
-
inventoryFilter !== null &&
|
|
412
|
-
"$in" in inventoryFilter) {
|
|
413
|
-
return Array.isArray(inventoryFilter.$in) ? inventoryFilter.$in : [];
|
|
414
|
-
}
|
|
415
|
-
return [];
|
|
416
|
-
}
|
|
417
|
-
function getSelectedProductOptions(searchOptions) {
|
|
418
|
-
const filter = searchOptions.filter;
|
|
419
|
-
if (!filter)
|
|
420
|
-
return {};
|
|
421
|
-
const selectedOptions = {};
|
|
422
|
-
// Look for options.{optionId}.choice filters
|
|
423
|
-
Object.keys(filter).forEach((key) => {
|
|
424
|
-
if (key.startsWith("options.") && key.endsWith(".choice")) {
|
|
425
|
-
const optionId = key.slice(8, -7); // Remove "options." and ".choice"
|
|
426
|
-
const optionFilter = filter[key];
|
|
427
|
-
if (typeof optionFilter === "string" && optionFilter.length > 0) {
|
|
428
|
-
selectedOptions[optionId] = [optionFilter];
|
|
429
|
-
}
|
|
430
|
-
else if (typeof optionFilter === "object" &&
|
|
431
|
-
optionFilter !== null &&
|
|
432
|
-
"$in" in optionFilter &&
|
|
433
|
-
Array.isArray(optionFilter.$in)) {
|
|
434
|
-
selectedOptions[optionId] = optionFilter.$in;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
return selectedOptions;
|
|
439
|
-
}
|
|
440
|
-
function getAvailableProductOptions(aggregationData = [], customizations = []) {
|
|
441
|
-
// Helper function to match aggregation names case-insensitively
|
|
442
|
-
const matchesAggregationName = (name, aggregationNames) => {
|
|
443
|
-
return aggregationNames.some((aggName) => aggName.toLowerCase() === name.toLowerCase());
|
|
444
|
-
};
|
|
445
|
-
// Helper function to sort choices intelligently (numbers first, then alphabetically)
|
|
446
|
-
const sortChoicesIntelligently = (choices) => {
|
|
447
|
-
return [...choices].sort((a, b) => {
|
|
448
|
-
const aIsNumber = /^\d+$/.test(a.name);
|
|
449
|
-
const bIsNumber = /^\d+$/.test(b.name);
|
|
450
|
-
if (aIsNumber && bIsNumber) {
|
|
451
|
-
return parseInt(b.name) - parseInt(a.name);
|
|
452
|
-
}
|
|
453
|
-
if (aIsNumber && !bIsNumber)
|
|
454
|
-
return -1;
|
|
455
|
-
if (!aIsNumber && bIsNumber)
|
|
456
|
-
return 1;
|
|
457
|
-
return a.name.localeCompare(b.name);
|
|
458
|
-
});
|
|
459
|
-
};
|
|
460
|
-
// Extract option names from aggregation data
|
|
461
|
-
const optionNames = [];
|
|
462
|
-
const choiceNames = [];
|
|
463
|
-
// Process aggregation results to extract available option and choice names
|
|
464
|
-
aggregationData.forEach((result) => {
|
|
465
|
-
if (result.name === "optionNames" && result.values?.results) {
|
|
466
|
-
optionNames.push(...result.values.results
|
|
467
|
-
.map((item) => item.value)
|
|
468
|
-
.filter((value) => typeof value === "string"));
|
|
469
|
-
}
|
|
470
|
-
if (result.name === "choiceNames" && result.values?.results) {
|
|
471
|
-
choiceNames.push(...result.values.results
|
|
472
|
-
.map((item) => item.value)
|
|
473
|
-
.filter((value) => typeof value === "string"));
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
// Build options by matching customizations with aggregation data
|
|
477
|
-
const options = customizations
|
|
478
|
-
.filter((customization) => customization.name &&
|
|
479
|
-
customization._id &&
|
|
480
|
-
customization.customizationType ===
|
|
481
|
-
customizationsV3.CustomizationType.PRODUCT_OPTION &&
|
|
482
|
-
(optionNames.length === 0 ||
|
|
483
|
-
matchesAggregationName(customization.name, optionNames)))
|
|
484
|
-
.map((customization) => {
|
|
485
|
-
const choices = (customization.choicesSettings?.choices || [])
|
|
486
|
-
.filter((choice) => choice._id &&
|
|
487
|
-
choice.name &&
|
|
488
|
-
(choiceNames.length === 0 ||
|
|
489
|
-
matchesAggregationName(choice.name, choiceNames)))
|
|
490
|
-
.map((choice) => ({
|
|
491
|
-
id: choice._id,
|
|
492
|
-
name: choice.name,
|
|
493
|
-
colorCode: choice.colorCode,
|
|
494
|
-
}));
|
|
495
|
-
return {
|
|
496
|
-
id: customization._id,
|
|
497
|
-
name: customization.name,
|
|
498
|
-
choices: sortChoicesIntelligently(choices),
|
|
499
|
-
optionRenderType: customization.customizationRenderType,
|
|
500
|
-
};
|
|
501
|
-
})
|
|
502
|
-
.filter((option) => option.choices.length > 0);
|
|
503
|
-
return options;
|
|
504
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { type Signal } from "@wix/services-definitions/core-services/signals";
|
|
2
|
-
/**
|
|
3
|
-
* Service definition for the Products List Pagination service.
|
|
4
|
-
* This defines the reactive API contract for managing product list pagination state and navigation.
|
|
5
|
-
*
|
|
6
|
-
* @constant
|
|
7
|
-
*/
|
|
8
|
-
export declare const ProductsListPaginationServiceDefinition: string & {
|
|
9
|
-
__api: {
|
|
10
|
-
/** Reactive signal containing the current page size limit */
|
|
11
|
-
currentLimit: Signal<number>;
|
|
12
|
-
/** Reactive signal containing the current cursor for pagination */
|
|
13
|
-
currentCursor: Signal<string | null>;
|
|
14
|
-
/** Computed signal indicating if there's a next page available */
|
|
15
|
-
hasNextPage: {
|
|
16
|
-
get: () => boolean;
|
|
17
|
-
};
|
|
18
|
-
/** Computed signal indicating if there's a previous page available */
|
|
19
|
-
hasPrevPage: {
|
|
20
|
-
get: () => boolean;
|
|
21
|
-
};
|
|
22
|
-
/** Function to set the page size limit */
|
|
23
|
-
setLimit: (limit: number) => void;
|
|
24
|
-
/** Function to navigate to the next page */
|
|
25
|
-
nextPage: () => void;
|
|
26
|
-
/** Function to navigate to the previous page */
|
|
27
|
-
prevPage: () => void;
|
|
28
|
-
/** Function to navigate to the first page */
|
|
29
|
-
navigateToFirstPage: () => void;
|
|
30
|
-
/** Function to load more items (increase the limit) */
|
|
31
|
-
loadMore: (count: number) => void;
|
|
32
|
-
};
|
|
33
|
-
__config: {};
|
|
34
|
-
isServiceDefinition?: boolean;
|
|
35
|
-
} & {
|
|
36
|
-
/** Reactive signal containing the current page size limit */
|
|
37
|
-
currentLimit: Signal<number>;
|
|
38
|
-
/** Reactive signal containing the current cursor for pagination */
|
|
39
|
-
currentCursor: Signal<string | null>;
|
|
40
|
-
/** Computed signal indicating if there's a next page available */
|
|
41
|
-
hasNextPage: {
|
|
42
|
-
get: () => boolean;
|
|
43
|
-
};
|
|
44
|
-
/** Computed signal indicating if there's a previous page available */
|
|
45
|
-
hasPrevPage: {
|
|
46
|
-
get: () => boolean;
|
|
47
|
-
};
|
|
48
|
-
/** Function to set the page size limit */
|
|
49
|
-
setLimit: (limit: number) => void;
|
|
50
|
-
/** Function to navigate to the next page */
|
|
51
|
-
nextPage: () => void;
|
|
52
|
-
/** Function to navigate to the previous page */
|
|
53
|
-
prevPage: () => void;
|
|
54
|
-
/** Function to navigate to the first page */
|
|
55
|
-
navigateToFirstPage: () => void;
|
|
56
|
-
/** Function to load more items (increase the limit) */
|
|
57
|
-
loadMore: (count: number) => void;
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Configuration interface for the Products List Pagination service.
|
|
61
|
-
* Currently empty as this service doesn't require initial configuration.
|
|
62
|
-
*
|
|
63
|
-
* @interface ProductsListPaginationServiceConfig
|
|
64
|
-
*/
|
|
65
|
-
export type ProductsListPaginationServiceConfig = {};
|
|
66
|
-
/**
|
|
67
|
-
* Implementation of the Products List Pagination service that manages reactive pagination state.
|
|
68
|
-
* This service provides signals for pagination state and automatically updates the products list
|
|
69
|
-
* search options when pagination settings change. It supports both cursor-based pagination
|
|
70
|
-
* and load-more functionality.
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```tsx
|
|
74
|
-
* import { ProductsListPaginationService, ProductsListPaginationServiceDefinition } from '@wix/stores/services';
|
|
75
|
-
* import { useService } from '@wix/services-manager-react';
|
|
76
|
-
*
|
|
77
|
-
* function PaginationComponent() {
|
|
78
|
-
* return (
|
|
79
|
-
* <ServiceProvider services={createServicesMap([
|
|
80
|
-
* [ProductsListPaginationServiceDefinition, ProductsListPaginationService.withConfig({})]
|
|
81
|
-
* ])}>
|
|
82
|
-
* <PaginationControls />
|
|
83
|
-
* </ServiceProvider>
|
|
84
|
-
* );
|
|
85
|
-
* }
|
|
86
|
-
*
|
|
87
|
-
* function PaginationControls() {
|
|
88
|
-
* const paginationService = useService(ProductsListPaginationServiceDefinition);
|
|
89
|
-
* const currentLimit = paginationService.currentLimit.get();
|
|
90
|
-
* const hasNextPage = paginationService.hasNextPage.get();
|
|
91
|
-
* const hasPrevPage = paginationService.hasPrevPage.get();
|
|
92
|
-
*
|
|
93
|
-
* return (
|
|
94
|
-
* <div>
|
|
95
|
-
* <div>
|
|
96
|
-
* Items per page:
|
|
97
|
-
* <select
|
|
98
|
-
* value={currentLimit}
|
|
99
|
-
* onChange={(e) => paginationService.setLimit(parseInt(e.target.value))}
|
|
100
|
-
* >
|
|
101
|
-
* <option value={12}>12</option>
|
|
102
|
-
* <option value={24}>24</option>
|
|
103
|
-
* <option value={48}>48</option>
|
|
104
|
-
* </select>
|
|
105
|
-
* </div>
|
|
106
|
-
*
|
|
107
|
-
* <div>
|
|
108
|
-
* <button
|
|
109
|
-
* onClick={() => paginationService.navigateToFirstPage()}
|
|
110
|
-
* disabled={!hasPrevPage}
|
|
111
|
-
* >
|
|
112
|
-
* First
|
|
113
|
-
* </button>
|
|
114
|
-
* <button
|
|
115
|
-
* onClick={() => paginationService.prevPage()}
|
|
116
|
-
* disabled={!hasPrevPage}
|
|
117
|
-
* >
|
|
118
|
-
* Previous
|
|
119
|
-
* </button>
|
|
120
|
-
* <button
|
|
121
|
-
* onClick={() => paginationService.nextPage()}
|
|
122
|
-
* disabled={!hasNextPage}
|
|
123
|
-
* >
|
|
124
|
-
* Next
|
|
125
|
-
* </button>
|
|
126
|
-
* </div>
|
|
127
|
-
*
|
|
128
|
-
* <button onClick={() => paginationService.loadMore(12)}>
|
|
129
|
-
* Load More
|
|
130
|
-
* </button>
|
|
131
|
-
* </div>
|
|
132
|
-
* );
|
|
133
|
-
* }
|
|
134
|
-
* ```
|
|
135
|
-
*/
|
|
136
|
-
export declare const ProductsListPaginationService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
137
|
-
__api: {
|
|
138
|
-
/** Reactive signal containing the current page size limit */
|
|
139
|
-
currentLimit: Signal<number>;
|
|
140
|
-
/** Reactive signal containing the current cursor for pagination */
|
|
141
|
-
currentCursor: Signal<string | null>;
|
|
142
|
-
/** Computed signal indicating if there's a next page available */
|
|
143
|
-
hasNextPage: {
|
|
144
|
-
get: () => boolean;
|
|
145
|
-
};
|
|
146
|
-
/** Computed signal indicating if there's a previous page available */
|
|
147
|
-
hasPrevPage: {
|
|
148
|
-
get: () => boolean;
|
|
149
|
-
};
|
|
150
|
-
/** Function to set the page size limit */
|
|
151
|
-
setLimit: (limit: number) => void;
|
|
152
|
-
/** Function to navigate to the next page */
|
|
153
|
-
nextPage: () => void;
|
|
154
|
-
/** Function to navigate to the previous page */
|
|
155
|
-
prevPage: () => void;
|
|
156
|
-
/** Function to navigate to the first page */
|
|
157
|
-
navigateToFirstPage: () => void;
|
|
158
|
-
/** Function to load more items (increase the limit) */
|
|
159
|
-
loadMore: (count: number) => void;
|
|
160
|
-
};
|
|
161
|
-
__config: {};
|
|
162
|
-
isServiceDefinition?: boolean;
|
|
163
|
-
} & {
|
|
164
|
-
/** Reactive signal containing the current page size limit */
|
|
165
|
-
currentLimit: Signal<number>;
|
|
166
|
-
/** Reactive signal containing the current cursor for pagination */
|
|
167
|
-
currentCursor: Signal<string | null>;
|
|
168
|
-
/** Computed signal indicating if there's a next page available */
|
|
169
|
-
hasNextPage: {
|
|
170
|
-
get: () => boolean;
|
|
171
|
-
};
|
|
172
|
-
/** Computed signal indicating if there's a previous page available */
|
|
173
|
-
hasPrevPage: {
|
|
174
|
-
get: () => boolean;
|
|
175
|
-
};
|
|
176
|
-
/** Function to set the page size limit */
|
|
177
|
-
setLimit: (limit: number) => void;
|
|
178
|
-
/** Function to navigate to the next page */
|
|
179
|
-
nextPage: () => void;
|
|
180
|
-
/** Function to navigate to the previous page */
|
|
181
|
-
prevPage: () => void;
|
|
182
|
-
/** Function to navigate to the first page */
|
|
183
|
-
navigateToFirstPage: () => void;
|
|
184
|
-
/** Function to load more items (increase the limit) */
|
|
185
|
-
loadMore: (count: number) => void;
|
|
186
|
-
}, ProductsListPaginationServiceConfig>;
|