@liquidcommerce/elements-sdk 2.7.30 → 2.7.32

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.
@@ -3,19 +3,19 @@ export declare const API_CLIENT_URL: {
3
3
  production: string;
4
4
  };
5
5
  export declare const PRODUCT_LIST_FILTER_TYPES: {
6
- ENGRAVING: string;
7
- FULFILLMENT: string;
8
- PRICE: string;
9
- BRANDS: string;
10
- CATEGORIES: string;
11
- FLAVOR: string;
12
- REGION: string;
13
- VARIETY: string;
14
- VINTAGE: string;
15
- COUNTRY: string;
16
- APPELLATION: string;
17
- MATERIALS: string;
18
- SIZES: string;
6
+ readonly ENGRAVING: "engraving";
7
+ readonly FULFILLMENT: "fulfillment";
8
+ readonly PRICE: "price";
9
+ readonly BRANDS: "brands";
10
+ readonly CATEGORIES: "categories";
11
+ readonly FLAVOR: "flavor";
12
+ readonly REGION: "region";
13
+ readonly VARIETY: "variety";
14
+ readonly VINTAGE: "vintage";
15
+ readonly COUNTRY: "country";
16
+ readonly APPELLATION: "appellation";
17
+ readonly MATERIALS: "materials";
18
+ readonly SIZES: "sizes";
19
19
  };
