@liquidcommerce/elements-sdk 2.6.0-beta.96 → 2.6.0-beta.97

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 (53) hide show
  1. package/README.md +20 -4
  2. package/dist/index.checkout.esm.js +7883 -7316
  3. package/dist/index.esm.js +12992 -12079
  4. package/dist/types/core/a11y/glyph-button.d.ts +8 -0
  5. package/dist/types/core/a11y/index.d.ts +2 -0
  6. package/dist/types/core/a11y/single-select.d.ts +16 -0
  7. package/dist/types/modules/address/address-display.component.d.ts +3 -1
  8. package/dist/types/modules/product/components/components.d.ts +6 -1
  9. package/dist/types/modules/product/components/product-add-to-cart-section.component.d.ts +6 -1
  10. package/dist/types/modules/product/components/product-image-carousel.component.d.ts +1 -0
  11. package/dist/types/modules/product/components/product-retailers-carousel.component.d.ts +1 -0
  12. package/dist/types/modules/product/components/product-retailers-popup-list.component.d.ts +5 -0
  13. package/dist/types/modules/product-list/components/card-components/product-quantity-selector.d.ts +2 -1
  14. package/dist/types/modules/theme-provider/services/contrast-guard.service.d.ts +19 -0
  15. package/dist/types/modules/ui-components/input/input.component.d.ts +1 -0
  16. package/dist/types/modules/ui-components/lce-element/lce-element.component.d.ts +9 -1
  17. package/dist/types/static/icon/arrow-right.icon.d.ts +1 -1
  18. package/dist/types/static/icon/bag.icon.d.ts +1 -1
  19. package/dist/types/static/icon/check.icon.d.ts +1 -1
  20. package/dist/types/static/icon/checkbox.icon.d.ts +1 -1
  21. package/dist/types/static/icon/chevron-down.icon.d.ts +1 -1
  22. package/dist/types/static/icon/chevron-left.icon.d.ts +1 -1
  23. package/dist/types/static/icon/chevron-up.icon.d.ts +1 -1
  24. package/dist/types/static/icon/close.icon.d.ts +1 -1
  25. package/dist/types/static/icon/error-info.icon.d.ts +1 -1
  26. package/dist/types/static/icon/filter.icon.d.ts +1 -1
  27. package/dist/types/static/icon/icon.a11y.d.ts +1 -0
  28. package/dist/types/static/icon/icon.types.d.ts +1 -0
  29. package/dist/types/static/icon/index.d.ts +1 -0
  30. package/dist/types/static/icon/info.icon.d.ts +1 -1
  31. package/dist/types/static/icon/loading-spinner.icon.d.ts +1 -1
  32. package/dist/types/static/icon/search.icon.d.ts +1 -1
  33. package/dist/types/static/icon/success.icon.d.ts +1 -1
  34. package/dist/types/static/icon/trash.icon.d.ts +1 -1
  35. package/dist/types/static/icon/warning.icon.d.ts +1 -1
  36. package/dist/types/utils/color-contrast.d.ts +16 -0
  37. package/docs/v1/README.md +3 -0
  38. package/docs/v1/api/actions/address-actions.md +1 -1
  39. package/docs/v1/api/client.md +4 -3
  40. package/docs/v1/api/configuration.md +13 -0
  41. package/docs/v1/api/ui-helpers.md +2 -2
  42. package/docs/v1/getting-started/concepts.md +24 -7
  43. package/docs/v1/getting-started/installation.md +2 -2
  44. package/docs/v1/getting-started/quick-start.md +8 -3
  45. package/docs/v1/guides/accessibility.md +190 -0
  46. package/docs/v1/guides/address-component.md +3 -3
  47. package/docs/v1/guides/best-practices.md +1 -1
  48. package/docs/v1/guides/cart-component.md +3 -3
  49. package/docs/v1/guides/events.md +18 -5
  50. package/docs/v1/guides/theming.md +6 -1
  51. package/docs/v1/integration/vanilla-js.md +1 -1
  52. package/docs/v1/reference/error-handling.md +1 -1
  53. package/package.json +1 -1
@@ -0,0 +1,8 @@
1
+ export interface IGlyphButtonParams {
2
+ glyph: string;
3
+ label: string;
4
+ className?: string;
5
+ focusKey?: string;
6
+ enabled?: boolean;
7
+ }
8
+ export declare function createGlyphButton({ glyph, label, className, focusKey, enabled }: IGlyphButtonParams): HTMLButtonElement;
@@ -1,3 +1,5 @@
1
1
  export { type AnnouncementPoliteness, AnnouncerService } from '@/core/a11y/announcer.service';
