@liquidcommerce/elements-sdk 2.7.30 → 2.7.31

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.
@@ -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;
@@ -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;
@@ -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,7 @@ export declare class ProductListComponent extends BaseComponent<IProductListComp
35
39
  private lastAnnouncedSentinelState;
36
40
  private renderProducts;
37
41
  private announceResultCount;
42
+ private insertOrderedProducts;
38
43
  private appendNewProducts;
39
44
  private setupInfiniteScroll;
40
45
  private createScrollSentinel;
@@ -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()
@@ -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
@@ -227,6 +228,66 @@ await client.injectProductList({
227
228
 
228
229
  Total products per page = rows × columns (e.g., 5 × 4 = 20 products)
229
230
 
231
+ ## Product Order
232
+
233
+ By default the grid renders products in the order the API returns them for the
234
+ list's slug. `productOrder` overrides that with an explicit order you control
235
+ from the page.
236
+
237
+ ```javascript
238
+ await client.injectProductList({
239
+ containerId: 'products',
240
+ slug: 'my-collection-slug',
241
+ productOrder: [
242
+ '00832889005513',
243
+ 'GROUPING-33277',
244
+ '00619947000020',
245
+ ],
246
+ });
247
+ ```
248
+
249
+ ```html
250
+ <div
251
+ data-liquid-commerce-elements-products-list="my-collection-slug"
252
+ data-product-order="00832889005513,GROUPING-33277,00619947000020"
253
+ ></div>
254
+ ```
255
+
256
+ **Identifiers.** Each entry is a UPC or a `salsifyGrouping` ID — the same identifier
257
+ concept the [Product URL Map](#product-url-map) keys on and `injectProductElement`
258
+ accepts. The two forms mix freely in one array. A UPC identifies a single size, so
259
+ naming any size of a product positions that product.
260
+
261
+ **It reorders; it does not select.** The slug still decides which products the list
262
+ contains. An identifier for a product outside the list matches nothing, and no error
263
+ is raised. Products the array doesn't name render after the ones it does, keeping
264
+ their API order among themselves — so a partial list reorders only what it names.
265
+
266
+ **Pagination.** A product named early keeps its position even if the API returns it on
267
+ a later page. Each new page is spliced into the grid at the right slots rather than
268
+ appended, so cards already on screen are left untouched — scroll position and keyboard
269
+ focus survive loading a page. For a small curated set, size `rows × columns` to cover
270
+ it in a single page so no scroll is needed at all.
271
+
272
+ **Search and filters** re-query the API for a narrower result set. The order is
273
+ re-applied to whatever comes back, so named products keep their relative order among
274
+ the results that survive.
275
+
276
+ **Nothing about `productOrder` throws.** Ordering is presentational and the grid
277
+ renders fine without it, so a malformed value costs you the ordering and nothing
278
+ else — never the product list itself.
279
+
280
+ - An entry that isn't a non-empty string is skipped with a console warning, the way
281
+ an unrecognized `filters` value is. The surviving entries keep their relative
282
+ order, so the rest of your ordering still applies around the dropped one.
283
+ - A `productOrder` that isn't an array is ignored entirely with a warning, and the
284
+ list renders in the order the API returns.
285
+
286
+ > The warning goes through the SDK logger, which is silent in production and at the
287
+ > default `debugMode: 'none'`. On a live storefront a dropped entry produces no
288
+ > console output at all, so validate the array you generate rather than relying on
289
+ > the warning to surface a bad one.
290
+
230
291
  ## Infinite Scroll
231
292
 
232
293
  ### How It Works
@@ -274,6 +335,32 @@ Product card images automatically have their white/near-white backgrounds remove
274
335
 
275
336
  **Click on image:** Navigate to the configured `productUrl` (only when `productUrl` is set — otherwise the image is not a link)
276
337
 
338
+ ### Card Identifiers
339
+
340
+ Each `product-list-card-lc` host carries the product's identifiers as data
341
+ attributes, so a card can be traced back to catalogue data while inspecting the
342
+ page:
343
+
344
+ ```html
345
+ <product-list-card-lc data-grouping="GROUPING-33277" data-upc="00832889005513">
346
+ ```
347
+
348
+ - `data-grouping` — the product's `salsifyGrouping` ID. Fixed for the life of the card.
349
+ - `data-upc` — the **selected size's** UPC, the same value `{upc}` resolves to in a
350
+ `productUrl` template. It updates when the shopper changes size.
351
+
352
+ Both use the identifier forms accepted everywhere else in the SDK, so a value read
353
+ off a card can be pasted straight into `productOrder`, a `productUrl` map key, or
354
+ `injectProductElement`.
355
+
356
+ > **These are for inspection, not for scripting.** Cards render inside the SDK's
357
+ > shadow root, which is **always closed in production** — `openShadowDom` is forced
358
+ > to `false` there and only has effect in development. Chrome DevTools displays
359
+ > closed shadow roots, so the attributes are visible when inspecting the page, but
360
+ > `document.querySelector('[data-upc]')` from partner JavaScript will not reach them
361
+ > on a live storefront. To react to card activity from your own code, use the
362
+ > [events](#events) instead.
363
+
277
364
  ## Customization
278
365
 
279
366
  ### 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.31",
7
7
  "homepage": "https://docs.liquidcommerce.co/elements-sdk",
8
8
  "repository": {
9
9
  "type": "git",