@wix/headless-stores 0.0.42 → 0.0.44

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.
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { type ProductOption, InventoryStatusType } from "../services/products-list-search-service.js";
3
+ import { Category } from "@wix/auto_sdk_categories_categories";
3
4
  /**
4
5
  * Props for InventoryStatus headless component
5
6
  */
@@ -168,6 +169,15 @@ export interface PriceRangeRenderProps {
168
169
  * ```
169
170
  */
170
171
  export declare function PriceRange(props: PriceRangeProps): ReactNode;
172
+ export interface CategoryFilterRenderProps {
173
+ selectedCategory: Category | null;
174
+ setSelectedCategory: (category: Category | null) => void;
175
+ }
176
+ export interface CategoryFilterProps {
177
+ /** Content to display (can be a render function receiving category data or ReactNode) */
178
+ children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
179
+ }
180
+ export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
171
181
  /**
172
182
  * Props for ProductOptions headless component
173
183
  */
@@ -148,6 +148,14 @@ export function PriceRange(props) {
148
148
  })
149
149
  : props.children;
150
150
  }
151
+ export function CategoryFilter(props) {
152
+ const service = useService(ProductsListSearchServiceDefinition);
153
+ const selectedCategory = service.selectedCategory.get();
154
+ const setSelectedCategory = service.setSelectedCategory;
155
+ return typeof props.children === "function"
156
+ ? props.children({ selectedCategory, setSelectedCategory })
157
+ : props.children;
158
+ }
151
159
  /**
152
160
  * Headless component that renders content for each product option in the list.
153
161
  * Maps over all available product options and provides each option through a render prop.
@@ -1,5 +1,6 @@
1
1
  import type { Signal } from "@wix/services-definitions/core-services/signals";
2
2
  import { productsV3, customizationsV3 } from "@wix/stores";
3
+ import { type Category } from "./category-service.js";
3
4
  import { SortType } from "./../enums/sort-enums.js";
4
5
  export { SortType } from "./../enums/sort-enums.js";
5
6
  /**
@@ -42,7 +43,7 @@ type InitialSearchState = {
42
43
  };
43
44
  inventoryStatuses?: InventoryStatusType[];
44
45
  productOptions?: Record<string, string[]>;
45
- category?: string;
46
+ category?: Category;
46
47
  visible?: boolean;
47
48
  productType?: string;
48
49
  };
@@ -83,13 +84,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
83
84
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
84
85
  availableProductOptions: Signal<ProductOption[]>;
85
86
  selectedProductOptions: Signal<Record<string, string[]>>;
86
- selectedCategory: Signal<string | null>;
87
+ selectedCategory: Signal<Category | null>;
87
88
  setSelectedMinPrice: (minPrice: number) => void;
88
89
  setSelectedMaxPrice: (maxPrice: number) => void;
89
90
  toggleInventoryStatus: (status: InventoryStatusType) => void;
90
91
  toggleProductOption: (optionId: string, choiceId: string) => void;
91
- setSelectedCategory: (category: string | null) => void;
92
- isFiltered: Signal<boolean>;
92
+ setSelectedCategory: (category: Category | null) => void;
93
+ isFiltered: {
94
+ get: () => boolean;
95
+ };
93
96
  reset: () => void;
94
97
  };
95
98
  __config: {};
@@ -119,13 +122,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
119
122
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
120
123
  availableProductOptions: Signal<ProductOption[]>;
121
124
  selectedProductOptions: Signal<Record<string, string[]>>;
122
- selectedCategory: Signal<string | null>;
125
+ selectedCategory: Signal<Category | null>;
123
126
  setSelectedMinPrice: (minPrice: number) => void;
124
127
  setSelectedMaxPrice: (maxPrice: number) => void;
125
128
  toggleInventoryStatus: (status: InventoryStatusType) => void;
126
129
  toggleProductOption: (optionId: string, choiceId: string) => void;
127
- setSelectedCategory: (category: string | null) => void;
128
- isFiltered: Signal<boolean>;
130
+ setSelectedCategory: (category: Category | null) => void;
131
+ isFiltered: {
132
+ get: () => boolean;
133
+ };
129
134
  reset: () => void;
130
135
  };
131
136
  /**
@@ -135,7 +140,7 @@ export declare function convertUrlSortToSortType(urlSort: string): SortType | nu
135
140
  /**
136
141
  * Parse URL and build complete search options with all filters, sort, and pagination
137
142
  */
138
- export declare function parseUrlForProductsListSearch(url: string, defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
143
+ export declare function parseUrlForProductsListSearch(url: string, categoriesList: Category[], defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
139
144
  searchOptions: productsV3.V3ProductSearch;
140
145
  initialSearchState: InitialSearchState;
141
146
  }>;
@@ -172,13 +177,15 @@ export declare const ProductsListSearchService: import("@wix/services-definition
172
177
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
173
178
  availableProductOptions: Signal<ProductOption[]>;
174
179
  selectedProductOptions: Signal<Record<string, string[]>>;
175
- selectedCategory: Signal<string | null>;
180
+ selectedCategory: Signal<Category | null>;
176
181
  setSelectedMinPrice: (minPrice: number) => void;
177
182
  setSelectedMaxPrice: (maxPrice: number) => void;
178
183
  toggleInventoryStatus: (status: InventoryStatusType) => void;
179
184
  toggleProductOption: (optionId: string, choiceId: string) => void;
180
- setSelectedCategory: (category: string | null) => void;
181
- isFiltered: Signal<boolean>;
185
+ setSelectedCategory: (category: Category | null) => void;
186
+ isFiltered: {
187
+ get: () => boolean;
188
+ };
182
189
  reset: () => void;
183
190
  };
184
191
  __config: {};
@@ -208,12 +215,14 @@ export declare const ProductsListSearchService: import("@wix/services-definition
208
215
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
209
216
  availableProductOptions: Signal<ProductOption[]>;
210
217
  selectedProductOptions: Signal<Record<string, string[]>>;
211
- selectedCategory: Signal<string | null>;
218
+ selectedCategory: Signal<Category | null>;
212
219
  setSelectedMinPrice: (minPrice: number) => void;
213
220
  setSelectedMaxPrice: (maxPrice: number) => void;
214
221
  toggleInventoryStatus: (status: InventoryStatusType) => void;
215
222
  toggleProductOption: (optionId: string, choiceId: string) => void;
216
- setSelectedCategory: (category: string | null) => void;
217
- isFiltered: Signal<boolean>;
223
+ setSelectedCategory: (category: Category | null) => void;
224
+ isFiltered: {
225
+ get: () => boolean;
226
+ };
218
227
  reset: () => void;
219
228
  }, ProductsListSearchServiceConfig>;
@@ -2,6 +2,7 @@ import { defineService, implementService } from "@wix/services-definitions";
2
2
  import { SignalsServiceDefinition } from "@wix/services-definitions/core-services/signals";
3
3
  import { DEFAULT_QUERY_LIMIT, ProductsListServiceDefinition, } from "./products-list-service.js";
4
4
  import { productsV3, customizationsV3 } from "@wix/stores";
5
+ import { loadCategoriesListServiceConfig } from "./categories-list-service.js";
5
6
  const PRICE_FILTER_DEBOUNCE_TIME = 300;
6
7
  import { SortType } from "./../enums/sort-enums.js";
7
8
  export { SortType } from "./../enums/sort-enums.js";
@@ -63,7 +64,7 @@ export function convertUrlSortToSortType(urlSort) {
63
64
  function updateUrlWithSearchState(searchState) {
64
65
  if (typeof window === "undefined")
65
66
  return;
66
- const { sort, filters, customizations, catalogBounds } = searchState;
67
+ const { sort, filters, customizations, catalogBounds, categorySlug } = searchState;
67
68
  // Convert filter IDs back to human-readable names for URL
68
69
  const humanReadableOptions = {};
69
70
  for (const [optionId, choiceIds] of Object.entries(filters?.productOptions ?? {})) {
@@ -91,10 +92,10 @@ function updateUrlWithSearchState(searchState) {
91
92
  "minPrice",
92
93
  "maxPrice",
93
94
  "inventoryStatus",
94
- "category",
95
95
  "visible",
96
96
  "productType",
97
97
  // Product option names will be dynamically added below
98
+ // Note: category is NOT included here as it's handled in the URL path
98
99
  ];
99
100
  // Remove existing search parameters first
100
101
  searchParams.forEach((param) => params.delete(param));
@@ -124,10 +125,6 @@ function updateUrlWithSearchState(searchState) {
124
125
  if (filters.inventoryStatuses && filters.inventoryStatuses.length > 0) {
125
126
  params.set("inventoryStatus", filters.inventoryStatuses.join(","));
126
127
  }
127
- // Add category filter
128
- if (filters.category) {
129
- params.set("category", filters.category);
130
- }
131
128
  // Add visibility filter (only if explicitly false, since true is default)
132
129
  if (filters.visible === false) {
133
130
  params.set("visible", "false");
@@ -142,8 +139,15 @@ function updateUrlWithSearchState(searchState) {
142
139
  params.set(optionName, values.join(","));
143
140
  }
144
141
  }
142
+ // Handle URL path construction with category
143
+ let baseUrl = window.location.pathname;
144
+ // If categorySlug is provided, update the path
145
+ if (categorySlug) {
146
+ if (categorySlug) {
147
+ baseUrl = `/category/${categorySlug}`;
148
+ }
149
+ }
145
150
  // Build the new URL
146
- const baseUrl = window.location.pathname;
147
151
  const newUrl = params.toString()
148
152
  ? `${baseUrl}?${params.toString()}`
149
153
  : baseUrl;
@@ -155,7 +159,7 @@ function updateUrlWithSearchState(searchState) {
155
159
  /**
156
160
  * Parse URL and build complete search options with all filters, sort, and pagination
157
161
  */
158
- export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
162
+ export async function parseUrlForProductsListSearch(url, categoriesList, defaultSearchOptions) {
159
163
  const urlObj = new URL(url);
160
164
  const searchParams = urlObj.searchParams;
161
165
  // Get customizations for product option parsing
@@ -171,6 +175,21 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
171
175
  };
172
176
  // Initialize search state for service
173
177
  const initialSearchState = {};
178
+ // Extract category slug from URL path (e.g., /category/category-slug)
179
+ const pathSegments = urlObj.pathname.split("/");
180
+ const categoryIndex = pathSegments.findIndex((segment) => segment === "category");
181
+ let categorySlug = null;
182
+ let category = undefined;
183
+ if (categoryIndex !== -1 && categoryIndex + 1 < pathSegments.length) {
184
+ categorySlug = pathSegments[categoryIndex + 1] || null;
185
+ // Find the category by slug from the provided categories list
186
+ if (categorySlug) {
187
+ category = categoriesList.find((cat) => cat.slug === categorySlug);
188
+ if (category) {
189
+ initialSearchState.category = category;
190
+ }
191
+ }
192
+ }
174
193
  // Handle text search (q parameter)
175
194
  const query = searchParams.get("q");
176
195
  if (query) {
@@ -252,12 +271,11 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
252
271
  filter["productType"] = productType;
253
272
  initialSearchState.productType = productType;
254
273
  }
255
- const category = searchParams.get("category");
274
+ // Add category filter if found
256
275
  if (category) {
257
276
  filter["allCategoriesInfo.categories"] = {
258
- $matchItems: [{ _id: { $in: [category] } }],
277
+ $matchItems: [{ _id: { $in: [category._id] } }],
259
278
  };
260
- initialSearchState.category = category;
261
279
  }
262
280
  // Price range filtering
263
281
  const minPrice = searchParams.get("minPrice");
@@ -285,7 +303,6 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
285
303
  "maxPrice",
286
304
  "inventory_status",
287
305
  "inventoryStatus",
288
- "category",
289
306
  "visible",
290
307
  "productType",
291
308
  "q",
@@ -378,7 +395,9 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
378
395
  * Load search service configuration from URL
379
396
  */
380
397
  export async function loadProductsListSearchServiceConfig(url) {
381
- const { initialSearchState } = await parseUrlForProductsListSearch(url);
398
+ // Load categories using the categories service
399
+ const categoriesListConfig = await loadCategoriesListServiceConfig();
400
+ const { initialSearchState } = await parseUrlForProductsListSearch(url, categoriesListConfig.categories);
382
401
  const { items: customizations = [] } = await customizationsV3
383
402
  .queryCustomizations()
384
403
  .find();
@@ -419,7 +438,31 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
419
438
  const selectedCategorySignal = signalsService.signal(initialSearchState?.category || null);
420
439
  const selectedVisibleSignal = signalsService.signal(initialSearchState?.visible ?? null);
421
440
  const selectedProductTypeSignal = signalsService.signal(initialSearchState?.productType || null);
422
- const isFilteredSignal = signalsService.signal(false);
441
+ // Computed signal to check if any filters are applied
442
+ const isFilteredSignal = signalsService.computed(() => {
443
+ const catalogPriceRange = getCatalogPriceRange(productsListService.aggregations.get()?.results || []);
444
+ const minPrice = userFilterMinPriceSignal.get();
445
+ const maxPrice = userFilterMaxPriceSignal.get();
446
+ const selectedInventoryStatuses = selectedInventoryStatusesSignal.get();
447
+ const selectedProductOptions = selectedProductOptionsSignal.get();
448
+ const selectedCategory = selectedCategorySignal.get();
449
+ const selectedVisible = selectedVisibleSignal.get();
450
+ const selectedProductType = selectedProductTypeSignal.get();
451
+ // Check if any filters are different from default values
452
+ const hasPriceFilter = minPrice > catalogPriceRange.minPrice ||
453
+ maxPrice < catalogPriceRange.maxPrice;
454
+ const hasInventoryFilter = selectedInventoryStatuses.length > 0;
455
+ const hasProductOptionsFilter = Object.keys(selectedProductOptions).length > 0;
456
+ const hasCategoryFilter = selectedCategory !== null;
457
+ const hasVisibilityFilter = selectedVisible !== null;
458
+ const hasProductTypeFilter = selectedProductType !== null;
459
+ return (hasPriceFilter ||
460
+ hasInventoryFilter ||
461
+ hasProductOptionsFilter ||
462
+ hasCategoryFilter ||
463
+ hasVisibilityFilter ||
464
+ hasProductTypeFilter);
465
+ });
423
466
  // Computed signals for pagination
424
467
  const hasNextPageSignal = signalsService.computed(() => {
425
468
  const pagingMetadata = productsListService.pagingMetadata.get();
@@ -550,7 +593,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
550
593
  }
551
594
  if (selectedCategory) {
552
595
  newSearchOptions.filter["allCategoriesInfo.categories"] = {
553
- $matchItems: [{ _id: { $in: [selectedCategory] } }],
596
+ $matchItems: [{ _id: { $in: [selectedCategory._id] } }],
554
597
  };
555
598
  }
556
599
  if (selectedVisible !== null) {
@@ -571,7 +614,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
571
614
  priceRange: { min: minPrice, max: maxPrice },
572
615
  inventoryStatuses: selectedInventoryStatuses,
573
616
  productOptions: selectedProductOptions,
574
- ...(selectedCategory && { category: selectedCategory }),
575
617
  ...(selectedVisible !== null && { visible: selectedVisible }),
576
618
  ...(selectedProductType && { productType: selectedProductType }),
577
619
  };
@@ -580,6 +622,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
580
622
  filters: currentFilters,
581
623
  customizations,
582
624
  catalogBounds,
625
+ categorySlug: selectedCategory?.slug || undefined,
583
626
  });
584
627
  });
585
628
  }
@@ -702,7 +745,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
702
745
  selectedCategorySignal.set(null);
703
746
  selectedVisibleSignal.set(null);
704
747
  selectedProductTypeSignal.set(null);
705
- isFilteredSignal.set(false);
706
748
  },
707
749
  };
708
750
  });
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { type ProductOption, InventoryStatusType } from "../services/products-list-search-service.js";
3
+ import { Category } from "@wix/auto_sdk_categories_categories";
3
4
  /**
4
5
  * Props for InventoryStatus headless component
5
6
  */
@@ -168,6 +169,15 @@ export interface PriceRangeRenderProps {
168
169
  * ```
169
170
  */
170
171
  export declare function PriceRange(props: PriceRangeProps): ReactNode;
172
+ export interface CategoryFilterRenderProps {
173
+ selectedCategory: Category | null;
174
+ setSelectedCategory: (category: Category | null) => void;
175
+ }
176
+ export interface CategoryFilterProps {
177
+ /** Content to display (can be a render function receiving category data or ReactNode) */
178
+ children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
179
+ }
180
+ export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
171
181
  /**
172
182
  * Props for ProductOptions headless component
173
183
  */
@@ -148,6 +148,14 @@ export function PriceRange(props) {
148
148
  })