2
2
  export { A11Y_PERSIST_ATTRIBUTE, FocusManagerService } from '@/core/a11y/focus-manager.service';
3
3
  export { getFocusableElements, isFocusable, isVisible, safeFocus } from '@/core/a11y/focusable';
4
+ export { type IGlyphButtonParams, createGlyphButton } from '@/core/a11y/glyph-button';
5
+ export { type IRovingFocusOptions, type RovingOrientation, applyOptionSemantics, focusSelectedItem, updateSelection, wireRovingFocus, } from '@/core/a11y/single-select';
@@ -0,0 +1,16 @@
1
+ export type RovingOrientation = 'horizontal' | 'vertical' | 'both';
2
+ type SelectionAttribute = 'aria-selected' | 'aria-checked';
3
+ export interface IRovingFocusOptions {
4
+ items: HTMLElement[];
5
+ orientation?: RovingOrientation;
6
+ selectionFollowsFocus?: boolean;
7
+ onActivate?: (item: HTMLElement, index: number) => void;
8
+ }
9
+ export declare function updateSelection(items: HTMLElement[], selected: HTMLElement | null, attribute?: SelectionAttribute): void;
10
+ export declare function applyOptionSemantics(element: HTMLElement, { label, selected }: {
11
+ label: string;
12
+ selected: boolean;
13
+ }): void;
14
+ export declare function focusSelectedItem(root: ParentNode, itemSelector: string): boolean;
15
+ export declare function wireRovingFocus({ items, orientation, selectionFollowsFocus, onActivate }: IRovingFocusOptions): void;
16
+ export {};
@@ -1,4 +1,4 @@
1
- import { BaseComponent } from '@/core/base-component.service';
1
+ import { BaseComponent, type IOnStoreChanged } from '@/core/base-component.service';
2
2
  import type { IAddressComponent } from '@/interfaces/configs';
3
3
  import type { IAddressOptions } from './address.interface';