20
20
  export declare const Z_INDEX: {
21
21
  readonly DRAWER_WRAPPER: 2147483640;
@@ -21,7 +21,7 @@ export interface IFilterParams {
21
21
  export type FacetFilterKeys = typeof FILTER_KEYS.BRANDS | typeof FILTER_KEYS.CATEGORIES | typeof FILTER_KEYS.FLAVOR | typeof FILTER_KEYS.REGION | typeof FILTER_KEYS.VARIETY | typeof FILTER_KEYS.VINTAGE | typeof FILTER_KEYS.COUNTRY | typeof FILTER_KEYS.APPELLATION | typeof FILTER_KEYS.TAGS | typeof FILTER_KEYS.MATERIALS | typeof FILTER_KEYS.SIZES | typeof FILTER_KEYS.ENGRAVING | typeof FILTER_KEYS.FULFILLMENT | typeof FILTER_KEYS.PRICE | typeof FILTER_KEYS.AVAILABILITY | typeof FILTER_KEYS.COLORS;
22
22
  export interface IFilterSchema {
23
23
  type: FacetFilterKeys;
24
- values: IFilterValue[];
24
+ values: IFilterValue[] | string[];
25
25
  }
26
26
  export interface IFilterValue {
27
27
  value: string;
@@ -14,6 +14,7 @@ export interface IInjectProductListParams {
14
14
  columns?: number;
15
15
  filters?: ProductListFilterType[];
16
16
  productUrl?: PLCProductUrl;
17
+ productOrder?: string[];
17
18
  }
18
19
  export interface IInjectProductListSearchParams {
19
20
  containerId: string;
@@ -1,6 +1,7 @@
1
1
  import type { ICheckboxLabelStates, ICreateCheckboxFilterHeaderParams, ICreateCheckboxFilterSearchContainerParams, ICreateCheckboxItemParams, IHandleCheckboxFilterSearchParams, IRenderCheckboxItemsParams } from '../../product-list.interface';
2
2
  export declare function createCheckboxItem(params: ICreateCheckboxItemParams): HTMLElement;
3
3
  export declare function handleCheckboxFilterSearch(params: IHandleCheckboxFilterSearchParams): void;
4
+ export declare function checkboxFilterContentId(filterType: string): string;
4
5
  export declare function createCheckboxFilterHeader(params: ICreateCheckboxFilterHeaderParams): HTMLElement;
5
6
  export declare function createCheckboxFilterSearchContainer(params: ICreateCheckboxFilterSearchContainerParams): HTMLElement;
6
7
  export declare function renderCheckboxItems(params: IRenderCheckboxItemsParams): void;
@@ -19,4 +19,5 @@ export declare function createPriceFilter(params: ICreatePriceFilterParams): {
19
19
  elements: HTMLElement[];
20
20
  chevronContainer: HTMLElement;
21
21
  sliderContainer: HTMLElement;
22
+ headerButton: HTMLButtonElement;
22
23
  };
@@ -25,6 +25,7 @@ export declare class ProductListCardComponent extends BaseComponent<IProductList
25
25
  private getSelectedSize;
26
26
  private getDisplayPrice;
27
27
  protected template(): HTMLElement[];
28
+ protected afterRender(): void;
28
29
  private updatePriceDisplay;
29
30
  private updateFulfillmentText;
30
31
  private updateImage;
@@ -13,15 +13,13 @@ export declare class ProductListFiltersComponent extends BaseComponent<IProductL
13
13
  private isPriceFilterCollapsed;
14
14
  private isFulfillmentFilterCollapsed;
15
15
  private appliedFiltersChipsContainer;
16
- private engravingFiltersContainer?;
17
- private fulfillmentFilterContainer?;
18
- private priceFiltersContainer?;
19
- private dynamicFiltersContainer?;
16
+ private filterSectionsContainer?;
20
17
  private filterButtonContainer?;
21
18
  private fulfillmentOptionsList?;
22
19
  private priceFilterChevronIcon?;
23
20
  private fulfillmentFilterChevronIcon?;
24
21
  private priceSliderWrapper?;
22
+ private priceFilterHeaderButton;
25
23
  private boundDrawerClosedHandler;
26
24
  private priceFilterDebounceTimer;
27
25
  constructor();
@@ -35,7 +33,8 @@ export declare class ProductListFiltersComponent extends BaseComponent<IProductL
35
33
  private createFilterSection;
36
34
  private rebuildContainer;
37
35
  private refreshUIStates;
38
- private transformFilterValue;
36
+ private buildFilterSections;
37
+ private refreshFilterButtonVisibility;
39
38
  private fetchFilters;
40
39
  private applyFiltersAndCloseDrawer;
41
40
  protected template(): HTMLElement[];
@@ -44,7 +43,10 @@ export declare class ProductListFiltersComponent extends BaseComponent<IProductL
44
43
  private buildFilterHeader;
45
44
  private clearAllActiveFilters;
46
45
  private removeSingleFilter;
46
+ private removeFacetValue;
47
47
  private getFilteredAndSanitizedFilters;
48
+ private isFilterConfigured;
49
+ private hasVisibleFilters;
48
50
  private buildAppliedFiltersChipsContainer;
49
51
  private handleFilterChange;
50
52
  private isEngravingCurrentlyDisabled;
@@ -0,0 +1,42 @@
1
+ import type { IFilterParamsValue } from '@/interfaces/api/product-list.interface';
2
+ import type { ProductListFilterType } from '@/interfaces/injection.interface';
3
+ import type { IProductListFilters } from './product-list.interface';
4
+ export declare const FILTER_KIND: {
5
+ readonly FACET: "facet";
6
+ readonly TOGGLE: "toggle";
7
+ readonly CHOICE: "choice";
8
+ readonly RANGE: "range";
9
+ };
10
+ export type FilterKind = (typeof FILTER_KIND)[keyof typeof FILTER_KIND];
11
+ interface IFilterDefinitionBase {
12
+ key: string;
13
+ type: ProductListFilterType;
14
+ }
15
+ export interface IFacetFilterDefinition extends IFilterDefinitionBase {
16
+ kind: typeof FILTER_KIND.FACET;
17
+ }
18
+ export interface IToggleFilterDefinition extends IFilterDefinitionBase {
19
+ kind: typeof FILTER_KIND.TOGGLE;
20
+ apiOn: string;
21
+ }
22
+ export interface IChoiceFilterDefinition extends IFilterDefinitionBase {
23
+ kind: typeof FILTER_KIND.CHOICE;
24
+ anyValue: string;
25
+ anyExpandsTo: string[];
26
+ values: string[];
27
+ }
28
+ export interface IRangeFilterDefinition extends IFilterDefinitionBase {
29
+ kind: typeof FILTER_KIND.RANGE;
30
+ defaultMin: string;
31
+ defaultMax: string;
32
+ }
33
+ export type IFilterDefinition = IFacetFilterDefinition | IToggleFilterDefinition | IChoiceFilterDefinition | IRangeFilterDefinition;
34
+ export declare const FILTER_DEFINITIONS: IFilterDefinition[];
35
+ export declare function getFilterDefinition(key: string): IFilterDefinition | undefined;
36
+ export declare function getConfiguredFilterDefinitions(configuredFilters: ProductListFilterType[] | undefined): IFilterDefinition[];
37
+ export declare function getDefaultFilterValue(definition: IFilterDefinition): IProductListFilters[string];
38
+ export declare function createDefaultFilterValues(): IProductListFilters;
39
+ export declare function isFilterValueApplied(definition: IFilterDefinition, value: IProductListFilters[string]): boolean;
40
+ export declare function decodeStoredFilterValue(definition: IFilterDefinition, stored: unknown): IProductListFilters[string];
41
+ export declare function encodeFilterValueForApi(definition: IFilterDefinition, value: IProductListFilters[string]): IFilterParamsValue;
42
+ export {};
@@ -1,8 +1,7 @@
1
1
  import type { IFilterSchema, IProductSearchParams } from '@/interfaces/api/product-list.interface';
2
2
  import type { ProductListFilterType } from '@/interfaces/injection.interface';
3
3
  import type { IExtendedFilterSchema, IProductListFilters } from './product-list.interface';
4
- export declare const FILTER_TYPE_MAP: Record<string, string>;
5
4
  export declare function getSanitizedFilters(filters: IFilterSchema[], configuredFilters: ProductListFilterType[]): IExtendedFilterSchema[];
6
5
  export declare function syncFiltersToAppliedFormat(filters: IProductListFilters): Record<string, string[]>;
7
- export declare function buildSearchParams(slug: string, page: number, perPage: number, searchTerm: string, currentFilters: IProductListFilters, configuredFilters: ProductListFilterType[]): IProductSearchParams;
8
6
  export declare function buildCurrentFiltersFromState(stateFilters: Record<string, string[]>): IProductListFilters;
7
+ export declare function buildSearchParams(slug: string, page: number, perPage: number, searchTerm: string, currentFilters: IProductListFilters, configuredFilters: ProductListFilterType[]): IProductSearchParams;
@@ -0,0 +1,3 @@
1
+ import type { IPLProductStore } from '@/core/store/interfaces/product-list.interface';
2
+ export declare function buildProductOrderIndex(productOrder: string[]): Map<string, number>;
3
+ export declare function sortProductsByOrder(products: IPLProductStore[], orderIndex: Map<string, number>): IPLProductStore[];
@@ -8,6 +8,7 @@ export interface IProductListComponentParams {
8
8
  columns: number;
9
9
  filters: ProductListFilterType[];
10
10
  productUrl?: PLCProductUrl;
11
+ productOrder?: string[];
11
12
  }
12
13
  export declare class ProductListComponent extends BaseComponent<IProductListComponentParams, IProductListComponent> {
13
14
  private products;
@@ -17,6 +18,7 @@ export declare class ProductListComponent extends BaseComponent<IProductListComp
17
18
  private loadingState;
18
19
  private scrollObserver;
19
20
  private initializationPromise;
21
+ private productOrderIndex;
20
22
  private unsubscribeFromState?;
21
23
  get hostClasses(): string[];
22
24
  constructor();
@@ -24,6 +26,8 @@ export declare class ProductListComponent extends BaseComponent<IProductListComp
24
26
  protected afterRender(): void;
25
27
  disconnected(): void;
26
28
  private initializeComponent;
29
+ private getProductOrderIndex;
30
+ private hasProductOrder;
27
31
  private toProductArray;
28
32
  private setLoadingState;
29
33
  private renderCurrentState;
@@ -35,6 +39,8 @@ export declare class ProductListComponent extends BaseComponent<IProductListComp
35
39
  private lastAnnouncedSentinelState;
36
40
  private renderProducts;
37
41
  private announceResultCount;
42
+ private getListConfig;
43
+ private insertOrderedProducts;
38
44
  private appendNewProducts;
39
45
  private setupInfiniteScroll;
40
46
  private createScrollSentinel;
@@ -26,6 +26,7 @@ export interface ICheckboxLabelStates {
26
26
  filteredItems: IFilterValue[];
27
27
  contentElement: HTMLElement | null;
28
28
  iconElement: HTMLElement | null;
29
+ toggleButton: HTMLButtonElement | null;
29
30
  hasUserInteraction: boolean;
30
31
  }
31
32
  export interface ICreateCheckboxItemParams {
@@ -134,6 +134,7 @@ interface IInjectProductListParams {
134
134
  columns?: number; // Default: 4
135
135
  filters?: ProductListFilterType[];
136
136
  productUrl?: PLCProductUrl; // string template OR Record<identifier, url> map
137
+ productOrder?: string[]; // display order, by UPC or grouping ID
137
138
  }
138
139
 
139
140
  // String template: replace {upc} or {grouping} per product.
@@ -177,6 +178,39 @@ await client.injectProductList({
177
178
  });
178
179
  ```
179
180
 
181
+ ### Example — explicit product order
182
+
183
+ `productOrder` sets the display order of the grid. Entries are the same product
184
+ identifiers `productUrl` maps and `injectProductElement` accepts — a UPC (any size
185
+ of the product) or a `salsifyGrouping` ID — and may be mixed freely.
186
+
187
+ ```javascript
188
+ await client.injectProductList({
189
+ containerId: 'products',
190
+ slug: 'best-sellers',
191
+ productOrder: [
192
+ '00832889005513',
193
+ 'GROUPING-33277',
194
+ '00619947000020',
195
+ ],
196
+ });
197
+ ```
198
+
199
+ It **reorders, it does not select.** The list's `slug` still decides which products
200
+ are returned; an identifier for a product outside the list matches nothing. Products
201
+ the array doesn't name render after the ones it does, in the order the API returned
202
+ them, so a partial list reorders only what it names.
203
+
204
+ Ordering also applies across pagination: a product named early in the array takes its
205
+ place even if the API returns it on a later page. Each new page is spliced into the
206
+ existing grid rather than appended, so cards already on screen are never re-rendered
207
+ and the scroll position holds. If the curated set is small, size `rows × columns` to
208
+ cover it in one page so no scroll is needed at all.
209
+
210
+ > A search term or an applied filter re-queries the API for a narrower result set.
211
+ > `productOrder` is re-applied to whatever comes back, so named products keep their
212
+ > relative order among the results that survive.
213
+
180
214
  ---
181
215
 
182
216
  ## injectProductListSearch()
@@ -240,6 +274,11 @@ await client.injectProductListFilters({
240
274
  });
241
275
  ```
242
276
 
277
+ > **Note:** Every section of the panel is opt-in — a filter type absent from `filters` is not
278
+ > rendered, `'price'`, `'fulfillment'` and `'engraving'` included — and all sections render
279
+ > collapsed. See
280
+ > [Available Filters](../guides/product-list-component.md#available-filters).
281
+
243
282
  ---
244
283
 
245
284
  ## Component Management
@@ -197,6 +197,7 @@ interface IInjectProductListParams {
197
197
  columns?: number;
198
198
  filters?: ProductListFilterType[];
199
199
  productUrl?: PLCProductUrl;
200
+ productOrder?: string[]; // display order, by UPC or grouping ID
200
201
  }
201
202
  ```
202
203
 
@@ -43,6 +43,7 @@ Use data attributes to configure the product list:
43
43
  - `data-columns`: Number of columns (default: 4)
44
44
  - `data-filters`: Comma-separated filter types
45
45
  - `data-product-url`: URL pattern for product detail pages (optional)
46
+ - `data-product-order`: Comma-separated product identifiers setting the display order (optional) — see [Product Order](#product-order)
46
47
 
47
48
  `data-product-url` accepts a string template with one of two placeholders:
48
49
  - `{grouping}` — replaced with the product's salsifyGrouping ID
@@ -113,7 +114,7 @@ The following filter type values can be used in the `filters` array:
113
114
  | `'brands'` | Checkboxes for available brands |
114
115
  | `'categories'` | Category selection checkboxes |
115
116
  | `'fulfillment'`| Shipping vs. on-demand delivery toggle |
116
- | `'engraving'` | Filter by personalization support |
117
+ | `'engraving'` | Toggle: on shows only personalizable products, off shows all |
117
118
  | `'sizes'` | Filter by product size/volume |
118
119
  | `'flavor'` | Filter by flavor profile |
119
120
  | `'region'` | Filter by region of origin |
@@ -128,6 +129,15 @@ The following filter type values can be used in the `filters` array:
128
129
  filters: ['price', 'brands', 'categories', 'fulfillment', 'sizes']
129
130
  ```
130
131
 
132
+ Every section of the filters panel is opt-in — `'price'`, `'fulfillment'` and `'engraving'`
133
+ included. A value you leave out of the array is not rendered, and an empty array draws no
134
+ filters panel at all. Omit `filters` entirely and the list falls back to the server-configured
135
+ defaults for that slug.
136
+
137
+ Sections render **collapsed**; the shopper opens the ones they care about. Nothing is
138
+ auto-expanded, not even a filter seeded from the URL — an applied value shows as a removable
139
+ chip above the sections, so it stays visible without opening anything.
140
+
131
141
  ## URL Query Param Filters
132
142
 
133
143
  The product list auto-applies filters from the page URL on first load. Useful for category landing pages, "shop the look" links, marketing emails, or any flow where you want to deep-link into a pre-filtered list.
@@ -150,6 +160,10 @@ Only filter keys that are configured for the list are honored — anything else
150
160
  | `engraving` | `true` or `false` | `?engraving=true` |
151
161
  | `price` | `min-max` range; `min-` or `-max` are accepted | `?price=20-150`, `?price=20-`, `?price=-150` |
152
162
 
163
+ `engraving=true` narrows the list to products that support personalization. `engraving=false` is
164
+ the filter's unset state and narrows nothing — it is equivalent to leaving the param off, not a
165
+ request for products that cannot be personalized.
166
+
153
167
  Invalid values are dropped (e.g. `?fulfillment=garbage`, `?price=abc` — no error, the filter just isn't applied). Combining params is supported:
154
168
 
155
169
  ```
@@ -227,6 +241,66 @@ await client.injectProductList({
227
241
 
228
242
  Total products per page = rows × columns (e.g., 5 × 4 = 20 products)
229
243
 
244
+ ## Product Order
245
+
246
+ By default the grid renders products in the order the API returns them for the
247
+ list's slug. `productOrder` overrides that with an explicit order you control
248
+ from the page.
249
+
250
+ ```javascript
251
+ await client.injectProductList({
252
+ containerId: 'products',
253
+ slug: 'my-collection-slug',
254
+ productOrder: [
255
+ '00832889005513',
256
+ 'GROUPING-33277',
257
+ '00619947000020',
258
+ ],
259
+ });
260
+ ```
261
+
262
+ ```html
263
+ <div
264
+ data-liquid-commerce-elements-products-list="my-collection-slug"
265
+ data-product-order="00832889005513,GROUPING-33277,00619947000020"
266
+ ></div>
267
+ ```
268
+
269
+ **Identifiers.** Each entry is a UPC or a `salsifyGrouping` ID — the same identifier
270
+ concept the [Product URL Map](#product-url-map) keys on and `injectProductElement`
271
+ accepts. The two forms mix freely in one array. A UPC identifies a single size, so
272
+ naming any size of a product positions that product.
273
+
274
+ **It reorders; it does not select.** The slug still decides which products the list
275
+ contains. An identifier for a product outside the list matches nothing, and no error
276
+ is raised. Products the array doesn't name render after the ones it does, keeping
277
+ their API order among themselves — so a partial list reorders only what it names.
278
+
279
+ **Pagination.** A product named early keeps its position even if the API returns it on
280
+ a later page. Each new page is spliced into the grid at the right slots rather than
281
+ appended, so cards already on screen are left untouched — scroll position and keyboard
282
+ focus survive loading a page. For a small curated set, size `rows × columns` to cover
283
+ it in a single page so no scroll is needed at all.
284
+
285
+ **Search and filters** re-query the API for a narrower result set. The order is
286
+ re-applied to whatever comes back, so named products keep their relative order among
287
+ the results that survive.
288
+
289
+ **Nothing about `productOrder` throws.** Ordering is presentational and the grid
290
+ renders fine without it, so a malformed value costs you the ordering and nothing
291
+ else — never the product list itself.
292
+
293
+ - An entry that isn't a non-empty string is skipped with a console warning, the way
294
+ an unrecognized `filters` value is. The surviving entries keep their relative
295
+ order, so the rest of your ordering still applies around the dropped one.
296
+ - A `productOrder` that isn't an array is ignored entirely with a warning, and the
297
+ list renders in the order the API returns.
298
+
299
+ > The warning goes through the SDK logger, which is silent in production and at the
300
+ > default `debugMode: 'none'`. On a live storefront a dropped entry produces no
301
+ > console output at all, so validate the array you generate rather than relying on
302
+ > the warning to surface a bad one.
303
+
230
304
  ## Infinite Scroll
231
305
 
232
306
  ### How It Works
@@ -274,6 +348,32 @@ Product card images automatically have their white/near-white backgrounds remove
274
348
 
275
349
  **Click on image:** Navigate to the configured `productUrl` (only when `productUrl` is set — otherwise the image is not a link)
276
350
 
351
+ ### Card Identifiers
352
+
353
+ Each `product-list-card-lc` host carries the product's identifiers as data
354
+ attributes, so a card can be traced back to catalogue data while inspecting the
355
+ page:
356
+
357
+ ```html
358
+ <product-list-card-lc data-grouping="GROUPING-33277" data-upc="00832889005513">
359
+ ```
360
+
361
+ - `data-grouping` — the product's `salsifyGrouping` ID. Fixed for the life of the card.
362
+ - `data-upc` — the **selected size's** UPC, the same value `{upc}` resolves to in a
363
+ `productUrl` template. It updates when the shopper changes size.
364
+
365
+ Both use the identifier forms accepted everywhere else in the SDK, so a value read
366
+ off a card can be pasted straight into `productOrder`, a `productUrl` map key, or
367
+ `injectProductElement`.
368
+
369
+ > **These are for inspection, not for scripting.** Cards render inside the SDK's
370
+ > shadow root, which is **always closed in production** — `openShadowDom` is forced
371
+ > to `false` there and only has effect in development. Chrome DevTools displays
372
+ > closed shadow roots, so the attributes are visible when inspecting the page, but
373
+ > `document.querySelector('[data-upc]')` from partner JavaScript will not reach them
374
+ > on a live storefront. To react to card activity from your own code, use the
375
+ > [events](#events) instead.
376
+
277
377
  ## Customization
278
378
 
279
379
  ### Theme Configuration
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "LiquidCommerce Elements SDK",
4
4
  "license": "UNLICENSED",
5
5
  "author": "LiquidCommerce Team",
6
- "version": "2.7.30",
6
+ "version": "2.7.32",
7
7
  "homepage": "https://docs.liquidcommerce.co/elements-sdk",
8
8
  "repository": {
9
9
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "module": "./dist/index.esm.js",
16
16
  "types": "./dist/types/index.d.ts",
17
- "packageManager": "pnpm@11.21.0",
17
+ "packageManager": "pnpm@11.22.0",
18
18
  "exports": {
19
19
  ".": {
20
20
  "types": "./dist/types/index.d.ts",
@@ -107,7 +107,7 @@
107
107
  "theming"
108
108
  ],
109
109
  "devDependencies": {
110
- "@biomejs/biome": "^2.5.8",
110
+ "@biomejs/biome": "^2.5.9",
111
111
  "@commitlint/cli": "^21.2.2",
112
112
  "@commitlint/config-conventional": "^21.2.2",
113
113
  "@rollup/plugin-alias": "^6.0.0",