@liquidcommerce/elements-sdk 2.7.13 → 2.7.15

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.
@@ -28,11 +28,14 @@ export declare const SHARED_ATTR: {
28
28
  readonly JSON_SCRIPT: {
29
29
  readonly DEVELOPMENT: "data-liquid-commerce-elements-development";
30
30
  readonly CUSTOM_THEME: "data-liquid-commerce-elements-custom-theme";
31
+ readonly PRODUCT_URLS: "data-liquid-commerce-elements-product-urls";
31
32
  };
32
33
  };
34
+ export type ProductUrlsConfig = Record<string, Record<string, string>>;
33
35
  export declare function startsWithLcePrefix(value: string | null): boolean;
34
36
  export declare function getDevelopmentConfigs(): ILiquidCommerceElementsDevelopmentConfig | undefined;
35
37
  export declare function getCustomThemeConfig(): IClientCustomThemeConfig | undefined;
38
+ export declare function getProductUrlsConfig(): ProductUrlsConfig | undefined;
36
39
  export declare function triggerAutoInit(initFunction: () => Promise<void>, errorPrefix: string): void;
37
40
  export declare function setupCheckout(script: HTMLScriptElement, injectFn: (params: IInjectCheckoutParams) => Promise<IInjectedComponent | null>, exitCheckoutFn: () => void, options: {
38
41
  errorPrefix: string;
@@ -1,5 +1,4 @@
1
1
  import { type ComponentType } from '@/enums';
2
- export declare function applyConfigDefaults<T extends Record<string, any>>(data: T): T;
3
2
  export declare function normalizeProductListLists<T extends Record<string, any>>(data: T): T;
4
3
  export declare function deepMergeConfigs<T extends Record<string, any>>(target: T, source: Partial<T>, currentPath?: string): T;
5
4
  export declare const layoutFieldToComponentTypes: Map<string, ComponentType[]>;
@@ -3,6 +3,7 @@ import { LoggerFactory } from '@/core/logger/logger-factory';
3
3
  import { PubSubService } from '@/core/pubsub/pubsub.service';
4
4
  import { StoreService } from '@/core/store/store.service';
5
5
  import type { ILocation } from '@/interfaces/api/address.interface';
6
+ import type { IProductSizeEngraving, IProductVariant } from '@/interfaces/api/product.interface';
6
7
  import { ThemeProviderService } from '@/modules/theme-provider/theme-provider.service';
7
8
  export declare abstract class BaseActionService {
8
9
  protected readonly pubSub: PubSubService;
@@ -12,4 +13,10 @@ export declare abstract class BaseActionService {
12
13
  protected readonly logger: ReturnType<typeof LoggerFactory.get>;
13
14
  constructor(loggerName: string);
14
15
  protected getLocation(): ILocation | undefined;
16
+ protected sanitizeEngravingInput(rawLines: string[] | undefined, identifier: string): string[] | undefined;
17
+ protected clampEngravingToSizeLimits(requestedLines: string[], sizeEngraving: IProductSizeEngraving | undefined, identifier: string): string[] | undefined;
18
+ protected findVariantForFulfillment(variants: (IProductVariant | undefined)[], wantsEngraving: boolean): {
19
+ variant: IProductVariant | null;
20
+ engravingDropped: boolean;
21
+ };
15
22
  }
@@ -5,6 +5,7 @@ export interface IAddProductParams {
5
5
  identifier: string;
6
6
  fulfillmentType: FulfillmentType;
7
7
  quantity: number;
8
+ engravingLines?: string[];
8
9
  }
9
10
  export interface ICartActions {
10
11
  openCart: () => void;
@@ -3,6 +3,7 @@ import type { ProductListFilterType } from '@/interfaces/injection.interface';
3
3
  export type PLCPresentationModeType = 'drawer' | 'modal';
4
4
  export type PLCListType = 'curated' | 'dynamic';
5
5
  export type PLCCardStyle = 'card' | 'ghost';
6
+ export type PLCProductUrl = string | Record<string, string>;
6
7
  export interface IPLCProductCard {
7
8
  style: PLCCardStyle;
8
9
  cornerRadius: string;
@@ -16,6 +17,8 @@ export interface IPLCProductCard {
16
17
  showQuantityCounter: boolean;
17
18
  enablePreCart: boolean;
18
19
  showCollectionTags: boolean;
20
+ productUrl: PLCProductUrl | null;
21
+ noAvailabilityText: string;
19
22
  }
20
23
  export interface IPLCList {
21
24
  type: PLCListType;
@@ -27,7 +30,6 @@ export interface IPLCList {
27
30
  availableFilters: ProductListFilterType[];
28
31
  rows: number;
29
32
  columns: number;
30
- productUrl: string | null;
31
33
  }
32
34
  export interface IProductListLayout {
33
35
  lists: IPLCList[];
@@ -1,12 +1,12 @@
1
1
  import type { DeepPartial } from '@/interfaces/config.interface';
2
2
  export type FulfillmentDisplayType = 'carousel' | 'popup';
3
- export type DescriptionPosition = 'above' | 'below';
3
+ export type DescriptionPositionType = 'above' | 'below';
4
4
  export interface IProductLayout {
5
5
  showImages: boolean;
6
6
  showOnlyMainImage: boolean;
7
7
  showTitle: boolean;
8
8
  showDescription: boolean;
9
- descriptionPosition: DescriptionPosition;
9
+ descriptionPosition: DescriptionPositionType;
10
10
  showQuantityCounter: boolean;
11
11
  showOffHours: boolean;
12
12
  quantityCounterStyle: 'outlined' | 'ghost';
@@ -1,5 +1,6 @@
1
1
  import type { PRODUCT_LIST_FILTER_TYPES } from '@/constants';
2
2
  import type { ComponentType } from '@/enums';
3
+ import type { PLCProductUrl } from '@/interfaces/configs/product-list.interface';
3
4
  import type { IAddressOptions } from '@/modules/address/address.interface';
4
5
  export interface IInjectProductElement {
5
6
  containerId: string;
@@ -12,7 +13,7 @@ export interface IInjectProductListParams {
12
13
  rows?: number;
13
14
  columns?: number;
14
15
  filters?: ProductListFilterType[];
15
- productUrl?: string;
16
+ productUrl?: PLCProductUrl;
16
17
  }
17
18
  export interface IInjectProductListSearchParams {
18
19
  containerId: string;
@@ -13,6 +13,7 @@ export declare class ProductCommands extends BaseCommand {
13
13
  createProductInstance(productId: string, firstTime?: boolean): Promise<void>;
14
14
  loadMultipleProducts(productIds: string[]): Promise<void>;
15
15
  loadProduct(productId: string, customProductData?: IProductAvailabilityResponse | null): Promise<void>;
16
+ cacheProductFromApiData(identifier: string, apiData: IProductAvailabilityResponse): void;
16
17
  selectSize(productId: string, sizeId: string): Promise<void>;
17
18
  selectFulfillment(productId: string, selectedFulfillmentId: string): Promise<void>;
18
19
  updateQuantity(productId: string, delta: number): Promise<void>;
@@ -1,16 +1,19 @@
1
1
  import { BaseComponent, type IOnStoreChanged } from '@/core/base-component.service';
2
2
  import type { IProductListComponent } from '@/interfaces/configs';
3
+ import type { PLCProductUrl } from '@/interfaces/configs/product-list.interface';
3
4
  export interface IProductListCardParams {
4
5
  slug: string;
5
6
  productId: string;
6
- productUrl?: string;
7
+ productUrl?: PLCProductUrl;
7
8
  }
8
9
  export declare class ProductListCardComponent extends BaseComponent<IProductListCardParams, IProductListComponent> {
9
10
  private imageElement;
10
11
  private priceElement;
11
12
  private personalizeElement;
12
13
  private fulfillmentTextElement;
14
+ private fulfillmentWrapperElement;
13
15
  private addToCartButton;
16
+ private noAvailabilityElement;
14
17
  private previousSelectedSizeId;
15
18
  private previousSelectedFulfillmentId;
16
19
  constructor();
@@ -27,10 +30,13 @@ export declare class ProductListCardComponent extends BaseComponent<IProductList
27
30
  private updateImage;
28
31
  private updatePersonalizeVisibility;
29
32
  private updateAddToCartButton;
33
+ private syncFulfillmentWrapperContent;
30
34
  private generateProductUrl;
31
35
  private createImageSection;
32
36
  private createContentSection;
37
+ private createNoAvailabilityMessage;
33
38
  private createFulfillmentSectionWrapper;
39
+ private createDeliveryOptionsButton;
34
40
  private createFulfillmentTextSection;
35
41
  private createAddToCartSection;
36
42
  }
@@ -0,0 +1,3 @@
1
+ import type { ProductListFilterType } from '@/interfaces/injection.interface';
2
+ import type { IProductListFilters } from './product-list.interface';
3
+ export declare function parseQueryParamsToFilters(configuredFilters: ProductListFilterType[], search?: string): IProductListFilters | null;
@@ -9,7 +9,7 @@ export declare class ProductListCommands extends BaseCommand {
9
9
  private readonly cartCommands;
10
10
  constructor();
11
11
  static getInstance(): ProductListCommands;
12
- initializeListStore(slug: string): void;
12
+ initializeListStore(slug: string, configuredFilters?: ProductListFilterType[]): void;
13
13
  updateSearchTerm(slug: string, searchTerm: string): void;
14
14
  setLoading(slug: string, isLoading: boolean): void;
15
15
  rerenderProductLists(): void;
@@ -1,12 +1,13 @@
1
1
  import { BaseComponent, type IOnStoreChanged } from '@/core/base-component.service';
2
2
  import type { IProductListComponent } from '@/interfaces/configs';
3
+ import type { PLCProductUrl } from '@/interfaces/configs/product-list.interface';
3
4
  import type { ProductListFilterType } from '@/interfaces/injection.interface';
4
5
  export interface IProductListComponentParams {
5
6
  slug: string;
6
7
  rows: number;
7
8
  columns: number;
8
9
  filters: ProductListFilterType[];
9
- productUrl?: string;
10
+ productUrl?: PLCProductUrl;
10
11
  }
11
12
  export declare class ProductListComponent extends BaseComponent<IProductListComponentParams, IProductListComponent> {
12
13
  private products;
@@ -81,9 +81,20 @@ interface IAddProductParams {
81
81
  identifier: string; // Product UPC, SKU, or ID
82
82
  fulfillmentType: FulfillmentType; // 'shipping' or 'onDemand'
83
83
  quantity: number; // Number of items
84
+ engravingLines?: string[]; // Optional engraving (see below)
84
85
  }
85
86
  ```
86
87
 
88
+ #### `engravingLines` (optional)
89
+
90
+ Pre-fills an engraving for the added item. The SDK enforces the product's engraving rules and degrades silently rather than failing the call:
91
+
92
+ - Non-string-array input is ignored (warning logged).
93
+ - Blank/whitespace-only entries are stripped; an empty result is treated as no engraving.
94
+ - If the product/size doesn't support engraving, lines are dropped.
95
+ - Lines beyond `maxLines` are sliced off; any line longer than `maxCharsPerLine` is truncated.
96
+ - If no engravable variant is available for the chosen fulfillment, lines are dropped and the item is still added.
97
+
87
98
  ### Example
88
99
 
89
100
  ```javascript
@@ -109,6 +120,16 @@ await window.LiquidCommerce.elements.actions.cart.addProduct([
109
120
  quantity: 1
110
121
  }
111
122
  ]);
123
+
124
+ // Add a product with engraving
125
+ await window.LiquidCommerce.elements.actions.cart.addProduct([
126
+ {
127
+ identifier: '00619947000020',
128
+ fulfillmentType: 'shipping',
129
+ quantity: 1,
130
+ engravingLines: ['Happy Birthday', 'Love, Sam']
131
+ }
132
+ ], true);
112
133
  ```
113
134
 
114
135
  ### Address Requirement
@@ -62,6 +62,8 @@ addProduct(params: IAddProductParams[], openCheckout?: boolean): Promise<void>
62
62
 
63
63
  Add products directly to checkout, bypassing the cart.
64
64
 
65
+ `IAddProductParams.engravingLines?: string[]` — optional engraving. Same rules as [`actions.cart.addProduct()`](./cart-actions.md#actionscartaddproduct): invalid input is ignored, blanks stripped, lines clamped to the product's `maxLines` / `maxCharsPerLine`, and engraving is dropped (without failing) when the product, variant, or fulfillment doesn't support it.
66
+
65
67
  ```javascript
66
68
  await window.LiquidCommerce.elements.actions.checkout.addProduct([
67
69
  {
@@ -70,6 +72,16 @@ await window.LiquidCommerce.elements.actions.checkout.addProduct([
70
72
  quantity: 1
71
73
  }
72
74
  ], true); // Open checkout after adding
75
+
76
+ // With engraving
77
+ await window.LiquidCommerce.elements.actions.checkout.addProduct([
78
+ {
79
+ identifier: '00619947000020',
80
+ fulfillmentType: 'shipping',
81
+ quantity: 1,
82
+ engravingLines: ['For Dad']
83
+ }
84
+ ], true);
73
85
  ```
74
86
 
75
87
  ### actions.checkout.addAnonymousProduct()
@@ -130,11 +130,15 @@ interface IInjectProductListParams {
130
130
  rows?: number; // Default: 3
131
131
  columns?: number; // Default: 4
132
132
  filters?: ProductListFilterType[];
133
- productUrl?: string; // URL pattern with {identifier}
133
+ productUrl?: PLCProductUrl; // string template OR Record<identifier, url> map
134
134
  }
135
+
136
+ // String template: replace {upc} or {grouping} per product.
137
+ // Map: keys are product identifiers (UPC or salsifyGrouping ID); UPC checked first.
138
+ type PLCProductUrl = string | Record<string, string>;
135
139
  ```
136
140
 
137
- ### Example
141
+ ### Example — string template
138
142
 
139
143
  ```javascript
140
144
  await client.injectProductList({
@@ -143,7 +147,30 @@ await client.injectProductList({
143
147
  rows: 4,
144
148
  columns: 3,
145
149
  filters: ['price', 'brands', 'categories'],
146
- productUrl: '/product/{identifier}'
150
+ productUrl: '/product/{grouping}'
151
+ });
152
+ ```
153
+
154
+ > The `filters` array also acts as the whitelist for **URL query param filtering** —
155
+ > the product list reads `?brands=...&price=...` etc. from `window.location.search`
156
+ > on first mount and applies them. See
157
+ > [URL Query Param Filters](../guides/product-list-component.md#url-query-param-filters).
158
+
159
+ ### Example — URL map (partner-owned dedicated PDPs)
160
+
161
+ Use this form when each product has a hand-curated URL that can't be derived
162
+ from a single `{upc}` or `{grouping}` token. See the
163
+ [Product List guide](../guides/product-list-component.md#product-url-map) for
164
+ the equivalent declarative `<script type="application/json">` pattern.
165
+
166
+ ```javascript
167
+ await client.injectProductList({
168
+ containerId: 'products',
169
+ slug: 'best-sellers',
170
+ productUrl: {
171
+ 'GROUPING-33277': '/wines/macallan-12-special-edition',
172
+ '00832889005513': '/spirits/cabernet-2018-club-only',
173
+ },
147
174
  });
148
175
  ```
149
176
 
@@ -190,10 +190,26 @@ interface IInjectProductListParams {
190
190
  rows?: number;
191
191
  columns?: number;
192
192
  filters?: ProductListFilterType[];
193
- productUrl?: string;
193
+ productUrl?: PLCProductUrl;
194
194
  }
195
195
  ```
196
196
 
197
+ ### PLCProductUrl
198
+
199
+ ```typescript
200
+ /**
201
+ * Product card link configuration.
202
+ *
203
+ * - String: template with `{upc}` (selected size's UPC) or `{grouping}`
204
+ * (product's salsifyGrouping ID). Replaced per product card.
205
+ * - Map: keyed by product identifier (UPC or salsifyGrouping ID — same
206
+ * identifier types accepted by `injectProductElement`). The card looks up
207
+ * UPC first, then grouping ID. Use this when partner PDPs have
208
+ * hand-curated URLs that aren't derivable from a single token.
209
+ */
210
+ type PLCProductUrl = string | Record<string, string>;
211
+ ```
212
+
197
213
  ### IInjectProductListSearchParams
198
214
 
199
215
  ```typescript
@@ -177,7 +177,7 @@ await client.injectProductList({
177
177
  slug: 'whiskey-collection',
178
178
  rows: 4,
179
179
  columns: 3,
180
- productUrl: '/products/{identifier}'
180
+ productUrl: '/products/{grouping}'
181
181
  });
182
182
  ```
183
183
 
@@ -33,7 +33,7 @@ Use data attributes to configure the product list:
33
33
  data-rows="3"
34
34
  data-columns="4"
35
35
  data-filters="price,brands,categories"
36
- data-product-url="/product/{identifier}"
36
+ data-product-url="/product/{grouping}"
37
37
  ></div>
38
38
  ```
39
39
 
@@ -44,7 +44,13 @@ Use data attributes to configure the product list:
44
44
  - `data-filters`: Comma-separated filter types
45
45
  - `data-product-url`: URL pattern for product detail pages (optional)
46
46
 
47
- The `{identifier}` placeholder in `data-product-url` is replaced with the product's identifier.
47
+ `data-product-url` accepts a string template with one of two placeholders:
48
+ - `{grouping}` — replaced with the product's salsifyGrouping ID
49
+ - `{upc}` — replaced with the selected size's UPC
50
+
51
+ If your PDP URLs aren't derivable from a single placeholder (e.g. each
52
+ product has a hand-curated marketing slug), use the
53
+ [Product URL Map](#product-url-map) instead.
48
54
 
49
55
  ### With Search and Filters
50
56
 
@@ -62,7 +68,7 @@ Separate containers for search and filters:
62
68
  data-liquid-commerce-elements-products-list="my-collection-slug"
63
69
  data-rows="4"
64
70
  data-columns="3"
65
- data-product-url="/products/{identifier}"
71
+ data-product-url="/products/{grouping}"
66
72
  ></div>
67
73
  ```
68
74
 
@@ -80,7 +86,7 @@ await client.injectProductList({
80
86
  rows: 3,
81
87
  columns: 4,
82
88
  filters: ['price', 'brands', 'categories', 'fulfillment'],
83
- productUrl: '/product/{identifier}'
89
+ productUrl: '/product/{grouping}'
84
90
  });
85
91
 
86
92
  // Inject search (optional)
@@ -122,6 +128,55 @@ The following filter type values can be used in the `filters` array:
122
128
  filters: ['price', 'brands', 'categories', 'fulfillment', 'sizes']
123
129
  ```
124
130
 
131
+ ## URL Query Param Filters
132
+
133
+ 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.
134
+
135
+ ### Whitelist
136
+
137
+ Only filter keys that are configured for the list are honored — anything else in the URL is silently ignored. The whitelist resolves in this priority order:
138
+
139
+ 1. `data-filters` on `<div data-liquid-commerce-elements-products-list>` (use this when the page does **not** mount a filters UI but you still want URL filtering — e.g. a curated category page).
140
+ 2. `data-filters` on the matching `<... -products-list-filters>` container (the common case when a filters panel is mounted).
141
+ 3. `availableFilters` from the theme config for the list slug.
142
+ 4. `filters` array passed to `injectProductList(...)` programmatically.
143
+
144
+ ### Supported formats
145
+
146
+ | Filter | URL format | Example |
147
+ | --- | --- | --- |
148
+ | Multi-value (`brands`, `categories`, `flavor`, `region`, `variety`, `vintage`, `country`, `appellation`, `materials`, `sizes`) | Comma-separated **or** repeated keys | `?brands=Bacardi,Glenlivet` or `?brands=Bacardi&brands=Glenlivet` |
149
+ | `fulfillment` | Single value: `all`, `shipping`, or `onDemand` | `?fulfillment=shipping` |
150
+ | `engraving` | `true` or `false` | `?engraving=true` |
151
+ | `price` | `min-max` range; `min-` or `-max` are accepted | `?price=20-150`, `?price=20-`, `?price=-150` |
152
+
153
+ Invalid values are dropped (e.g. `?fulfillment=garbage`, `?price=abc` — no error, the filter just isn't applied). Combining params is supported:
154
+
155
+ ```
156
+ https://yoursite.com/best-sellers?brands=Bacardi&categories=Wine&price=20-150&fulfillment=shipping
157
+ ```
158
+
159
+ ### Behavior
160
+
161
+ - URL params win over any state persisted from a previous session.
162
+ - Once the list mounts, the filters panel (if present) reflects the seeded values, and the initial product fetch is filtered.
163
+ - Subsequent in-page interactions (toggling filters, scrolling, etc.) do not write back to the URL — the URL is read-only at load time.
164
+
165
+ ### Standalone use (no filters component)
166
+
167
+ URL filtering works without injecting a filters panel. Declare the whitelist on the products-list container itself:
168
+
169
+ ```html
170
+ <div
171
+ data-liquid-commerce-elements-products-list="curated-page"
172
+ data-filters="price,brands,categories"
173
+ data-rows="4"
174
+ data-columns="4"
175
+ ></div>
176
+ ```
177
+
178
+ Now `https://yoursite.com/curated-page?brands=Bacardi&price=20-150` filters the list on load even though no filters UI is present.
179
+
125
180
  ## Search Functionality
126
181
 
127
182
  ### Search Box
@@ -213,7 +268,7 @@ Each product card shows:
213
268
 
214
269
  ### Card Interaction
215
270
 
216
- **Click on card:** Navigate to product detail page (if `productUrl` configured)
271
+ **Click on card:** Navigate to product detail page (if `productUrl` configured — see [Product URL Map](#product-url-map) for partner-owned PDP URLs that aren't derivable from a token).
217
272
 
218
273
  **Quick Add:** Add product to cart directly from list view (if enabled)
219
274
 
@@ -298,7 +353,7 @@ See [Configuration Reference](../api/configuration.md#product-list-theme) for th
298
353
  data-liquid-commerce-elements-products-list="whiskey-collection"
299
354
  data-rows="4"
300
355
  data-columns="3"
301
- data-product-url="/whiskey/{identifier}"
356
+ data-product-url="/whiskey/{grouping}"
302
357
  ></div>
303
358
  </main>
304
359
  </div>
@@ -339,13 +394,13 @@ const productType = getProductTypeFromData();
339
394
  let urlPattern;
340
395
  switch (productType) {
341
396
  case 'whiskey':
342
- urlPattern = '/spirits/whiskey/{identifier}';
397
+ urlPattern = '/spirits/whiskey/{grouping}';
343
398
  break;
344
399
  case 'wine':
345
- urlPattern = '/wine/{identifier}';
400
+ urlPattern = '/wine/{grouping}';
346
401
  break;
347
402
  default:
348
- urlPattern = '/products/{identifier}';
403
+ urlPattern = '/products/{grouping}';
349
404
  }
350
405
 
351
406
  await client.injectProductList({
@@ -357,6 +412,76 @@ await client.injectProductList({
357
412
  });
358
413
  ```
359
414
 
415
+ ### Product URL Map
416
+
417
+ For partners whose PDPs have hand-curated URLs that aren't derivable from a
418
+ single placeholder (e.g. dedicated marketing pages, Shopify handles, WordPress
419
+ slugs), pass a **map** instead of a string template. Keys are product
420
+ identifiers — either a UPC or a salsifyGrouping ID, the same identifier types
421
+ accepted by `injectProductElement`. The card looks up UPC first, then grouping
422
+ ID; products not in the map render without a link.
423
+
424
+ #### Declarative — JSON script tag
425
+
426
+ Drop a single `<script type="application/json">` tag anywhere on the page,
427
+ keyed by list slug → identifier → URL. Generate it server-side from your CMS.
428
+
429
+ ```html
430
+ <script data-liquid-commerce-elements-product-urls type="application/json">
431
+ {
432
+ "best-sellers": {
433
+ "GROUPING-33277": "/wines/macallan-12-special-edition",
434
+ "00832889005513": "/spirits/cabernet-2018-club-only"
435
+ },
436
+ "limited-releases": {
437
+ "GROUPING-78941": "/exclusive/pappy-23-allocation"
438
+ }
439
+ }
440
+ </script>
441
+
442
+ <div data-liquid-commerce-elements-products-list="best-sellers"></div>
443
+ ```
444
+
445
+ When both `data-product-url` and a slug entry in this script are present for
446
+ the same list, the **map wins** — it's the more specific intent.
447
+
448
+ ##### Shopify Liquid
449
+
450
+ ```liquid
451
+ <script data-liquid-commerce-elements-product-urls type="application/json">
452
+ {
453
+ "best-sellers": {
454
+ {% for p in collections.best-sellers.products %}
455
+ "{{ p.metafields.lc.grouping_id }}": "{{ p.url }}"{% unless forloop.last %},{% endunless %}
456
+ {% endfor %}
457
+ }
458
+ }
459
+ </script>
460
+ ```
461
+
462
+ ##### WordPress / WooCommerce (PHP)
463
+
464
+ ```php
465
+ <script data-liquid-commerce-elements-product-urls type="application/json">
466
+ <?= json_encode(['best-sellers' => $lc_identifier_to_pdp_url_map]) ?>
467
+ </script>
468
+ ```
469
+
470
+ #### Programmatic
471
+
472
+ ```javascript
473
+ await client.injectProductList({
474
+ containerId: 'products',
475
+ slug: 'best-sellers',
476
+ rows: 3,
477
+ columns: 4,
478
+ productUrl: {
479
+ 'GROUPING-33277': '/wines/macallan-12-special-edition',
480
+ '00832889005513': '/spirits/cabernet-2018-club-only',
481
+ },
482
+ });
483
+ ```
484
+
360
485
  ## Events
361
486
 
362
487
  While product list events are primarily internal, you can listen for cart events when users add products:
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.13",
6
+ "version": "2.7.15",
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@10.33.0",
17
+ "packageManager": "pnpm@10.33.4",
18
18
  "exports": {
19
19
  ".": {
20
20
  "types": "./dist/types/index.d.ts",
@@ -77,9 +77,9 @@
77
77
  "embeddable commerce"
78
78
  ],
79
79
  "devDependencies": {
80
- "@biomejs/biome": "^2.4.13",
81
- "@commitlint/cli": "^20.5.0",
82
- "@commitlint/config-conventional": "^20.5.0",
80
+ "@biomejs/biome": "^2.4.14",
81
+ "@commitlint/cli": "^20.5.3",
82
+ "@commitlint/config-conventional": "^20.5.3",
83
83
  "@rollup/plugin-alias": "^6.0.0",
84
84
  "@rollup/plugin-commonjs": "^29.0.2",
85
85
  "@rollup/plugin-json": "^6.1.0",
@@ -97,7 +97,7 @@
97
97
  "conventional-changelog": "^7.2.0",
98
98
  "husky": "^9.1.7",
99
99
  "process": "^0.11.10",
100
- "rollup": "^4.60.2",
100
+ "rollup": "^4.60.3",
101
101
  "rollup-obfuscator": "^4.1.1",
102
102
  "rollup-plugin-typescript2": "^0.37.0",
103
103
  "semantic-release": "^25.0.3",