4
4
  export interface IAddressDisplayComponentParams {
@@ -8,6 +8,8 @@ export interface IAddressDisplayComponentParams {
8
8
  }
9
9
  export declare class AddressDisplayComponent extends BaseComponent<IAddressDisplayComponentParams, IAddressComponent> {
10
10
  get hostClasses(): string[];
11
+ private lastAnnouncedAddress;
11
12
  constructor();
13
+ onStoreChanged(changes: IOnStoreChanged[]): boolean | undefined;
12
14
  protected template(): HTMLElement[];
13
15
  }
@@ -1,11 +1,16 @@
1
1
  import type { IProductFulfillmentStore } from '@/core/store/interfaces/product.interface';
2
2
  import { type FulfillmentType } from '@/enums';
3
3
  import type { IProductSizeAttributes } from '@/interfaces/api/product.interface';
4
- export declare function renderPopupRetailerCard({ fulfillment, selectedFulfillmentType, isSelected, selectedSizeAttributes, }: {
4
+ export declare const FULFILLMENT_TAB_SELECTOR = ".fulfillment-tab";
5
+ export declare function fulfillmentSelectedMessage(fulfillment: IProductFulfillmentStore, type: FulfillmentType): string;
6
+ export declare function fulfillmentTypeMessage(type: FulfillmentType, optionCount: number): string;
7
+ export declare function focusSelectedFulfillmentTab(root: ParentNode): boolean;
8
+ export declare function renderPopupRetailerCard({ fulfillment, selectedFulfillmentType, isSelected, selectedSizeAttributes, interactive, }: {
5
9
  fulfillment: IProductFulfillmentStore;
6
10
  selectedFulfillmentType: FulfillmentType;
7
11
  isSelected: boolean;
8
12
  selectedSizeAttributes: IProductSizeAttributes | null;
13
+ interactive?: boolean;
9
14
  }): HTMLElement;
10
15
  export declare function renderFulfillmentTabsContainer({ shippingFulfillments, onDemandFulfillments, shippingSelected, onDemandSelected, onTabClick, enableShippingFulfillment, enableOnDemandFulfillment, }: {
11
16
  shippingFulfillments: IProductFulfillmentStore[];
@@ -1,4 +1,4 @@
1
- import { BaseComponent } from '@/core/base-component.service';
1
+ import { BaseComponent, type IOnStoreChanged } from '@/core/base-component.service';
2
2
  import type { IProductComponent } from '@/interfaces/configs';
3
3
  export interface IAddToCartSectionComponentParams {
4
4
  productId: string;
@@ -8,10 +8,13 @@ export declare class ProductAddToCartSectionComponent extends BaseComponent<IAdd
8
8
  private qtyIncreaseButton;
9
9
  private qtyDecreaseButton;
10
10
  private quantityText;
11
+ private pendingFocusKey;
11
12
  get hostClasses(): string[];
12
13
  get hostAttributes(): Record<string, string>;
13
14
  constructor();
14
15
  private getProductId;
16
+ onStoreChanged(changes: IOnStoreChanged[]): boolean | undefined;
17
+ afterRender(): void;
15
18
  private getAvailableQuantity;
16
19
  private isFulfillmentClosed;
17
20
  private isButtonLoading;
@@ -19,6 +22,8 @@ export declare class ProductAddToCartSectionComponent extends BaseComponent<IAdd
19
22
  private handleAddToCart;
20
23
  private handleQuantityChange;
21
24
  private calculateTotalPrice;
25
+ private stepperFocusKey;
26
+ private createStepper;
22
27
  private createQuantityContainer;
23
28
  private getSizeAttributes;
24
29
  private openEngravingForm;
@@ -36,5 +36,6 @@ export declare class ProductImageCarouselComponent extends BaseComponent<IImageC
36
36
  private updateTransform;
37
37
  private nextImage;
38
38
  private previousImage;
39
+ private mainImageLabel;
39
40
  protected template(): HTMLElement[];
40
41
  }
@@ -8,6 +8,7 @@ export declare class ProductRetailersCarouselComponent extends BaseComponent<IPr
8
8
  private listViewport;
9
9
  private listContainer;
10
10
  private retailerCards;
11
+ private pendingTabFocus;
11
12
  private isDragging;
12
13
  private hasMoved;
13
14
  private startX;
@@ -9,8 +9,13 @@ export interface IProductRetailersPopupListComponentParams {
9
9
  export declare class ProductRetailersPopupListComponent extends BaseComponent<IProductRetailersPopupListComponentParams, IProductComponent> {
10
10
  get hostClasses(): string[];
11
11
  private get isProductContext();
12
+ private productPath;
12
13
  beforeSetupStoreWatchers(): void;
14
+ private pendingTabFocus;
15
+ protected afterRender(): void;
13
16
  private onFulfillmentTabClick;
17
+ private announceSelectedFulfillment;
18
+ private announceFulfillmentTypeChange;
14
19
  private onRetailerCardClick;
15
20
  protected template(): HTMLElement[];
16
21
  }
@@ -5,6 +5,7 @@ export interface IQuantitySelectorParams {
5
5
  maxQuantity: number;
6
6
  cardStyle: PLCCardStyle;
7
7
  disabled?: boolean;
8
+ productName?: string;
8
9
  onChange: (quantity: number) => void;
9
10
  }
10
- export declare const createQuantitySelector: ({ initialQuantity, minQuantity, maxQuantity, cardStyle, disabled, onChange, }: IQuantitySelectorParams) => HTMLElement;
11
+ export declare const createQuantitySelector: ({ initialQuantity, minQuantity, maxQuantity, cardStyle, disabled, productName, onChange, }: IQuantitySelectorParams) => HTMLElement;
@@ -0,0 +1,19 @@
1
+ import type { IGlobalTheme } from '@/interfaces/configs';
2
+ import { type IRgbColor } from '@/utils/color-contrast';
3
+ interface IGuardedHost {
4
+ apply: () => void;
5
+ }
6
+ export declare class ContrastGuardService {
7
+ private readonly logger;
8
+ private readonly hosts;
9
+ private readonly reported;
10
+ static getInstance(): ContrastGuardService;
11
+ register(host: IGuardedHost): () => void;
12
+ refreshAll(): void;
13
+ computeOverrides(theme: Partial<IGlobalTheme> | undefined, surface: IRgbColor | null): Record<string, string>;
14
+ private token;
15
+ private correctAgainstAll;
16
+ private correct;
17
+ private report;
18
+ }
19
+ export {};
@@ -20,6 +20,7 @@ export interface IInputComponentParams {
20
20
  onValidation?: (isValid: boolean, errors: string[]) => void;
21
21
  onChange?: (value: string) => void;
22
22
  label?: string;
23
+ ariaLabel?: string;
23
24
  name: string;
24
25
  autocomplete?: boolean;
25
26
  disabled?: boolean;
@@ -1,12 +1,20 @@
1
- import type { ComponentType } from '@/enums';
1
+ import { type ComponentType } from '@/enums';
2
2
  import { SafeHTMLElement } from '@/utils/dom-compat';
3
3
  export declare class LceElementComponent extends SafeHTMLElement {
4
4
  private _initialized;
5
5
  protected _container: ShadowRoot | null;
6
6
  private _wrappedComponentRerender;
7
+ private _contentType;
8
+ private _appliedContrastVariables;
9
+ private _unregisterContrastGuard;
7
10
  constructor();
8
11
  initialize(contentType: ComponentType, contentElement: HTMLElement): void;
9
12
  rerender(context?: string): void;
13
+ connectedCallback(): void;
14
+ disconnectedCallback(): void;
15
+ private applyContrastCorrections;
16
+ private resolveSurfaceColor;
17
+ private measureHostSurfaceColor;
10
18
  private applyBasicStyles;
11
19
  private isCSSStyleSheetSupported;
12
20
  private applyThemeStyles;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const ArrowRightIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const ArrowRightIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const BagIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const BagIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const CheckIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const CheckIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const CheckboxIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const CheckboxIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const ChevronDownIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const ChevronDownIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const ChevronLeftIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const ChevronLeftIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const ChevronUpIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const ChevronUpIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const CloseIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const CloseIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const ErrorInfoIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const ErrorInfoIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const FilterIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const FilterIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -0,0 +1 @@
1
+ export declare function iconA11yAttributes(label?: string): string;
@@ -3,4 +3,5 @@ export interface IconProps {
3
3
  height?: number;
4
4
  className?: string;
5
5
  color?: string | null;
6
+ label?: string;
6
7
  }
@@ -7,6 +7,7 @@ export * from './chevron-left.icon';
7
7
  export * from './chevron-up.icon';
8
8
  export * from './close.icon';
9
9
  export * from './error-info.icon';
10
+ export * from './icon.a11y';
10
11
  export * from './icon.types';
11
12
  export * from './info.icon';
12
13
  export * from './loading-spinner.icon';
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const InfoIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const InfoIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const LoadingSpinnerIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const LoadingSpinnerIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const SearchIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const SearchIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const SuccessIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const SuccessIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const TrashIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const TrashIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -1,2 +1,2 @@
1
1
  import type { IconProps } from './icon.types';
2
- export declare const WarningIcon: ({ width, height, className, color }: IconProps) => string;
2
+ export declare const WarningIcon: ({ width, height, className, color, label }: IconProps) => string;
@@ -0,0 +1,16 @@
1
+ export interface IRgbColor {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ a: number;
6
+ }
7
+ export declare const TEXT_CONTRAST_MIN = 4.5;
8
+ export declare const NON_TEXT_CONTRAST_MIN = 3;
9
+ export declare function parseColor(value: string | null | undefined): IRgbColor | null;
10
+ export declare function toHexColor({ r, g, b }: IRgbColor): string;
11
+ export declare function compositeOver(foreground: IRgbColor, background: IRgbColor): IRgbColor;
12
+ export declare function relativeLuminance({ r, g, b }: IRgbColor): number;
13
+ export declare function contrastRatio(foreground: IRgbColor, background: IRgbColor): number;
14
+ export declare function meetsContrast(foreground: IRgbColor, background: IRgbColor, minRatio: number): boolean;
15
+ export declare function ensureContrast(foreground: IRgbColor, background: IRgbColor, minRatio: number): IRgbColor;
16
+ export declare function ensureContrastAgainstAll(foreground: IRgbColor, backgrounds: IRgbColor[], minRatio: number): IRgbColor;
package/docs/v1/README.md CHANGED
@@ -32,6 +32,7 @@ In-depth guides for each major component:
32
32
  - **[Address Component](./guides/address-component.md)** - Delivery location management
33
33
  - **[Product List Component](./guides/product-list-component.md)** - Filterable product catalogs
34
34
  - **[Theming](./guides/theming.md)** - Customize appearance and branding
35
+ - **[Accessibility](./guides/accessibility.md)** - Keyboard, screen reader, contrast and focus behaviour
35
36
  - **[Events](./guides/events.md)** - Event system and subscriptions
36
37
  - **[Best Practices](./guides/best-practices.md)** - Patterns, tips, and recommendations
37
38
 
@@ -83,6 +84,8 @@ Technical reference and troubleshooting:
83
84
  - **[Troubleshooting](./reference/troubleshooting.md)** - Common issues and solutions
84
85
  - **[Error Handling](./reference/error-handling.md)** - Error types and debugging
85
86
  - **[Performance](./reference/performance.md)** - Optimization tips and best practices
87
+ - **[Analytics](./reference/analytics.md)** - Google Tag Manager / GA4 integration
88
+ - **[Telemetry](./reference/telemetry.md)** - What the SDK reports and how to control it
86
89
 
87
90
  ### Examples
88
91
 
@@ -275,7 +275,7 @@ window.addEventListener('lce:actions.client_ready', () => {
275
275
  // Prompt for address on first visit
276
276
  showAddressModal();
277
277
  }
278
- });
278
+ }, { once: true });
279
279
  ```
280
280
 
281
281
  ## See Also
@@ -256,7 +256,7 @@ window.addEventListener('lce:actions.client_ready', ( event ) => {
256
256
 
257
257
  // Safe to use client
258
258
  window.LiquidCommerce.elements.injectProductElement([...]);
259
- });
259
+ }, { once: true });
260
260
  ```
261
261
 
262
262
  ## Client Interface
@@ -488,7 +488,8 @@ const config: ILiquidCommerceElementsConfig = {
488
488
  env: 'production'
489
489
  };
490
490
 
491
- const client: ILiquidCommerceElementsClient = await Elements('KEY', config);
491
+ // Elements() resolves to `null` on failure, so the type is nullable.
492
+ const client: ILiquidCommerceElementsClient | null = await Elements('KEY', config);
492
493
  ```
493
494
 
494
495
  ### Type Exports
@@ -566,7 +567,7 @@ if (window.LiquidCommerce?.elements) {
566
567
  // Wait for client ready
567
568
  window.addEventListener('lce:actions.client_ready', () => {
568
569
  window.LiquidCommerce.elements.actions.cart.openCart();
569
- });
570
+ }, { once: true });
570
571
  }
571
572
  ```
572
573
 
@@ -74,9 +74,22 @@ interface IGlobalTheme {
74
74
  warningColor: string;
75
75
  successColor: string;
76
76
  drawerBackgroundColor: string;
77
+
78
+ /**
79
+ * Boundary color for form fields. Optional; defaults to a neutral that clears the 3:1
80
+ * contrast required by WCAG 1.4.11.
81
+ */
82
+ inputBorderColor?: string;
83
+
84
+ /** Keyboard focus ring color. Must reach 3:1 against the surface behind the control. */
85
+ focusRingColor?: string;
77
86
  }
78
87
  ```
79
88
 
89
+ > `inputBorderColor` and `focusRingColor` are optional. If you omit them — or supply a value that
90
+ > fails contrast — the runtime contrast guard substitutes a compliant color. Set them explicitly to
91
+ > keep those two details on-brand. See [Accessibility](../guides/accessibility.md).
92
+
80
93
  | Property | Type | Description |
81
94
  |----------|------|-------------|
82
95
  | `buttonCornerRadius` | `string` | CSS border-radius for buttons (e.g., `'8px'`) |
@@ -136,7 +136,7 @@ window.LiquidCommerce.elements.ui.cartItemsCount('items-count', { hideZero: fals
136
136
  <script>
137
137
  window.addEventListener('lce:actions.client_ready', () => {
138
138
  window.LiquidCommerce.elements.ui.cartButton('cart-btn', true);
139
- });
139
+ }, { once: true });
140
140
  </script>
141
141
  ```
142
142
 
@@ -153,7 +153,7 @@ window.addEventListener('lce:actions.client_ready', () => {
153
153
  window.addEventListener('lce:actions.client_ready', () => {
154
154
  window.LiquidCommerce.elements.ui.cartItemsCount('item-count');
155
155
  window.LiquidCommerce.elements.ui.cartSubtotal('cart-total');
156
- });
156
+ }, { once: true });
157
157
  </script>
158
158
  ```
159
159
 
@@ -117,7 +117,7 @@ window.addEventListener('lce:actions.client_ready', (event) => {
117
117
 
118
118
  // Safe to use client
119
119
  window.LiquidCommerce.elements.actions.cart.openCart();
120
- });
120
+ }, { once: true });
121
121
  ```
122
122
 
123
123
  ## Declarative vs Programmatic
@@ -197,7 +197,7 @@ window.addEventListener('lce:actions.client_ready', async () => {
197
197
  await window.LiquidCommerce.elements.injectProductElement([
198
198
  { containerId: 'dynamic-product', identifier: selectedProductId }
199
199
  ]);
200
- });
200
+ }, { once: true });
201
201
  </script>
202
202
  ```
203
203
 
@@ -327,20 +327,37 @@ The SDK is designed to fail gracefully and not crash your site.
327
327
 
328
328
  ### Error Isolation
329
329
 
330
- SDK errors are logged and also emitted as a `*_FAILED` event, then re-thrown so your code can catch them:
330
+ Most action failures are **not** thrown. Action methods take one of three paths, so `try/catch`
331
+ alone is not enough to detect failure:
332
+
333
+ | Situation | Behaviour |
334
+ |---|---|
335
+ | Malformed argument (missing `identifier`, invalid `fulfillmentType`, `quantity < 1`) | Throws `SDKError` — `try/catch` catches it |
336
+ | Empty array or no arguments | Logs a warning and **resolves**. No event, no throw. |
337
+ | Valid input that fails at runtime (product not found, unavailable variant, API error) | Publishes `lce:actions.cart_product_add_failed` and **resolves** |
338
+
339
+ So listen for the `*_failed` event **and** wrap the call in `try/catch`:
331
340
 
332
341
  ```javascript
333
- // The SDK emits CART_PRODUCT_ADD_FAILED and re-throws, so this catch runs
342
+ window.addEventListener('lce:actions.cart_product_add_failed', (event) => {
343
+ console.log('Add to cart failed:', event.detail.data);
344
+ });
345
+
334
346
  try {
335
- await window.LiquidCommerce.elements.actions.cart.addProduct([/* invalid data */]);
347
+ // Throws only if the argument itself is malformed.
348
+ await window.LiquidCommerce.elements.actions.cart.addProduct([
349
+ { identifier: '00619947000020', fulfillmentType: 'shipping', quantity: 1 }
350
+ ]);
336
351
  } catch (error) {
337
- console.log('This error is re-thrown by the SDK and caught here');
352
+ console.log('Malformed input rejected by the SDK');
338
353
  }
339
354
 
340
- // Your page keeps working
355
+ // Either way, your page keeps working
341
356
  console.log('Page still functional');
342
357
  ```
343
358
 
359
+ See [Error Handling](../reference/error-handling.md) for the full matrix.
360
+
344
361
  ### Error Types
345
362
 
346
363
  The SDK uses a custom `SDKError` class for all errors:
@@ -37,7 +37,7 @@ The path segment selects which bundle is served:
37
37
  | Path | Bundle |
38
38
  |------|--------|
39
39
  | `/all/elements.js` | Full SDK bundle (product, cart, checkout, and all other elements) |
40
- | `/checkout/elements.js` | Checkout-only bundle (tree-shaken, smaller) |
40
+ | `/checkout/checkout.js` | Checkout-only bundle (tree-shaken, smaller) |
41
41
  | `/all/beta/elements.js` | Beta channel of the full SDK bundle |
42
42
 
43
43
  ### Script Attributes
@@ -87,7 +87,7 @@ If you only need checkout functionality (without product displays or cart), use
87
87
  data-token="YOUR_API_KEY"
88
88
  data-env="production"
89
89
  type="text/javascript"
90
- src="https://elements.reservebar-worker.workers.dev/checkout/elements.js"
90
+ src="https://elements.reservebar-worker.workers.dev/checkout/checkout.js"
91
91
  ></script>
92
92
  ```
93
93
 
@@ -165,7 +165,7 @@ If you prefer JavaScript over HTML attributes:
165
165
  identifier: '00619947000020'
166
166
  }
167
167
  ]);
168
- });
168
+ }, { once: true });
169
169
  </script>
170
170
  </head>
171
171
  <body>
@@ -181,10 +181,15 @@ Or with NPM:
181
181
  import { Elements } from '@liquidcommerce/elements-sdk';
182
182
 
183
183
  async function initProduct() {
184
+ // Elements() resolves to `null` if initialization fails — it never throws.
184
185
  const client = await Elements('YOUR_API_KEY', {
185
186
  env: 'production'
186
187
  });
187
-
188
+
189
+ if (!client) {
190
+ return;
191
+ }
192
+
188
193
  await client.injectProductElement([
189
194
  {
190
195
  containerId: 'product-display',
@@ -256,7 +261,7 @@ window.addEventListener('lce:actions.client_ready', async () => {
256
261
 
257
262
  // You can customize after initialization
258
263
  // See Theming Guide for more options
259
- });
264
+ }, { once: true });
260
265
  </script>
261
266
  ```
262
267