@liquidcommerce/elements-sdk 2.7.29 → 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.
- package/dist/index.checkout.esm.js +7488 -7469
- package/dist/index.esm.js +12426 -12258
- package/dist/ssr-stub.checkout.esm.js +0 -1
- package/dist/ssr-stub.esm.js +0 -1
- package/dist/types/constants/core.constant.d.ts +0 -1
- package/dist/types/core/debug-panel/debug-panel.styles.d.ts +1 -1
- package/dist/types/enums/core.enum.d.ts +0 -1
- package/dist/types/interfaces/configs/product.interface.d.ts +2 -0
- package/dist/types/interfaces/injection.interface.d.ts +1 -0
- package/dist/types/modules/product/components/components.d.ts +2 -1
- package/dist/types/modules/product-list/components/product-list-card.component.d.ts +1 -0
- package/dist/types/modules/product-list/product-list-order.utils.d.ts +3 -0
- package/dist/types/modules/product-list/product-list.component.d.ts +5 -0
- package/dist/types/utils/product.d.ts +4 -1
- package/docs/v1/api/client.md +2 -2
- package/docs/v1/api/configuration.md +4 -2
- package/docs/v1/api/injection-methods.md +34 -0
- package/docs/v1/api/typescript-types.md +2 -1
- package/docs/v1/getting-started/concepts.md +4 -4
- package/docs/v1/getting-started/installation.md +3 -3
- package/docs/v1/guides/best-practices.md +4 -4
- package/docs/v1/guides/product-component.md +1 -0
- package/docs/v1/guides/product-list-component.md +87 -0
- package/docs/v1/guides/theming.md +1 -0
- package/docs/v1/reference/analytics.md +1 -2
- package/docs/v1/reference/telemetry.md +1 -1
- package/package.json +9 -9
package/dist/ssr-stub.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const DEBUG_PANEL_STYLES = "\n :host {\n position: fixed;\n z-index: 2147483647;\n display: block;\n }\n\n .panel {\n position: relative;\n background: rgba(15, 23, 42, 0.95);\n backdrop-filter: blur(10px);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;\n font-size: 12px;\n color: #e2e8f0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n user-select: none;\n transition: all 0.2s ease;\n width: 100%;\n height: 100%;\n }\n\n .panel:hover {\n border-color: rgba(148, 163, 184, 0.4);\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);\n }\n\n .header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.2);\n background: rgba(30, 41, 59, 0.5);\n cursor: move;\n }\n\n .icon {\n font-size: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n }\n\n .title-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n margin: 0 12px;\n }\n
|
|
1
|
+
export declare const DEBUG_PANEL_STYLES = "\n :host {\n position: fixed;\n z-index: 2147483647;\n display: block;\n letter-spacing: normal;\n }\n\n .panel {\n position: relative;\n background: rgba(15, 23, 42, 0.95);\n backdrop-filter: blur(10px);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;\n font-size: 12px;\n color: #e2e8f0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n user-select: none;\n transition: all 0.2s ease;\n width: 100%;\n height: 100%;\n }\n\n .panel:hover {\n border-color: rgba(148, 163, 184, 0.4);\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);\n }\n\n .header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.2);\n background: rgba(30, 41, 59, 0.5);\n cursor: move;\n }\n\n .icon {\n font-size: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n }\n\n .title-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n margin: 0 12px;\n }\n\n .title-container > *:not(:first-child) {\n margin-top: 2px;\n }\n\n .title {\n font-weight: 600;\n color: #f1f5f9;\n font-size: 13px;\n }\n\n\n .subtitle {\n font-size: 10px;\n color: #94a3b8;\n }\n\n .minimize {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 18px;\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n transition: all 0.2s ease;\n line-height: 1;\n font-weight: bold;\n }\n\n .minimize:hover {\n background: rgba(148, 163, 184, 0.1);\n color: #cbd5e1;\n }\n\n .toggles {\n display: flex;\n padding: 8px 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.2);\n background: rgba(30, 41, 59, 0.3);\n align-items: center;\n }\n\n .toggles > *:not(:last-child) {\n margin-right: 12px;\n }\n\n .toggle {\n display: flex;\n align-items: center;\n cursor: pointer;\n font-size: 11px;\n color: #cbd5e1;\n transition: color 0.2s ease;\n }\n\n .toggle > *:not(:last-child) {\n margin-right: 6px;\n }\n\n .toggle:hover {\n color: #f1f5f9;\n }\n\n .toggle input[type=\"checkbox\"] {\n width: 12px;\n height: 12px;\n accent-color: #3b82f6;\n cursor: pointer;\n }\n\n .clear-btn {\n background: none;\n border: none;\n color: #ef4444;\n font-size: 14px;\n padding: 0;\n cursor: pointer;\n transition: all 0.2s ease;\n margin-left: auto;\n min-width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .clear-btn:hover {\n transform: scale(1.1);\n color: #dc2626;\n }\n\n .logs {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 8px;\n background: rgba(15, 23, 42, 0.8);\n min-height: 0;\n max-height: 100%;\n }\n\n .log-entry {\n display: flex;\n align-items: flex-start;\n padding: 8px 12px;\n margin-bottom: 2px;\n border-radius: 6px;\n font-size: 11px;\n line-height: 1.4;\n transition: background-color 0.2s ease;\n user-select: text;\n }\n\n .log-entry > *:not(:last-child) {\n margin-right: 8px;\n }\n\n .log-entry:hover {\n background-color: rgba(148, 163, 184, 0.1);\n }\n\n /* Event type background colors */\n .log-entry.event {\n background: rgba(59, 130, 246, 0.1);\n }\n\n .log-entry.action {\n background: rgba(16, 185, 129, 0.1);\n }\n\n .log-entry.gtm {\n background: rgba(245, 158, 11, 0.1);\n }\n\n .log-entry.logger {\n background: rgba(139, 92, 246, 0.1);\n }\n\n\n .log-meta {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n min-width: 50px;\n }\n\n .log-meta > *:not(:first-child) {\n margin-top: 2px;\n }\n\n .log-type {\n font-weight: 600;\n font-size: 9px;\n padding: 2px 0;\n border-radius: 3px;\n white-space: nowrap;\n text-align: center;\n line-height: 1;\n font-weight: bold;\n letter-spacing: 0.5px;\n }\n\n .log-timestamp {\n color: #64748b;\n font-size: 9px;\n white-space: nowrap;\n line-height: 1;\n }\n\n .log-type.event {\n background: rgba(59, 130, 246, 0.2);\n color: #60a5fa;\n }\n\n .log-type.action {\n color: #34d399;\n }\n\n .log-type.gtm {\n color: #fbbf24;\n }\n\n .log-type.logger {\n color: #a78bfa;\n }\n\n .log-message {\n flex: 1;\n word-break: break-word;\n user-select: text;\n }\n\n .log-context {\n color: #94a3b8;\n font-size: 10px;\n margin-top: 2px;\n font-style: italic;\n }\n\n .log-data {\n background: rgba(30, 41, 59, 0.6);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 4px;\n padding: 8px;\n margin-top: 6px;\n font-size: 10px;\n color: #cbd5e1;\n max-height: 120px;\n overflow-y: auto;\n font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;\n user-select: text;\n }\n\n .log-data pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .resize-handle {\n position: absolute;\n bottom: 0;\n right: 0;\n width: 12px;\n height: 12px;\n background: linear-gradient(-45deg, transparent 30%, rgba(148, 163, 184, 0.3) 30%, rgba(148, 163, 184, 0.3) 70%, transparent 70%);\n cursor: nw-resize;\n border-radius: 0 0 12px 0;\n }\n\n .resize-handle:hover {\n background: linear-gradient(-45deg, transparent 30%, rgba(148, 163, 184, 0.7) 30%, rgba(148, 163, 184, 0.7) 70%, transparent 70%);\n transform: scale(1.1);\n }\n\n .resize-handle:active {\n background: linear-gradient(-45deg, transparent 30%, rgba(148, 163, 184, 0.9) 30%, rgba(148, 163, 184, 0.9) 70%, transparent 70%);\n }\n\n /* Scrollbar styling */\n .logs::-webkit-scrollbar {\n width: 6px;\n }\n\n .logs::-webkit-scrollbar-track {\n background: rgba(30, 41, 59, 0.3);\n border-radius: 3px;\n }\n\n .logs::-webkit-scrollbar-thumb {\n background: rgba(148, 163, 184, 0.3);\n border-radius: 3px;\n }\n\n .logs::-webkit-scrollbar-thumb:hover {\n background: rgba(148, 163, 184, 0.5);\n }\n\n .log-data::-webkit-scrollbar {\n width: 4px;\n }\n\n .log-data::-webkit-scrollbar-track {\n background: rgba(30, 41, 59, 0.3);\n border-radius: 2px;\n }\n\n .log-data::-webkit-scrollbar-thumb {\n background: rgba(148, 163, 184, 0.3);\n border-radius: 2px;\n }\n\n .log-data::-webkit-scrollbar-thumb:hover {\n background: rgba(148, 163, 184, 0.5);\n }\n\n /* Floating button styles */\n .floating-btn {\n position: absolute;\n top: 20px;\n left: 20px;\n width: 40px;\n height: 40px;\n background: rgba(15, 23, 42, 0.95);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 50%;\n display: none;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n font-size: 18px;\n user-select: none;\n z-index: 1;\n }\n\n .floating-btn:hover {\n background: rgba(30, 41, 59, 0.95);\n border-color: rgba(148, 163, 184, 0.4);\n }\n\n /* Mobile Responsiveness */\n @media (max-width: 768px) {\n .panel {\n font-size: 11px;\n border-radius: 8px;\n }\n\n .header {\n padding: 8px 12px;\n }\n\n .title {\n font-size: 12px;\n }\n\n .subtitle {\n font-size: 9px;\n }\n\n .toggles {\n padding: 6px 12px;\n flex-wrap: wrap;\n }\n\n .toggles > *:not(:last-child) {\n margin-right: 8px;\n }\n\n .toggle {\n font-size: 10px;\n }\n\n .toggle input[type=\"checkbox\"] {\n width: 10px;\n height: 10px;\n }\n\n .logs {\n padding: 6px;\n }\n\n .log-entry {\n padding: 6px 8px;\n font-size: 10px;\n }\n\n .log-type {\n font-size: 8px;\n }\n\n .log-timestamp {\n font-size: 8px;\n }\n\n .log-data {\n font-size: 9px;\n padding: 6px;\n max-height: 80px;\n }\n\n .floating-btn {\n width: 36px;\n height: 36px;\n font-size: 16px;\n top: 15px;\n left: 15px;\n }\n }\n\n @media (max-width: 480px) {\n .panel {\n font-size: 10px;\n border-radius: 6px;\n }\n\n .header {\n padding: 6px 10px;\n }\n\n .title {\n font-size: 11px;\n }\n\n .subtitle {\n font-size: 8px;\n }\n\n .toggles {\n padding: 4px 10px;\n }\n\n .toggles > *:not(:first-child) {\n margin-right: 6px;\n }\n\n .toggle {\n font-size: 9px;\n }\n\n .logs {\n padding: 4px;\n }\n\n .log-entry {\n padding: 4px 6px;\n font-size: 9px;\n }\n\n .log-entry > *:not(:first-child) {\n margin-top: 4px;\n }\n\n .log-meta {\n min-width: 40px;\n }\n\n .log-type {\n font-size: 7px;\n }\n\n .log-timestamp {\n font-size: 7px;\n }\n\n .log-data {\n font-size: 8px;\n padding: 4px;\n max-height: 60px;\n }\n\n .floating-btn {\n width: 32px;\n height: 32px;\n font-size: 14px;\n top: 10px;\n left: 10px;\n }\n }\n";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FulfillmentType } from '@/enums';
|
|
1
2
|
import type { DeepPartial } from '@/interfaces/config.interface';
|
|
2
3
|
export type FulfillmentDisplayType = 'carousel' | 'popup';
|
|
3
4
|
export type DescriptionPositionType = 'above' | 'below';
|
|
@@ -13,6 +14,7 @@ export interface IProductLayout {
|
|
|
13
14
|
fulfillmentDisplay: FulfillmentDisplayType;
|
|
14
15
|
enableShippingFulfillment: boolean;
|
|
15
16
|
enableOnDemandFulfillment: boolean;
|
|
17
|
+
primaryFulfillmentMethod: FulfillmentType;
|
|
16
18
|
addToCartButtonText: string;
|
|
17
19
|
addToCartButtonShowTotalPrice: boolean;
|
|
18
20
|
buyNowButtonText: string;
|
|
@@ -12,7 +12,7 @@ export declare function renderPopupRetailerCard({ fulfillment, selectedFulfillme
|
|
|
12
12
|
selectedSizeAttributes: IProductSizeAttributes | null;
|
|
13
13
|
interactive?: boolean;
|
|
14
14
|
}): HTMLElement;
|
|
15
|
-
export declare function renderFulfillmentTabsContainer({ shippingFulfillments, onDemandFulfillments, shippingSelected, onDemandSelected, onTabClick, enableShippingFulfillment, enableOnDemandFulfillment, }: {
|
|
15
|
+
export declare function renderFulfillmentTabsContainer({ shippingFulfillments, onDemandFulfillments, shippingSelected, onDemandSelected, onTabClick, enableShippingFulfillment, enableOnDemandFulfillment, primaryFulfillmentMethod, }: {
|
|
16
16
|
shippingFulfillments: IProductFulfillmentStore[];
|
|
17
17
|
onDemandFulfillments: IProductFulfillmentStore[];
|
|
18
18
|
shippingSelected: boolean;
|
|
@@ -20,4 +20,5 @@ export declare function renderFulfillmentTabsContainer({ shippingFulfillments, o
|
|
|
20
20
|
onTabClick: (event: Event) => void;
|
|
21
21
|
enableShippingFulfillment: boolean;
|
|
22
22
|
enableOnDemandFulfillment: boolean;
|
|
23
|
+
primaryFulfillmentMethod: FulfillmentType;
|
|
23
24
|
}): HTMLElement;
|
|
@@ -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;
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { IProductStore } from '@/core/store/interfaces/product.interface';
|
|
2
2
|
import type { IPLProductStore } from '@/core/store/interfaces/product-list.interface';
|
|
3
|
+
import { type FulfillmentType } from '@/enums';
|
|
3
4
|
import type { IProduct, IProductAvailabilityResponse } from '@/interfaces/api/product.interface';
|
|
4
5
|
export interface IPrepareApiProductForStoreParams {
|
|
5
6
|
context: 'product-list' | 'product-page';
|
|
6
7
|
product: IProduct;
|
|
7
8
|
retailers: IProductAvailabilityResponse['retailers'];
|
|
8
9
|
enableShippingFulfillment: boolean;
|
|
10
|
+
enableOnDemandFulfillment?: boolean;
|
|
11
|
+
primaryFulfillmentMethod?: FulfillmentType;
|
|
9
12
|
}
|
|
10
13
|
export declare function buildSizeLabel(size: {
|
|
11
14
|
size: string;
|
|
12
15
|
pack: boolean;
|
|
13
16
|
packDesc: string;
|
|
14
17
|
}): string;
|
|
15
|
-
export declare function prepareApiProductForStore({ context, product, retailers, enableShippingFulfillment, }: IPrepareApiProductForStoreParams): IProductStore | IPLProductStore;
|
|
18
|
+
export declare function prepareApiProductForStore({ context, product, retailers, enableShippingFulfillment, enableOnDemandFulfillment, primaryFulfillmentMethod, }: IPrepareApiProductForStoreParams): IProductStore | IPLProductStore;
|
package/docs/v1/api/client.md
CHANGED
|
@@ -103,7 +103,7 @@ Complete configuration interface for the full SDK.
|
|
|
103
103
|
```typescript
|
|
104
104
|
interface ILiquidCommerceElementsConfig {
|
|
105
105
|
// Optional
|
|
106
|
-
env?: ElementsEnv; // defaults to 'production' ('
|
|
106
|
+
env?: ElementsEnv; // defaults to 'production' ('staging' | 'production')
|
|
107
107
|
debugMode?: DebugMode; // 'none' | 'console' | 'panel'
|
|
108
108
|
customTheme?: IClientCustomThemeConfig;
|
|
109
109
|
promoTicker?: IPromoTicker[];
|
|
@@ -116,7 +116,7 @@ interface ILiquidCommerceElementsConfig {
|
|
|
116
116
|
#### Environment
|
|
117
117
|
|
|
118
118
|
```typescript
|
|
119
|
-
type ElementsEnv = '
|
|
119
|
+
type ElementsEnv = 'staging' | 'production';
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
Determines which API environment to use.
|
|
@@ -22,7 +22,7 @@ interface ILiquidCommerceElementsConfig {
|
|
|
22
22
|
|
|
23
23
|
| Property | Type | Required | Description |
|
|
24
24
|
|----------|------|----------|-------------|
|
|
25
|
-
| `env` | `'
|
|
25
|
+
| `env` | `'staging' \| 'production'` | No | API environment |
|
|
26
26
|
| `promoTicker` | `IPromoTicker[]` | No | Promotional ticker configurations |
|
|
27
27
|
| `customTheme` | `IClientCustomThemeConfig` | No | Theme overrides for all components |
|
|
28
28
|
| `debugMode` | `'none' \| 'console' \| 'panel'` | No | Debug output mode |
|
|
@@ -30,7 +30,7 @@ interface ILiquidCommerceElementsConfig {
|
|
|
30
30
|
| `proxy` | `IElementsProxyConfig` | No | Proxy configuration for API requests |
|
|
31
31
|
| `development` | `ILiquidCommerceElementsDevelopmentConfig` | No | Development/testing options |
|
|
32
32
|
|
|
33
|
-
> **Note:** `debugMode` (`'console'` or `'panel'`) is **ignored in production** -- it is forced off when `env` is `'production'`. It only takes effect
|
|
33
|
+
> **Note:** `debugMode` (`'console'` or `'panel'`) is **ignored in production** -- it is forced off when `env` is `'production'`. It only takes effect outside production (`'staging'`).
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
@@ -201,6 +201,7 @@ interface IProductLayout {
|
|
|
201
201
|
fulfillmentDisplay: 'carousel' | 'popup';
|
|
202
202
|
enableShippingFulfillment: boolean;
|
|
203
203
|
enableOnDemandFulfillment: boolean;
|
|
204
|
+
primaryFulfillmentMethod: 'shipping' | 'onDemand';
|
|
204
205
|
addToCartButtonText: string;
|
|
205
206
|
addToCartButtonShowTotalPrice: boolean;
|
|
206
207
|
buyNowButtonText: string;
|
|
@@ -223,6 +224,7 @@ interface IProductLayout {
|
|
|
223
224
|
| `fulfillmentDisplay` | `'carousel' \| 'popup'` | How to display fulfillment/retailer options |
|
|
224
225
|
| `enableShippingFulfillment` | `boolean` | Enable shipping fulfillment option |
|
|
225
226
|
| `enableOnDemandFulfillment` | `boolean` | Enable on-demand delivery option |
|
|
227
|
+
| `primaryFulfillmentMethod` | `'shipping' \| 'onDemand'` | Fulfillment method shown first and selected by default; falls back to the other enabled method when it has no availability |
|
|
226
228
|
| `addToCartButtonText` | `string` | Custom text for add-to-cart button |
|
|
227
229
|
| `addToCartButtonShowTotalPrice` | `boolean` | Show total price on add-to-cart button |
|
|
228
230
|
| `buyNowButtonText` | `string` | Custom text for buy-now button |
|
|
@@ -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
|
|
|
@@ -310,7 +311,7 @@ See [Actions API](./actions/) for detailed method signatures.
|
|
|
310
311
|
### ElementsEnv
|
|
311
312
|
|
|
312
313
|
```typescript
|
|
313
|
-
type ElementsEnv = '
|
|
314
|
+
type ElementsEnv = 'staging' | 'production';
|
|
314
315
|
```
|
|
315
316
|
|
|
316
317
|
### DebugMode
|
|
@@ -379,7 +379,7 @@ Enable debug mode for detailed logging:
|
|
|
379
379
|
|
|
380
380
|
```javascript
|
|
381
381
|
const client = await Elements('YOUR_API_KEY', {
|
|
382
|
-
env: '
|
|
382
|
+
env: 'staging',
|
|
383
383
|
debugMode: 'console' // or 'panel'
|
|
384
384
|
});
|
|
385
385
|
```
|
|
@@ -409,9 +409,9 @@ Your API key is used for authentication but has limited privileges:
|
|
|
409
409
|
Use different API keys per environment:
|
|
410
410
|
|
|
411
411
|
```javascript
|
|
412
|
-
//
|
|
413
|
-
const client = await Elements('
|
|
414
|
-
env: '
|
|
412
|
+
// Staging
|
|
413
|
+
const client = await Elements('staging_key_abc123', {
|
|
414
|
+
env: 'staging'
|
|
415
415
|
});
|
|
416
416
|
|
|
417
417
|
// Production
|
|
@@ -46,7 +46,7 @@ The path segment selects which bundle is served:
|
|
|
46
46
|
|-----------|----------|-------------|
|
|
47
47
|
| `data-liquid-commerce-elements` | Yes | Identifies this as the Elements SDK script |
|
|
48
48
|
| `data-token` | Yes | Your LiquidCommerce API key |
|
|
49
|
-
| `data-env` | No | Environment: `
|
|
49
|
+
| `data-env` | No | Environment: `staging` or `production`. Defaults to `production` if omitted. |
|
|
50
50
|
| `defer` | Recommended | Allows non-blocking script loading |
|
|
51
51
|
|
|
52
52
|
### Where to Place the Script
|
|
@@ -264,7 +264,7 @@ The SDK accepts a configuration object during initialization:
|
|
|
264
264
|
|
|
265
265
|
```javascript
|
|
266
266
|
const client = await Elements('YOUR_API_KEY', {
|
|
267
|
-
env: 'production', // Optional (defaults to 'production'): '
|
|
267
|
+
env: 'production', // Optional (defaults to 'production'): 'staging' | 'production'
|
|
268
268
|
debugMode: 'console', // Optional: 'none' | 'console' | 'panel'
|
|
269
269
|
customTheme: { /* ... */ }, // Optional: Theme customization
|
|
270
270
|
promoTicker: [ /* ... */ ], // Optional: Promo ticker configuration
|
|
@@ -274,7 +274,7 @@ const client = await Elements('YOUR_API_KEY', {
|
|
|
274
274
|
});
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
-
> **Note:** `debugMode` is ignored when `env` is `'production'`. Debug logging and the debug panel are only activated
|
|
277
|
+
> **Note:** `debugMode` is ignored when `env` is `'production'`. Debug logging and the debug panel are only activated outside production (`staging`).
|
|
278
278
|
|
|
279
279
|
See [Configuration Reference](../api/configuration.md) for complete configuration options.
|
|
280
280
|
|
|
@@ -269,20 +269,20 @@ Prevents ad blockers and protects API keys.
|
|
|
269
269
|
```javascript
|
|
270
270
|
const apiKey = process.env.NODE_ENV === 'production'
|
|
271
271
|
? 'prod_key_xyz'
|
|
272
|
-
: '
|
|
272
|
+
: 'staging_key_abc';
|
|
273
273
|
|
|
274
274
|
const client = await Elements(apiKey, {
|
|
275
|
-
env: process.env.NODE_ENV === 'production' ? 'production' : '
|
|
275
|
+
env: process.env.NODE_ENV === 'production' ? 'production' : 'staging'
|
|
276
276
|
});
|
|
277
277
|
```
|
|
278
278
|
|
|
279
279
|
## Testing
|
|
280
280
|
|
|
281
|
-
### Use
|
|
281
|
+
### Use Staging With Debug Options
|
|
282
282
|
|
|
283
283
|
```javascript
|
|
284
284
|
const client = await Elements('YOUR_API_KEY', {
|
|
285
|
-
env: '
|
|
285
|
+
env: 'staging',
|
|
286
286
|
debugMode: 'console', // or 'panel'
|
|
287
287
|
development: {
|
|
288
288
|
openShadowDom: true // Makes debugging easier
|
|
@@ -375,6 +375,7 @@ const client = await Elements('YOUR_API_KEY', {
|
|
|
375
375
|
fulfillmentDisplay: 'carousel', // or 'popup'
|
|
376
376
|
enableShippingFulfillment: true,
|
|
377
377
|
enableOnDemandFulfillment: true,
|
|
378
|
+
primaryFulfillmentMethod: 'shipping', // or 'onDemand'
|
|
378
379
|
addToCartButtonText: 'Add to Cart',
|
|
379
380
|
addToCartButtonShowTotalPrice: true,
|
|
380
381
|
buyNowButtonText: 'Buy Now',
|
|
@@ -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
|
|
@@ -118,6 +118,7 @@ customTheme: {
|
|
|
118
118
|
fulfillmentDisplay: 'carousel',
|
|
119
119
|
enableShippingFulfillment: true,
|
|
120
120
|
enableOnDemandFulfillment: true,
|
|
121
|
+
primaryFulfillmentMethod: 'shipping',
|
|
121
122
|
addToCartButtonText: 'Add to Cart',
|
|
122
123
|
addToCartButtonShowTotalPrice: true,
|
|
123
124
|
buyNowButtonText: 'Buy Now',
|
|
@@ -12,13 +12,12 @@ It coexists with a host site's own GTM — see [Coexisting With Your Own GTM](#c
|
|
|
12
12
|
|
|
13
13
|
## Supported Environments
|
|
14
14
|
|
|
15
|
-
GTM initialization and every event push are enabled in production and staging. When the SDK is running in
|
|
15
|
+
GTM initialization and every event push are enabled in production and staging. When the SDK is running in Builder mode, no container is loaded and no events are sent.
|
|
16
16
|
|
|
17
17
|
Events are suppressed when any of the following is true:
|
|
18
18
|
|
|
19
19
|
- `window` is undefined (server-side rendering).
|
|
20
20
|
- The SDK is running in Builder mode.
|
|
21
|
-
- The environment is `development`.
|
|
22
21
|
|
|
23
22
|
The environment defaults to `production` when none is provided, so a standard production embed is analytics-enabled out of the box; a staging embed is also analytics-enabled when server GTM config is enabled for the partner.
|
|
24
23
|
|
|
@@ -13,7 +13,7 @@ Telemetry is enabled **only** when both conditions are true:
|
|
|
13
13
|
|
|
14
14
|
In every other case it is **disabled** and no events are queued or sent:
|
|
15
15
|
|
|
16
|
-
- Non-production environments (e.
|
|
16
|
+
- Non-production environments (i.e. staging).
|
|
17
17
|
- A custom API URL / proxy is configured via `development.customApiUrl`.
|
|
18
18
|
- Internal telemetry errors are only logged to the console in non-production or when debugging is enabled — never sent.
|
|
19
19
|
|
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.
|
|
6
|
+
"version": "2.7.31",
|
|
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.
|
|
17
|
+
"packageManager": "pnpm@11.21.0",
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|
|
20
20
|
"types": "./dist/types/index.d.ts",
|
|
@@ -107,9 +107,9 @@
|
|
|
107
107
|
"theming"
|
|
108
108
|
],
|
|
109
109
|
"devDependencies": {
|
|
110
|
-
"@biomejs/biome": "^2.5.
|
|
111
|
-
"@commitlint/cli": "^21.2.
|
|
112
|
-
"@commitlint/config-conventional": "^21.2.
|
|
110
|
+
"@biomejs/biome": "^2.5.8",
|
|
111
|
+
"@commitlint/cli": "^21.2.2",
|
|
112
|
+
"@commitlint/config-conventional": "^21.2.2",
|
|
113
113
|
"@rollup/plugin-alias": "^6.0.0",
|
|
114
114
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
115
115
|
"@rollup/plugin-json": "^6.1.0",
|
|
@@ -123,14 +123,14 @@
|
|
|
123
123
|
"@semantic-release/npm": "^13.1.5",
|
|
124
124
|
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
125
125
|
"@types/core-js": "^2.5.8",
|
|
126
|
-
"@types/node": "^26.
|
|
127
|
-
"conventional-changelog": "8.1.
|
|
126
|
+
"@types/node": "^26.2.0",
|
|
127
|
+
"conventional-changelog": "8.1.3",
|
|
128
128
|
"husky": "^9.1.7",
|
|
129
129
|
"process": "^0.11.10",
|
|
130
|
-
"rollup": "^4.62.
|
|
130
|
+
"rollup": "^4.62.4",
|
|
131
131
|
"rollup-obfuscator": "^4.1.1",
|
|
132
132
|
"rollup-plugin-typescript2": "^0.37.0",
|
|
133
|
-
"semantic-release": "^25.0.
|
|
133
|
+
"semantic-release": "^25.0.9",
|
|
134
134
|
"ts-node": "^10.9.2",
|
|
135
135
|
"typescript": "6.0.3"
|
|
136
136
|
},
|