@liquidcommerce/elements-sdk 2.6.0-beta.84 → 2.6.0-beta.86

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.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/dist/index.checkout.esm.js +7211 -7124
  3. package/dist/index.esm.js +11411 -11328
  4. package/dist/types/core/pubsub/interfaces/address.interface.d.ts +3 -0
  5. package/dist/types/core/pubsub/interfaces/core.interface.d.ts +2 -2
  6. package/docs/v1/api/actions/address-actions.md +20 -15
  7. package/docs/v1/api/actions/cart-actions.md +22 -23
  8. package/docs/v1/api/actions/checkout-actions.md +72 -25
  9. package/docs/v1/api/actions/product-actions.md +61 -15
  10. package/docs/v1/api/client.md +38 -14
  11. package/docs/v1/api/configuration.md +5 -1
  12. package/docs/v1/api/injection-methods.md +8 -4
  13. package/docs/v1/api/typescript-types.md +6 -0
  14. package/docs/v1/examples/advanced-patterns.md +7 -6
  15. package/docs/v1/examples/checkout-flow.md +1 -2
  16. package/docs/v1/getting-started/concepts.md +20 -25
  17. package/docs/v1/getting-started/installation.md +4 -4
  18. package/docs/v1/guides/address-component.md +12 -8
  19. package/docs/v1/guides/best-practices.md +5 -5
  20. package/docs/v1/guides/cart-component.md +27 -39
  21. package/docs/v1/guides/checkout-component.md +27 -29
  22. package/docs/v1/guides/events.md +4 -4
  23. package/docs/v1/guides/product-component.md +56 -19
  24. package/docs/v1/guides/product-list-component.md +8 -9
  25. package/docs/v1/guides/theming.md +6 -9
  26. package/docs/v1/integration/proxy-setup.md +22 -5
  27. package/docs/v1/reference/browser-support.md +2 -1
  28. package/docs/v1/reference/error-handling.md +12 -7
  29. package/docs/v1/reference/performance.md +1 -3
  30. package/docs/v1/reference/troubleshooting.md +3 -3
  31. package/package.json +8 -17
@@ -5,3 +5,6 @@ export interface IAddressActionEventData {
5
5
  address: IAddressAddress;
6
6
  coordinates: IAddressCoordinates;
7
7
  }
8
+ export interface IAddressFailedEventData extends IAddressActionEventData {
9
+ error: string;
10
+ }
@@ -1,4 +1,4 @@
1
- import type { IAddressActionEventData } from '@/core/pubsub/interfaces/address.interface';
1
+ import type { IAddressActionEventData, IAddressFailedEventData } from '@/core/pubsub/interfaces/address.interface';
2
2
  import type { ICartFailedEventData, ICartItemAddedEventData, ICartItemEngravingUpdatedEventData, ICartItemQuantityChangedEventData, ICartItemRemovedEventData, ICartLoadedEventData, ICartProductAddEventData, ICartProductAddFailedEventData, ICartPromoCodeEventData, ICartPromoCodeFailedEventData, ICartUpdatedEventData } from '@/core/pubsub/interfaces/cart.interface';
3
3
  import type { ICheckoutBillingInformationUpdatedEventData, ICheckoutCustomerInformationUpdatedEventData, ICheckoutFailedEventData, ICheckoutGiftCardEventData, ICheckoutGiftCardFailedEventData, ICheckoutGiftInformationUpdatedEventData, ICheckoutItemEngravingUpdatedEventData, ICheckoutItemQuantityChangedEventData, ICheckoutItemRemovedEventData, ICheckoutLoadedEventData, ICheckoutMarketingPreferencesToggleEventData, ICheckoutProductAddEventData, ICheckoutProductAddFailedEventData, ICheckoutPromoCodeEventData, ICheckoutPromoCodeFailedEventData, ICheckoutSubmitCompletedEventData, ICheckoutSubmitFailedEventData, ICheckoutSubmitStartedEventData, ICheckoutTipUpdatedEventData, ICheckoutToggleEventData } from '@/core/pubsub/interfaces/checkout.interface';
4
4
  import type { IProductAddToCartEventData, IProductFulfillmentChangedEventData, IProductFulfillmentTypeChangedEventData, IProductLoadedEventData, IProductQuantityChangedEventData, IProductSizeChangedEventData } from '@/core/pubsub/interfaces/product.interface';