149
149
  : props.children;
150
150
  }
151
+ export function CategoryFilter(props) {
152
+ const service = useService(ProductsListSearchServiceDefinition);
153
+ const selectedCategory = service.selectedCategory.get();
154
+ const setSelectedCategory = service.setSelectedCategory;
155
+ return typeof props.children === "function"
156
+ ? props.children({ selectedCategory, setSelectedCategory })
157
+ : props.children;
158
+ }
151
159
  /**
152
160
  * Headless component that renders content for each product option in the list.
153
161
  * Maps over all available product options and provides each option through a render prop.
@@ -1,5 +1,6 @@
1
1
  import type { Signal } from "@wix/services-definitions/core-services/signals";
2
2
  import { productsV3, customizationsV3 } from "@wix/stores";
3
+ import { type Category } from "./category-service.js";
3
4
  import { SortType } from "./../enums/sort-enums.js";
4
5
  export { SortType } from "./../enums/sort-enums.js";
5
6
  /**
@@ -42,7 +43,7 @@ type InitialSearchState = {
42
43
  };
43
44
  inventoryStatuses?: InventoryStatusType[];
44
45
  productOptions?: Record<string, string[]>;
45
- category?: string;
46
+ category?: Category;
46
47
  visible?: boolean;
47
48
  productType?: string;
48
49
  };
@@ -83,13 +84,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
83
84
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
84
85
  availableProductOptions: Signal<ProductOption[]>;
85
86
  selectedProductOptions: Signal<Record<string, string[]>>;
86
- selectedCategory: Signal<string | null>;
87
+ selectedCategory: Signal<Category | null>;
87
88
  setSelectedMinPrice: (minPrice: number) => void;
88
89
  setSelectedMaxPrice: (maxPrice: number) => void;
89
90
  toggleInventoryStatus: (status: InventoryStatusType) => void;
90
91
  toggleProductOption: (optionId: string, choiceId: string) => void;
91
- setSelectedCategory: (category: string | null) => void;
92
- isFiltered: Signal<boolean>;
92
+ setSelectedCategory: (category: Category | null) => void;
93
+ isFiltered: {
94
+ get: () => boolean;
95
+ };
93
96
  reset: () => void;
94
97
  };
95
98
  __config: {};
@@ -119,13 +122,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
119
122
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
120
123
  availableProductOptions: Signal<ProductOption[]>;
121
124
  selectedProductOptions: Signal<Record<string, string[]>>;
122
- selectedCategory: Signal<string | null>;
125
+ selectedCategory: Signal<Category | null>;
123
126
  setSelectedMinPrice: (minPrice: number) => void;
124
127
  setSelectedMaxPrice: (maxPrice: number) => void;
125
128
  toggleInventoryStatus: (status: InventoryStatusType) => void;
126
129
  toggleProductOption: (optionId: string, choiceId: string) => void;
127
- setSelectedCategory: (category: string | null) => void;
128
- isFiltered: Signal<boolean>;
130
+ setSelectedCategory: (category: Category | null) => void;
131
+ isFiltered: {
132
+ get: () => boolean;
133
+ };
129
134
  reset: () => void;
130
135
  };
131
136
  /**
@@ -135,7 +140,7 @@ export declare function convertUrlSortToSortType(urlSort: string): SortType | nu
135
140
  /**
136
141
  * Parse URL and build complete search options with all filters, sort, and pagination
137
142
  */