@@ -29,7 +29,7 @@ export interface IElementsActionsEventsMap {
29
29
  [ELEMENTS_ACTIONS_EVENT.CLIENT_READY]: IElementsClientIsReadyEventData;
30
30
  [ELEMENTS_ACTIONS_EVENT.ADDRESS_UPDATED]: IAddressActionEventData;
31
31
  [ELEMENTS_ACTIONS_EVENT.ADDRESS_CLEARED]: boolean;
32
- [ELEMENTS_ACTIONS_EVENT.ADDRESS_FAILED]: IAddressActionEventData;
32
+ [ELEMENTS_ACTIONS_EVENT.ADDRESS_FAILED]: IAddressFailedEventData;
33
33
  [ELEMENTS_ACTIONS_EVENT.PRODUCT_LOADED]: IProductLoadedEventData;
34
34
  [ELEMENTS_ACTIONS_EVENT.PRODUCT_QUANTITY_DECREASE]: IProductQuantityChangedEventData;
35
35
  [ELEMENTS_ACTIONS_EVENT.PRODUCT_QUANTITY_INCREASE]: IProductQuantityChangedEventData;
@@ -49,11 +49,12 @@ setAddressManually(
49
49
 
50
50
  ```typescript
51
51
  interface IAddressAddress {
52
- one: string; // Street address
53
- two?: string; // Apt, suite, etc. (optional)
54
- city: string; // City name
55
- state: string; // Two-letter state code
56
- zip: string; // ZIP/postal code
52
+ one: string; // Street address
53
+ two: string; // Apt, suite, etc.
54
+ city: string; // City name
55
+ state: string; // Two-letter state code
56
+ zip: string; // ZIP/postal code
57
+ country: string; // Country
57
58
  }
58
59
 
59
60
  interface IAddressCoordinates {
@@ -71,7 +72,8 @@ await window.LiquidCommerce.elements.actions.address.setAddressManually(
71
72
  two: 'Apt 4',
72
73
  city: 'New York',
73
74
  state: 'NY',
74
- zip: '10001'
75
+ zip: '10001',
76
+ country: 'US'
75
77
  },
76
78
  {
77
79
  latitude: 40.7128,
@@ -83,17 +85,19 @@ await window.LiquidCommerce.elements.actions.address.setAddressManually(
83
85
  ### Validation
84
86
 
85
87
  The SDK validates:
86
- - All required fields are present
87
- - State is 2-letter code
88
+ - All required fields are present (street, city, state, zip)
88
89
  - Latitude is between -90 and 90
89
90
  - Longitude is between -180 and 180
90
91
 
92
+ > Note: the `state` field is expected to be a 2-letter code by convention, but the SDK does **not** enforce its format.
93
+
91
94
  ### Errors
92
95
 
93
96
  **Throws `SDKError` if:**
94
- - Required fields are missing
95
- - Coordinates are out of range
96
- - Address format is invalid
97
+ - `address` or `coordinates` is missing
98
+ - A required field is missing (street, city, state, zip)
99
+ - Latitude or longitude is non-numeric
100
+ - Coordinates are out of range (latitude outside -90..90, longitude outside -180..180)
97
101
 
98
102
  ---
99
103
 
@@ -135,13 +139,14 @@ getDetails(): IAddressData | null
135
139
 
136
140
  ```typescript
137
141
  interface IAddressData {
138
- id: string; // Google Places ID or generated ID
142
+ id: string; // Google Places ID (always populated when an object is returned)
139
143
  address: {
140
144
  one: string;
141
- two?: string;
145
+ two: string;
142
146
  city: string;
143
147
  state: string;
144
148
  zip: string;
149
+ country: string;
145
150
  };
146
151
  coordinates: {
147
152
  latitude: number;
@@ -151,7 +156,7 @@ interface IAddressData {
151
156
  }
152
157
  ```
153
158
 
154
- Returns `null` if no address is set.
159
+ Returns `null` if no address is set. Manually-entered addresses (set via `setAddressManually`) are stored without a Google Places ID, so `getDetails()` also returns `null` for them.
155
160
 
156
161
  ### Example
157
162
 
@@ -194,7 +199,7 @@ Address actions trigger events:
194
199
  ```javascript
195
200
  // Address updated
196
201
  window.addEventListener('lce:actions.address_updated', (event) => {
197
- const { address, coordinates, formattedAddress } = event.detail.data;
202
+ const { googlePlacesId, address, coordinates, formattedAddress } = event.detail.data;
198
203
  console.log('Address set:', formattedAddress);
199
204
  });
200
205
 
@@ -78,7 +78,7 @@ addProduct(params: IAddProductParams[], openCart?: boolean): Promise<void>
78
78
 
79
79
  ```typescript
80
80
  interface IAddProductParams {
81
- identifier: string; // Product UPC, SKU, or ID
81
+ identifier: string; // Product UPC, size ID, or salsify grouping ID
82
82
  fulfillmentType: FulfillmentType; // 'shipping' or 'onDemand'
83
83
  quantity: number; // Number of items
84
84
  engravingLines?: string[]; // Optional engraving (see below)
@@ -145,8 +145,8 @@ If no address is set, the SDK automatically:
145
145
  - Identifier is invalid
146
146
  - Fulfillment type is not `'shipping'` or `'onDemand'`
147
147
  - Quantity is less than 1
148
- - Product is not available
149
- - Product is a presale item
148
+
149
+ Unavailable products and presale items are **not** thrown — they are silently skipped (a `cart.error` is set on the store). If no products could be added, a `cart_product_add_failed` event is emitted with `{ cartId, identifiers, error }` and the returned promise still resolves (it does not reject).
150
150
 
151
151
  ---
152
152
 
@@ -242,17 +242,17 @@ getDetails(): IBaseCartEventData
242
242
  ```typescript
243
243
  interface IBaseCartEventData {
244
244
  cartId: string;
245
- items: ICartItem[];
245
+ promoCodeDiscount: number | null; // in cents
246
246
  subtotal: number; // in cents
247
- total: number; // in cents
248
- itemsCount: number;
249
- itemsQuantity: number;
250
- promoCode?: {
251
- code: string;
252
- discount: number; // in cents
253
- };
254
- retailers: IRetailer[];
255
- // ... additional fields
247
+ itemCount: number;
248
+ items: Record<string, ICartItem>; // keyed by item ID
249
+ retailers: Record<string, ICartRetailer>; // keyed by retailer ID
250
+ location: {
251
+ placesId: string;
252
+ formattedAddress: string;
253
+ address: IAddressAddress;
254
+ coordinates: IAddressCoordinates;
255
+ } | null;
256
256
  }
257
257
  ```
258
258
 
@@ -262,12 +262,11 @@ interface IBaseCartEventData {
262
262
  const cart = window.LiquidCommerce.elements.actions.cart.getDetails();
263
263
 
264
264
  console.log(`Cart ID: ${cart.cartId}`);
265
- console.log(`Items: ${cart.itemsCount}`);
265
+ console.log(`Items: ${cart.itemCount}`);
266
266
  console.log(`Subtotal: $${cart.subtotal / 100}`);
267
- console.log(`Total: $${cart.total / 100}`);
268
267
 
269
- if (cart.promoCode) {
270
- console.log(`Discount: $${cart.promoCode.discount / 100}`);
268
+ if (cart.promoCodeDiscount) {
269
+ console.log(`Discount: $${cart.promoCodeDiscount / 100}`);
271
270
  }
272
271
  ```
273
272
 
@@ -279,8 +278,8 @@ if (cart.promoCode) {
279
278
  function updateCartSummary() {
280
279
  const cart = window.LiquidCommerce.elements.actions.cart.getDetails();
281
280
 
282
- document.getElementById('cart-count').textContent = cart.itemsCount;
283
- document.getElementById('cart-total').textContent = `$${cart.total / 100}`;
281
+ document.getElementById('cart-count').textContent = cart.itemCount;
282
+ document.getElementById('cart-total').textContent = `$${cart.subtotal / 100}`;
284
283
  }
285
284
 
286
285
  // Update on cart changes
@@ -293,7 +292,7 @@ window.addEventListener('lce:actions.cart_updated', updateCartSummary);
293
292
  document.getElementById('checkout-btn').addEventListener('click', () => {
294
293
  const cart = window.LiquidCommerce.elements.actions.cart.getDetails();
295
294
 
296
- if (cart.itemsCount === 0) {
295
+ if (cart.itemCount === 0) {
297
296
  alert('Your cart is empty');
298
297
  return;
299
298
  }
@@ -308,10 +307,10 @@ document.getElementById('checkout-btn').addEventListener('click', () => {
308
307
  const cart = window.LiquidCommerce.elements.actions.cart.getDetails();
309
308
 
310
309
  gtag('event', 'view_cart', {
311
- value: cart.total / 100,
310
+ value: cart.subtotal / 100,
312
311
  currency: 'USD',
313
- items: cart.items.map(item => ({
314
- item_id: item.identifier,
312
+ items: Object.values(cart.items).map(item => ({
313
+ item_id: item.partNumber,
315
314
  quantity: item.quantity
316
315
  }))
317
316
  });
@@ -60,7 +60,7 @@ window.LiquidCommerce.elements.actions.checkout.exitCheckout();
60
60
  addProduct(params: IAddProductParams[], openCheckout?: boolean): Promise<void>
61
61
  ```
62
62
 
63
- Add products directly to checkout, bypassing the cart.
63
+ Add products and open checkout in one call. Products are added to the cart (via the cart action, or directly when none is wired up), then checkout is prepared and opened. For a checkout without a cart session, use `addAnonymousProduct`.
64
64
 
65
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
66
 
@@ -94,15 +94,66 @@ addAnonymousProduct(
94
94
 
95
95
  Add a product to checkout without requiring a cart session. Used for direct-to-checkout flows.
96
96
 
97
+ **Parameters** (`IAnonymousCheckoutAddProductParams`):
98
+
99
+ | Field | Type | Required | Description |
100
+ |-------|------|----------|-------------|
101
+ | `location` | `ILocation` | Yes | `{ address: IAddressAddress; coordinates: IAddressCoordinates }` |
102
+ | `items` | `IAnonymousCheckoutAddProductItem[]` | No | Products to add: `{ identifier, fulfillmentType, quantity }`. Provide `items` **or** `cartItems`. |
103
+ | `cartItems` | `ICartUpdateItem[]` | No | Pre-resolved cart line items: `{ id?, partNumber, quantity, fulfillmentId, engravingLines? }` (alternative to `items`). |
104
+ | `promoCode` | `string` | No | Promo code applied while creating the anonymous checkout. |
105
+
97
106
  ```javascript
98
107
  const result = await window.LiquidCommerce.elements.actions.checkout
99
108
  .addAnonymousProduct({
100
- identifier: '00619947000020',
101
- fulfillmentType: 'shipping',
102
- quantity: 1
109
+ items: [
110
+ { identifier: '00619947000020', fulfillmentType: 'shipping', quantity: 1 }
111
+ ],
112
+ location: {
113
+ address: { one: '123 Main St', two: '', city: 'New York', state: 'NY', zip: '10001', country: 'US' },
114
+ coordinates: { latitude: 40.7128, longitude: -74.006 }
115
+ },
116
+ promoCode: 'WELCOME10' // optional
103
117
  });
104
118
  ```
105
119
 
120
+ **Returns** (`IAnonymousCheckoutAddProductResponse`):
121
+
122
+ ```typescript
123
+ interface IAnonymousCheckoutAddProductResponse {
124
+ checkout: ICheckoutPrepare | null; // the prepared checkout (see below), or null on failure
125
+ unavailableItems: IAnonymousCheckoutUnavailableItem[];
126
+ }
127
+
128
+ interface IAnonymousCheckoutUnavailableItem {
129
+ identifier: string;
130
+ productName: string | null;
131
+ fulfillmentType: FulfillmentType; // 'shipping' | 'onDemand'
132
+ reason: 'product_not_available' | 'fulfillment_not_available';
133
+ }
134
+
135
+ interface ICheckoutPrepare {
136
+ token: string;
137
+ cartId: string;
138
+ customer: ICheckoutCustomer;
139
+ isGift: boolean;
140
+ billingSameAsShipping: boolean;
141
+ payment?: string;
142
+ giftOptions: ICheckoutGiftRecipient;
143
+ marketingPreferences: ICheckoutMarketingPreferences;
144
+ shippingAddress: ICheckoutShippingAddress;
145
+ billingAddress: ICheckoutBilling;
146
+ promoCode: ICheckoutPromoCode | null;
147
+ amounts: ICheckoutAmounts;
148
+ giftCards: ICheckoutGiftCard[];
149
+ presale: ICheckoutPresale | null;
150
+ itemsQuantity: number;
151
+ items: Record<string /* cart item ID */, ICheckoutItem>;
152
+ retailers: Record<string /* retailer ID */, ICheckoutRetailer>;
153
+ events: ICheckoutEvent[];
154
+ }
155
+ ```
156
+
106
157
  ## Promo & Gift Card Actions
107
158
 
108
159
  ### actions.checkout.applyPromoCode()
@@ -219,7 +270,7 @@ Pre-fill customer information.
219
270
  **Available fields:**
220
271
  - `firstName`, `lastName`
221
272
  - `email`, `phone`
222
- - `birthdate`, `addressTwo`
273
+ - `birthDate`, `addressTwo`
223
274
  - `company`
224
275
 
225
276
  ```javascript
@@ -228,7 +279,7 @@ window.LiquidCommerce.elements.actions.checkout.updateCustomerInfo({
228
279
  lastName: 'Doe',
229
280
  email: 'john@example.com',
230
281
  phone: '+15551234567',
231
- birthdate: '1990-01-01',
282
+ birthDate: '1990-01-01',
232
283
  addressTwo: 'Apt 4B',
233
284
  company: 'Acme Corp'
234
285
  });
@@ -341,7 +392,7 @@ const checkout = window.LiquidCommerce.elements.actions.checkout.getDetails();
341
392
 
342
393
  console.log(`Cart ID: ${checkout.cartId}`);
343
394
  console.log(`Total: $${checkout.amounts.total / 100}`);
344
- console.log(`Items: ${checkout.items.length}`);
395
+ console.log(`Items: ${checkout.itemCount}`);
345
396
  console.log(`Is Gift: ${checkout.isGift}`);
346
397
  ```
347
398
 
@@ -349,23 +400,19 @@ console.log(`Is Gift: ${checkout.isGift}`);
349
400
 
350
401
  ```typescript
351
402
  interface ICheckoutDetailsEventData {
403
+ token: string;
352
404
  cartId: string;
353
- items: ICheckoutItem[];
354
- amounts: {
355
- subtotal: number;
356
- tax: number;
357
- shipping: number;
358
- tip: number;
359
- total: number;
360
- };
361
- customer: ICustomerInfo;
362
- shippingAddress: IAddress;
363
- billingAddress?: IAddress;
364
405
  isGift: boolean;
365
- giftRecipient?: IGiftRecipient;
366
- promoCode?: IPromoCode;
367
- giftCards: IGiftCard[];
368
- // ... additional fields
406
+ billingSameAsShipping: boolean;
407
+ marketingPreferences: {
408
+ canEmail: boolean;
409
+ canSms: boolean;
410
+ };
411
+ hasPromoCode: boolean;
412
+ hasGiftCards: boolean;
413
+ amounts: ICheckoutAmounts;
414
+ itemCount: number;
415
+ items: Record<string, ICheckoutItem>;
369
416
  }
370
417
  ```
371
418
 
@@ -419,11 +466,11 @@ window.addEventListener('lce:actions.checkout_submit_started', () => {
419
466
  });
420
467
 
421
468
  window.addEventListener('lce:actions.checkout_submit_completed', (event) => {
422
- const { orderId, total } = event.detail.data;
469
+ const { orderNumber, orderTotal } = event.detail.data;
423
470
 
424
471
  gtag('event', 'purchase', {
425
- transaction_id: orderId,
426
- value: total / 100,
472
+ transaction_id: orderNumber,
473
+ value: orderTotal / 100,
427
474
  currency: 'USD'
428
475
  });
429
476
  });
@@ -21,19 +21,22 @@ getDetails(identifier: string): IBaseProductEventData
21
21
  ### Returns
22
22
 
23
23
  ```typescript
24
+ // Inherits all IProduct fields except `sizes` (see below for the overridden `sizes` shape)
24
25
  interface IBaseProductEventData {
25
26
  identifier: string;
26
27
  name: string;
27
28
  description: string;
28
- price: number; // in cents
29
- selectedSize: IProductSize;
29
+ priceInfo: IProductPriceInfo | null; // { currency, minimum, average, maximum }
30
+ selectedSizeId: string | null;
30
31
  selectedFulfillmentType: FulfillmentType;
31
- selectedRetailer: IRetailer;
32
- quantity: number;
32
+ selectedFulfillmentId: string | null;
33
+ productHasAvailability: boolean;
34
+ fulfillmentHasAvailability: boolean;
35
+ sizes: Record<string, IProductSizeEventData>;
33
36
  images: string[];
34
37
  brand: string;
35
38
  category: string;
36
- // ... additional fields
39
+ // ... additional IProduct fields
37
40
  }
38
41
  ```
39
42
 
@@ -43,17 +46,16 @@ interface IBaseProductEventData {
43
46
  const productData = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');
44
47
 
45
48
  console.log(productData.name); // "Premium Whiskey"
46
- console.log(productData.price / 100); // 49.99
47
- console.log(productData.selectedSize.name); // "750ml"
48
- console.log(productData.quantity); // 1
49
+ console.log(productData.priceInfo?.minimum / 100); // 49.99
50
+ console.log(productData.sizes[productData.selectedSizeId]?.size); // "750ml"
51
+ console.log(productData.selectedSizeId); // "size_123"
49
52
  ```
50
53
 
51
54
  ### Errors
52
55
 
53
56
  **Throws `SDKError` if:**
54
57
  - Identifier is empty or invalid
55
- - Product has not been loaded yet
56
- - Product does not exist
58
+ - Product has not been loaded yet (a non-existent product surfaces as this same error)
57
59
 
58
60
  ```javascript
59
61
  try {
@@ -71,7 +73,7 @@ try {
71
73
  const product = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');
72
74
 
73
75
  document.getElementById('product-name').textContent = product.name;
74
- document.getElementById('product-price').textContent = `$${product.price / 100}`;
76
+ document.getElementById('product-price').textContent = `$${product.priceInfo?.minimum / 100}`;
75
77
  ```
76
78
 
77
79
  #### Sync with Analytics
@@ -84,7 +86,7 @@ window.addEventListener('lce:actions.product_loaded', () => {
84
86
  items: [{
85
87
  item_id: product.identifier,
86
88
  item_name: product.name,
87
- price: product.price / 100,
89
+ price: product.priceInfo?.minimum / 100,
88
90
  item_brand: product.brand
89
91
  }]
90
92
  });
@@ -97,7 +99,7 @@ window.addEventListener('lce:actions.product_loaded', () => {
97
99
  const product1 = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');
98
100
  const product2 = window.LiquidCommerce.elements.actions.product.getDetails('08504405135');
99
101
 
100
- if (product1.price < product2.price) {
102
+ if (product1.priceInfo?.minimum < product2.priceInfo?.minimum) {
101
103
  console.log(`${product1.name} is cheaper`);
102
104
  }
103
105
  ```
@@ -124,14 +126,58 @@ getProductAvailabilityByState(
124
126
  | `identifiers` | string[] | Yes | Array of product UPCs, SKUs, or IDs |
125
127
  | `state` | string | No | Two-letter state code (e.g., `'NY'`) |
126
128
 
129
+ ### Returns
130
+
131
+ Resolves to an `IProductAvailabilityResponse`:
132
+
133
+ ```typescript
134
+ interface IProductAvailabilityResponse {
135
+ products: IProduct[];
136
+ retailers: Record<string /* retailer ID */, IRetailer>;
137
+ }
138
+
139
+ interface IRetailer {
140
+ id: string;
141
+ name: string;
142
+ address: IRetailerAddress; // IAddressAddress & IAddressCoordinates
143
+ addressFormatted: string;
144
+ shippingFulfillment: IFulfillment | null;
145
+ onDemandFulfillment: IFulfillment | null;
146
+ }
147
+
148
+ interface IFulfillment {
149
+ id: string;
150
+ type: FulfillmentType; // 'shipping' | 'onDemand'
151
+ doesAllowGiftCards: boolean;
152
+ doesAllowPromos: boolean;
153
+ expectation: string;
154
+ engravingExpectation: string;
155
+ fee: number;
156
+ timezone: string;
157
+ hourStatus: { isOpen: boolean; openTime: string; isClosed: boolean; closeTime: string };
158
+ }
159
+
160
+ interface IProduct {
161
+ id: string; name: string; description: string; htmlDescription: string;
162
+ images: string[]; brand: string; region: string; country: string;
163
+ material: string; abv: string; proof: string; age: string; color: string;
164
+ flavor: string; variety: string; appellation: string; vintage: string;
165
+ tastingNotes: string; catPath: string; category: string; classification: string;
166
+ type: string; subType: string; salsifyGrouping: string;
167
+ priceInfo: IProductPriceInfo | null; // { currency, minimum, average, maximum } — cents
168
+ sizes: Record<string /* size ID */, IProductSize>;
169
+ }
170
+ ```
171
+
127
172
  ### Example
128
173
 
129
174
  ```javascript
130
175
  // Check availability in a specific state
131
- const availability = await window.LiquidCommerce.elements.actions.product
176
+ const availabilityCA = await window.LiquidCommerce.elements.actions.product
132
177
  .getProductAvailabilityByState(['00619947000020', '08504405135'], 'CA');
133
178
 
134
- // Check availability in the user's current state (uses address if set)
179
+ // Omit the state to query without a state filter
180
+ // (state: undefined is sent — the SDK does NOT derive it from the stored address)
135
181
  const availability = await window.LiquidCommerce.elements.actions.product
136
182
  .getProductAvailabilityByState(['00619947000020']);
137
183
  ```
@@ -64,6 +64,36 @@ const client = await ElementsCheckout('YOUR_API_KEY', {
64
64
  });
65
65
  ```
66
66
 
67
+ ### ElementsBuilder()
68
+
69
+ Initialize the builder client, which exposes manual `inject*` / `update*` methods for fully programmatic composition.
70
+
71
+ ```typescript
72
+ function ElementsBuilder(
73
+ apiKey: string,
74
+ config: ILiquidCommerceElementsBuilderConfig
75
+ ): Promise<ILiquidCommerceElementsBuilderClient | null>
76
+ ```
77
+
78
+ **Parameters:**
79
+
80
+ | Parameter | Type | Required | Description |
81
+ |-----------|--------------------------------------|----------|-----------------------------|
82
+ | `apiKey` | string | Yes | Your LiquidCommerce API key |
83
+ | `config` | ILiquidCommerceElementsBuilderConfig | Yes | Builder configuration object (same shape as `ILiquidCommerceElementsConfig`) |
84
+
85
+ **Returns:** Promise that resolves to the builder client instance (`ILiquidCommerceElementsBuilderClient`), or `null` if initialization fails or it is called outside the browser.
86
+
87
+ **Example:**
88
+
89
+ ```javascript
90
+ import { ElementsBuilder } from '@liquidcommerce/elements-sdk';
91
+
92
+ const builder = await ElementsBuilder('YOUR_API_KEY', {
93
+ env: 'production'
94
+ });
95
+ ```
96
+
67
97
  ## Configuration
68
98
 
69
99
  ### ILiquidCommerceElementsConfig
@@ -72,10 +102,8 @@ Complete configuration interface for the full SDK.
72
102
 
73
103
  ```typescript
74
104
  interface ILiquidCommerceElementsConfig {
75
- // Required
76
- env: ElementsEnv; // 'development' | 'staging' | 'production'
77
-
78
105
  // Optional
106
+ env?: ElementsEnv; // defaults to 'production' ('development' | 'staging' | 'production')
79
107
  debugMode?: DebugMode; // 'none' | 'console' | 'panel'
80
108
  customTheme?: IClientCustomThemeConfig;
81
109
  promoTicker?: IPromoTicker[];
@@ -168,7 +196,7 @@ See [Proxy Setup Guide](../integration/proxy-setup.md) for implementation.
168
196
 
169
197
  ```typescript
170
198
  interface ILiquidCommerceElementsCheckoutConfig {
171
- pageUrl: string; // URL pattern with {token} placeholder
199
+ pageUrl?: string; // Optional. URL pattern with {token} placeholder
172
200
  }
173
201
  ```
174
202
 
@@ -186,6 +214,7 @@ checkout: {
186
214
  interface ILiquidCommerceElementsDevelopmentConfig {
187
215
  customApiUrl?: string;
188
216
  openShadowDom?: boolean;
217
+ mockMode?: boolean; // enable mock data responses (sends 'X-Liquid-Api-Mock-Mode' header)
189
218
  }
190
219
  ```
191
220
 
@@ -194,16 +223,18 @@ interface ILiquidCommerceElementsDevelopmentConfig {
194
223
  ```javascript
195
224
  development: {
196
225
  customApiUrl: 'http://localhost:3000/api',
197
- openShadowDom: true // Disable Shadow DOM for debugging
226
+ openShadowDom: true // Use an OPEN (inspectable) Shadow DOM instead of the default closed mode; forced off in production
198
227
  }
199
228
  ```
200
229
 
201
230
  ## Global Access
202
231
 
203
- After initialization, the client is available globally under the `window.LiquidCommerce` namespace:
232
+ After initialization, each client is available globally under the `window.LiquidCommerce` namespace:
204
233
 
205
234
  ```javascript
206
- window.LiquidCommerce.elements
235
+ window.LiquidCommerce.elements // full client (from Elements())
236
+ window.LiquidCommerce.elementsBuilder // builder client (from ElementsBuilder())
237
+ window.LiquidCommerce.elementsCheckout // checkout-only client (from ElementsCheckout())
207
238
  ```
208
239
 
209
240
  This allows access from anywhere in your application:
@@ -397,13 +428,6 @@ import type {
397
428
  IInjectCheckoutParams,
398
429
  IInjectedComponent,
399
430
 
400
- // Action types
401
- IProductActions,
402
- ICartActions,
403
- ICheckoutActions,
404
- IAddressActions,
405
- IAddProductParams,
406
-
407
431
  // Configuration types
408
432
  IClientCustomThemeConfig,
409
433
  IComponentGlobalConfigs,
@@ -171,6 +171,7 @@ interface IProductLayout {
171
171
  showOnlyMainImage: boolean;
172
172
  showTitle: boolean;
173
173
  showDescription: boolean;
174
+ descriptionPosition: 'above' | 'below';
174
175
  showQuantityCounter: boolean;
175
176
  showOffHours: boolean;
176
177
  quantityCounterStyle: 'outlined' | 'ghost';
@@ -192,6 +193,7 @@ interface IProductLayout {
192
193
  | `showOnlyMainImage` | `boolean` | Show only the main product image (no carousel) |
193
194
  | `showTitle` | `boolean` | Show product title |
194
195
  | `showDescription` | `boolean` | Show product description |
196
+ | `descriptionPosition` | `'above' \| 'below'` | Placement of the product description relative to other content |
195
197
  | `showQuantityCounter` | `boolean` | Show quantity selector |
196
198
  | `showOffHours` | `boolean` | Show off-hours messaging |
197
199
  | `quantityCounterStyle` | `'outlined' \| 'ghost'` | Visual style for quantity counter |
@@ -509,13 +511,15 @@ See [Proxy Setup Guide](../integration/proxy-setup.md) for implementation detail
509
511
  interface ILiquidCommerceElementsDevelopmentConfig {
510
512
  customApiUrl?: string;
511
513
  openShadowDom?: boolean;
514
+ mockMode?: boolean;
512
515
  }
513
516
  ```
514
517
 
515
518
  | Property | Type | Description |
516
519
  |----------|------|-------------|
517
520
  | `customApiUrl` | `string` | Custom API URL for local or custom backend testing |
518
- | `openShadowDom` | `boolean` | Disable Shadow DOM isolation for debugging (default: `false`) |
521
+ | `openShadowDom` | `boolean` | Use an OPEN (inspectable) Shadow DOM instead of the default closed mode, for debugging. Shadow DOM is always attached; forced off in production (default: `false`) |
522
+ | `mockMode` | `boolean` | Enable mock mode to receive mock data from the API; sends an `X-Liquid-Api-Mock-Mode` header with all requests (default: `false`) |
519
523
 
520
524
  > To pre-select a payment method and skip the card form (in any environment), use [`actions.checkout.setSavedPaymentMethod`](./actions/checkout-actions.md#actionscheckoutsetsavedpaymentmethod) instead.
521
525
 
@@ -53,9 +53,12 @@ injectAddressElement(
53
53
 
54
54
  ```javascript
55
55
  await client.injectAddressElement('address-container', {
56
- onAddressSet: (address) => {
57
- console.log('Address set:', address);
58
- }
56
+ showLabel: true
57
+ });
58
+
59
+ // Listen for address changes via the published event (dispatched on window)
60
+ window.addEventListener('lce:actions.address_updated', (event) => {
61
+ console.log('Address set:', event.detail.data);
59
62
  });
60
63
  ```
61
64
 
@@ -127,7 +130,7 @@ injectProductList(params: IInjectProductListParams): Promise<void>
127
130
  interface IInjectProductListParams {
128
131
  containerId: string;
129
132
  slug: string; // Product list slug identifier
130
- rows?: number; // Default: 3
133
+ rows?: number; // Default: 4
131
134
  columns?: number; // Default: 4
132
135
  filters?: ProductListFilterType[];
133
136
  productUrl?: PLCProductUrl; // string template OR Record<identifier, url> map
@@ -192,6 +195,7 @@ injectProductListSearch(params: IInjectProductListSearchParams): Promise<void>
192
195
  interface IInjectProductListSearchParams {
193
196
  containerId: string;
194
197
  slug: string; // Product list slug identifier
198
+ filters?: ProductListFilterType[];
195
199
  }
196
200
  ```
197
201