138
- export declare function parseUrlForProductsListSearch(url: string, defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
143
+ export declare function parseUrlForProductsListSearch(url: string, categoriesList: Category[], defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
139
144
  searchOptions: productsV3.V3ProductSearch;
140
145
  initialSearchState: InitialSearchState;
141
146
  }>;
@@ -172,13 +177,15 @@ export declare const ProductsListSearchService: import("@wix/services-definition
172
177
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
173
178
  availableProductOptions: Signal<ProductOption[]>;
174
179
  selectedProductOptions: Signal<Record<string, string[]>>;
175
- selectedCategory: Signal<string | null>;
180
+ selectedCategory: Signal<Category | null>;
176
181
  setSelectedMinPrice: (minPrice: number) => void;
177
182
  setSelectedMaxPrice: (maxPrice: number) => void;
178
183
  toggleInventoryStatus: (status: InventoryStatusType) => void;
179
184
  toggleProductOption: (optionId: string, choiceId: string) => void;
180
- setSelectedCategory: (category: string | null) => void;
181
- isFiltered: Signal<boolean>;
185
+ setSelectedCategory: (category: Category | null) => void;
186
+ isFiltered: {
187
+ get: () => boolean;
188
+ };
182
189
  reset: () => void;
183
190
  };
184
191
  __config: {};
@@ -208,12 +215,14 @@ export declare const ProductsListSearchService: import("@wix/services-definition
208
215
  selectedInventoryStatuses: Signal<InventoryStatusType[]>;
209
216
  availableProductOptions: Signal<ProductOption[]>;
210
217
  selectedProductOptions: Signal<Record<string, string[]>>;
211
- selectedCategory: Signal<string | null>;
218
+ selectedCategory: Signal<Category | null>;
212
219
  setSelectedMinPrice: (minPrice: number) => void;
213
220
  setSelectedMaxPrice: (maxPrice: number) => void;
214
221
  toggleInventoryStatus: (status: InventoryStatusType) => void;
215
222
  toggleProductOption: (optionId: string, choiceId: string) => void;
216
- setSelectedCategory: (category: string | null) => void;
217
- isFiltered: Signal<boolean>;
223
+ setSelectedCategory: (category: Category | null) => void;
224
+ isFiltered: {
225
+ get: () => boolean;
226
+ };
218
227
  reset: () => void;
219
228
  }, ProductsListSearchServiceConfig>;
@@ -2,6 +2,7 @@ import { defineService, implementService } from "@wix/services-definitions";
2
2
  import { SignalsServiceDefinition } from "@wix/services-definitions/core-services/signals";
3
3
  import { DEFAULT_QUERY_LIMIT, ProductsListServiceDefinition, } from "./products-list-service.js";
4
4
  import { productsV3, customizationsV3 } from "@wix/stores";
5
+ import { loadCategoriesListServiceConfig } from "./categories-list-service.js";
5
6
  const PRICE_FILTER_DEBOUNCE_TIME = 300;
6
7
  import { SortType } from "./../enums/sort-enums.js";
7
8
  export { SortType } from "./../enums/sort-enums.js";
@@ -63,7 +64,7 @@ export function convertUrlSortToSortType(urlSort) {
63
64
  function updateUrlWithSearchState(searchState) {
64
65
  if (typeof window === "undefined")
65
66
  return;
66
- const { sort, filters, customizations, catalogBounds } = searchState;
67
+ const { sort, filters, customizations, catalogBounds, categorySlug } = searchState;
67
68
  // Convert filter IDs back to human-readable names for URL
68
69
  const humanReadableOptions = {};
69
70
  for (const [optionId, choiceIds] of Object.entries(filters?.productOptions ?? {})) {
@@ -91,10 +92,10 @@ function updateUrlWithSearchState(searchState) {
91
92
  "minPrice",
92
93
  "maxPrice",
93
94
  "inventoryStatus",
94
- "category",
95
95
  "visible",
96
96
  "productType",
97
97
  // Product option names will be dynamically added below
98
+ // Note: category is NOT included here as it's handled in the URL path
98
99
  ];
99
100
  // Remove existing search parameters first
100
101
  searchParams.forEach((param) => params.delete(param));
@@ -124,10 +125,6 @@ function updateUrlWithSearchState(searchState) {
124
125
  if (filters.inventoryStatuses && filters.inventoryStatuses.length > 0) {
125
126
  params.set("inventoryStatus", filters.inventoryStatuses.join(","));
126
127
  }
127
- // Add category filter
128
- if (filters.category) {
129
- params.set("category", filters.category);
130
- }
131
128
  // Add visibility filter (only if explicitly false, since true is default)
132
129
  if (filters.visible === false) {
133
130
  params.set("visible", "false");
@@ -142,8 +139,15 @@ function updateUrlWithSearchState(searchState) {
142
139
  params.set(optionName, values.join(","));
143
140
  }
144
141
  }
142
+ // Handle URL path construction with category
143
+ let baseUrl = window.location.pathname;
144
+ // If categorySlug is provided, update the path
145
+ if (categorySlug) {
146
+ if (categorySlug) {
147
+ baseUrl = `/category/${categorySlug}`;
148
+ }
149
+ }
145
150
  // Build the new URL
146
- const baseUrl = window.location.pathname;
147
151
  const newUrl = params.toString()
148
152
  ? `${baseUrl}?${params.toString()}`
149
153
  : baseUrl;
@@ -155,7 +159,7 @@ function updateUrlWithSearchState(searchState) {
155
159
  /**
156
160
  * Parse URL and build complete search options with all filters, sort, and pagination
157
161
  */
158
- export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
162
+ export async function parseUrlForProductsListSearch(url, categoriesList, defaultSearchOptions) {
159
163
  const urlObj = new URL(url);
160
164
  const searchParams = urlObj.searchParams;
161
165
  // Get customizations for product option parsing
@@ -171,6 +175,21 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
171
175
  };
172
176
  // Initialize search state for service
173
177
  const initialSearchState = {};
178
+ // Extract category slug from URL path (e.g., /category/category-slug)
179
+ const pathSegments = urlObj.pathname.split("/");
180
+ const categoryIndex = pathSegments.findIndex((segment) => segment === "category");
181
+ let categorySlug = null;
182
+ let category = undefined;
183
+ if (categoryIndex !== -1 && categoryIndex + 1 < pathSegments.length) {
184
+ categorySlug = pathSegments[categoryIndex + 1] || null;
185
+ // Find the category by slug from the provided categories list
186
+ if (categorySlug) {
187
+ category = categoriesList.find((cat) => cat.slug === categorySlug);
188
+ if (category) {
189
+ initialSearchState.category = category;
190
+ }
191
+ }
192
+ }
174
193
  // Handle text search (q parameter)
175
194
  const query = searchParams.get("q");
176
195
  if (query) {
@@ -252,12 +271,11 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
252
271
  filter["productType"] = productType;
253
272
  initialSearchState.productType = productType;
254
273
  }
255
- const category = searchParams.get("category");
274
+ // Add category filter if found
256
275
  if (category) {
257
276
  filter["allCategoriesInfo.categories"] = {
258
- $matchItems: [{ _id: { $in: [category] } }],
277
+ $matchItems: [{ _id: { $in: [category._id] } }],
259
278
  };
260
- initialSearchState.category = category;
261
279
  }
262
280
  // Price range filtering
263
281
  const minPrice = searchParams.get("minPrice");
@@ -285,7 +303,6 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
285
303
  "maxPrice",
286
304
  "inventory_status",
287
305
  "inventoryStatus",
288
- "category",
289
306
  "visible",
290
307
  "productType",
291
308
  "q",
@@ -378,7 +395,9 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
378
395
  * Load search service configuration from URL
379
396
  */
380
397
  export async function loadProductsListSearchServiceConfig(url) {
381
- const { initialSearchState } = await parseUrlForProductsListSearch(url);
398
+ // Load categories using the categories service
399
+ const categoriesListConfig = await loadCategoriesListServiceConfig();
400
+ const { initialSearchState } = await parseUrlForProductsListSearch(url, categoriesListConfig.categories);
382
401
  const { items: customizations = [] } = await customizationsV3
383
402
  .queryCustomizations()
384
403
  .find();
@@ -419,7 +438,31 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
419
438
  const selectedCategorySignal = signalsService.signal(initialSearchState?.category || null);
420
439
  const selectedVisibleSignal = signalsService.signal(initialSearchState?.visible ?? null);
421
440
  const selectedProductTypeSignal = signalsService.signal(initialSearchState?.productType || null);
422
- const isFilteredSignal = signalsService.signal(false);
441
+ // Computed signal to check if any filters are applied
442
+ const isFilteredSignal = signalsService.computed(() => {
443
+ const catalogPriceRange = getCatalogPriceRange(productsListService.aggregations.get()?.results || []);
444
+ const minPrice = userFilterMinPriceSignal.get();
445
+ const maxPrice = userFilterMaxPriceSignal.get();
446
+ const selectedInventoryStatuses = selectedInventoryStatusesSignal.get();
447
+ const selectedProductOptions = selectedProductOptionsSignal.get();
448
+ const selectedCategory = selectedCategorySignal.get();
449
+ const selectedVisible = selectedVisibleSignal.get();
450
+ const selectedProductType = selectedProductTypeSignal.get();
451
+ // Check if any filters are different from default values
452
+ const hasPriceFilter = minPrice > catalogPriceRange.minPrice ||
453
+ maxPrice < catalogPriceRange.maxPrice;
454
+ const hasInventoryFilter = selectedInventoryStatuses.length > 0;
455
+ const hasProductOptionsFilter = Object.keys(selectedProductOptions).length > 0;
456
+ const hasCategoryFilter = selectedCategory !== null;
457
+ const hasVisibilityFilter = selectedVisible !== null;
458
+ const hasProductTypeFilter = selectedProductType !== null;
459
+ return (hasPriceFilter ||
460
+ hasInventoryFilter ||
461
+ hasProductOptionsFilter ||
462
+ hasCategoryFilter ||
463
+ hasVisibilityFilter ||
464
+ hasProductTypeFilter);
465
+ });
423
466
  // Computed signals for pagination
424
467
  const hasNextPageSignal = signalsService.computed(() => {
425
468
  const pagingMetadata = productsListService.pagingMetadata.get();
@@ -550,7 +593,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
550
593
  }
551
594
  if (selectedCategory) {
552
595
  newSearchOptions.filter["allCategoriesInfo.categories"] = {
553
- $matchItems: [{ _id: { $in: [selectedCategory] } }],
596
+ $matchItems: [{ _id: { $in: [selectedCategory._id] } }],
554
597
  };
555
598
  }
556
599
  if (selectedVisible !== null) {
@@ -571,7 +614,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
571
614
  priceRange: { min: minPrice, max: maxPrice },
572
615
  inventoryStatuses: selectedInventoryStatuses,
573
616
  productOptions: selectedProductOptions,
574
- ...(selectedCategory && { category: selectedCategory }),
575
617
  ...(selectedVisible !== null && { visible: selectedVisible }),
576
618
  ...(selectedProductType && { productType: selectedProductType }),
577
619
  };
@@ -580,6 +622,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
580
622
  filters: currentFilters,
581
623
  customizations,
582
624
  catalogBounds,
625
+ categorySlug: selectedCategory?.slug || undefined,
583
626
  });
584
627
  });
585
628
  }
@@ -702,7 +745,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
702
745
  selectedCategorySignal.set(null);
703
746
  selectedVisibleSignal.set(null);
704
747
  selectedProductTypeSignal.set(null);
705
- isFilteredSignal.set(false);
706
748
  },
707
749
  };
708
750
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-stores",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "prebuild": "cd ../media && yarn build && cd ../ecom && yarn build",