@liquidcommerce/elements-sdk 2.7.24 → 2.7.26
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/README.md +20 -4
- package/dist/index.checkout.esm.js +8435 -7152
- package/dist/index.esm.js +13789 -11658
- package/dist/types/core/a11y/announcer.service.d.ts +16 -0
- package/dist/types/core/a11y/focus-manager.service.d.ts +21 -0
- package/dist/types/core/a11y/focusable.d.ts +5 -0
- package/dist/types/core/a11y/glyph-button.d.ts +8 -0
- package/dist/types/core/a11y/index.d.ts +5 -0
- package/dist/types/core/a11y/single-select.d.ts +16 -0
- package/dist/types/core/base-component.service.d.ts +2 -0
- package/dist/types/core/google-tag-manager.service.d.ts +1 -0
- package/dist/types/interfaces/configs/global.interface.d.ts +2 -0
- package/dist/types/modules/address/address-display.component.d.ts +3 -1
- package/dist/types/modules/address/address-input.component.d.ts +10 -1
- package/dist/types/modules/cart/components/cart-body.component.d.ts +1 -0
- package/dist/types/modules/cart/components/cart-item-quantity-price.component.d.ts +5 -0
- package/dist/types/modules/cart/components/cart-item.component.d.ts +6 -0
- package/dist/types/modules/checkout/components/checkout-completed.component.d.ts +2 -0
- package/dist/types/modules/checkout/components/checkout-item-quantity.component.d.ts +1 -0
- package/dist/types/modules/checkout/components/checkout-payment.component.d.ts +1 -0
- package/dist/types/modules/checkout/components/checkout-place-order-button.component.d.ts +2 -0
- package/dist/types/modules/product/components/components.d.ts +6 -1
- package/dist/types/modules/product/components/product-add-to-cart-section.component.d.ts +6 -1
- package/dist/types/modules/product/components/product-drawer.component.d.ts +6 -0
- package/dist/types/modules/product/components/product-image-carousel.component.d.ts +1 -0
- package/dist/types/modules/product/components/product-retailers-carousel.component.d.ts +1 -0
- package/dist/types/modules/product/components/product-retailers-popup-list.component.d.ts +5 -0
- package/dist/types/modules/product/product.commands.d.ts +2 -2
- package/dist/types/modules/product-list/components/card-components/product-price-and-personalization.d.ts +2 -1
- package/dist/types/modules/product-list/components/card-components/product-quantity-selector.d.ts +2 -1
- package/dist/types/modules/product-list/components/card-components/product-sizes-list.d.ts +1 -0
- package/dist/types/modules/product-list/components/filter-components/product-list-fulfillment-filter.d.ts +1 -0
- package/dist/types/modules/product-list/components/product-list-filters.component.d.ts +1 -0
- package/dist/types/modules/product-list/components/product-list-product-pre-cart.component.d.ts +2 -0
- package/dist/types/modules/product-list/product-list.commands.d.ts +1 -1
- package/dist/types/modules/product-list/product-list.component.d.ts +3 -0
- package/dist/types/modules/theme-provider/services/contrast-guard.service.d.ts +19 -0
- package/dist/types/modules/ui-components/alert/alert.component.d.ts +1 -1
- package/dist/types/modules/ui-components/buttons/buttons-open-cart.component.d.ts +5 -0
- package/dist/types/modules/ui-components/drawer/drawer.component.d.ts +14 -0
- package/dist/types/modules/ui-components/engraving/engraving-form.component.d.ts +2 -0
- package/dist/types/modules/ui-components/engraving/engraving-view.component.d.ts +1 -0
- package/dist/types/modules/ui-components/input/input.component.d.ts +5 -0
- package/dist/types/modules/ui-components/lce-element/lce-element.component.d.ts +9 -1
- package/dist/types/modules/ui-components/ui.commands.d.ts +3 -1
- package/dist/types/static/icon/arrow-right.icon.d.ts +1 -1
- package/dist/types/static/icon/bag.icon.d.ts +1 -1
- package/dist/types/static/icon/check.icon.d.ts +1 -1
- package/dist/types/static/icon/checkbox.icon.d.ts +1 -1
- package/dist/types/static/icon/chevron-down.icon.d.ts +1 -1
- package/dist/types/static/icon/chevron-left.icon.d.ts +1 -1
- package/dist/types/static/icon/chevron-up.icon.d.ts +1 -1
- package/dist/types/static/icon/close.icon.d.ts +1 -1
- package/dist/types/static/icon/error-info.icon.d.ts +1 -1
- package/dist/types/static/icon/filter.icon.d.ts +1 -1
- package/dist/types/static/icon/icon.a11y.d.ts +1 -0
- package/dist/types/static/icon/icon.types.d.ts +1 -0
- package/dist/types/static/icon/index.d.ts +1 -0
- package/dist/types/static/icon/info.icon.d.ts +1 -1
- package/dist/types/static/icon/loading-spinner.icon.d.ts +1 -1
- package/dist/types/static/icon/search.icon.d.ts +1 -1
- package/dist/types/static/icon/success.icon.d.ts +1 -1
- package/dist/types/static/icon/trash.icon.d.ts +1 -1
- package/dist/types/static/icon/warning.icon.d.ts +1 -1
- package/dist/types/utils/color-contrast.d.ts +16 -0
- package/docs/v1/README.md +3 -0
- package/docs/v1/api/actions/address-actions.md +1 -1
- package/docs/v1/api/actions/cart-actions.md +9 -2
- package/docs/v1/api/actions/checkout-actions.md +2 -0
- package/docs/v1/api/client.md +88 -9
- package/docs/v1/api/configuration.md +23 -0
- package/docs/v1/api/typescript-types.md +5 -1
- package/docs/v1/api/ui-helpers.md +2 -2
- package/docs/v1/getting-started/concepts.md +29 -10
- package/docs/v1/getting-started/installation.md +23 -2
- package/docs/v1/getting-started/quick-start.md +8 -3
- package/docs/v1/guides/accessibility.md +190 -0
- package/docs/v1/guides/address-component.md +3 -3
- package/docs/v1/guides/best-practices.md +1 -1
- package/docs/v1/guides/cart-component.md +35 -3
- package/docs/v1/guides/events.md +18 -5
- package/docs/v1/guides/product-component.md +6 -2
- package/docs/v1/guides/product-list-component.md +4 -5
- package/docs/v1/guides/theming.md +16 -1
- package/docs/v1/integration/vanilla-js.md +1 -1
- package/docs/v1/reference/analytics.md +108 -0
- package/docs/v1/reference/browser-support.md +3 -1
- package/docs/v1/reference/error-handling.md +9 -1
- package/docs/v1/reference/telemetry.md +85 -0
- package/docs/v1/reference/troubleshooting.md +1 -2
- package/package.json +44 -15
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Accessibility Guide
|
|
2
|
+
|
|
3
|
+
What the Elements SDK does for accessibility, what it needs from your page, and how to verify it.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Elements target **WCAG 2.1 Level AA**. Every element ships with keyboard support, screen-reader
|
|
8
|
+
announcements, managed focus and enforced colour contrast — there is nothing to switch on.
|
|
9
|
+
|
|
10
|
+
Accessibility is shared between the SDK and the host page:
|
|
11
|
+
|
|
12
|
+
| The SDK owns | Your page owns |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Roles, names and states of everything inside an element | Page structure: landmarks, heading order, `<html lang>` |
|
|
15
|
+
| Keyboard operation of every control it renders | Contrast of your own content, and the surface you place elements on |
|
|
16
|
+
| Focus movement into and out of its overlays | A theme whose colours are legible (the SDK corrects what it can, and logs when it does) |
|
|
17
|
+
| Announcing its own content changes | Not hiding or `inert`-ing the element container |
|
|
18
|
+
| Reflow down to a 320px viewport | Layout around the element at 400% zoom |
|
|
19
|
+
|
|
20
|
+
## Keyboard navigation
|
|
21
|
+
|
|
22
|
+
Every control is reachable and operable with a keyboard alone. Related controls are grouped into
|
|
23
|
+
a **single tab stop** with arrow keys inside it, so a long list of retailers or sizes is not a
|
|
24
|
+
tab-through slog.
|
|
25
|
+
|
|
26
|
+
| Widget | Keys |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Size selector (product) | `Tab` to the group, `←` `→` to move, `Enter` / `Space` to choose, `Home` / `End` for the ends |
|
|
29
|
+
| Size dropdown (product list) | `Enter` / `Space` / `↓` opens, `↑` `↓` moves, `Enter` chooses, `Esc` closes |
|
|
30
|
+
| Delivery method tabs (Shipping / Same-Day) | `←` `→` switches method immediately; unavailable methods are skipped |
|
|
31
|
+
| Retailer / delivery-option lists | `↑` `↓` moves, `Enter` / `Space` chooses, `Home` / `End` for the ends |
|
|
32
|
+
| Retailer carousel | `←` `→` moves, `Enter` / `Space` chooses |
|
|
33
|
+
| Product image thumbnails | `←` `→` moves, `Enter` / `Space` shows the image |
|
|
34
|
+
| Quantity steppers | `Tab` to each button, `Enter` / `Space` to step |
|
|
35
|
+
| Address field | Type to search, `↑` `↓` through suggestions, `Enter` selects, `Esc` dismisses |
|
|
36
|
+
| Cart / checkout drawer, modals | `Tab` cycles inside the panel only, `Esc` closes |
|
|
37
|
+
|
|
38
|
+
Arrow keys change the selection immediately only where that is cheap and reversible (the delivery
|
|
39
|
+
method tabs). Where choosing has a side effect the user cannot navigate back out of — picking a
|
|
40
|
+
retailer closes the panel — arrow keys move focus and `Enter` commits.
|
|
41
|
+
|
|
42
|
+
### Focus management
|
|
43
|
+
|
|
44
|
+
- **Opening an overlay** moves focus into it and remembers the control that opened it.
|
|
45
|
+
- **Closing** returns focus to that control, even when a re-render has replaced it.
|
|
46
|
+
- **While an overlay is open** focus is trapped inside it and the rest of the page is `inert` and
|
|
47
|
+
`aria-hidden`, so a screen reader cannot wander behind the dialog.
|
|
48
|
+
- **In-place content swaps** (Buy Now → address form, Add to Cart → personalize) re-seat focus on
|
|
49
|
+
the new view's heading; nothing is left focused on a destroyed node.
|
|
50
|
+
- **After a re-render** focus returns to the control that caused it — the quantity stepper you
|
|
51
|
+
pressed, the delivery-method tab you switched to.
|
|
52
|
+
- **Nothing hidden is focusable.** Collapsed panels are removed from the tab order and the
|
|
53
|
+
accessibility tree, and closed overlays have their content torn down.
|
|
54
|
+
|
|
55
|
+
### Focus indicator
|
|
56
|
+
|
|
57
|
+
A 2px ring in `--focus-ring-color`, offset 2px from the control. Controls that span their
|
|
58
|
+
container's full width use an inset ring instead, because an outward ring on those is clipped by
|
|
59
|
+
the scrolling panel around them. On primary-coloured fills the ring switches to
|
|
60
|
+
`--selected-text-color`, which is guaranteed legible against that fill.
|
|
61
|
+
|
|
62
|
+
The ring targets `:focus` as well as `:focus-visible`, so browsers without `:focus-visible`
|
|
63
|
+
support still show it.
|
|
64
|
+
|
|
65
|
+
## Screen reader support
|
|
66
|
+
|
|
67
|
+
Elements are Web Components inside a shadow root, which is fully visible to assistive technology.
|
|
68
|
+
|
|
69
|
+
### Announcements
|
|
70
|
+
|
|
71
|
+
Content that appears or changes without navigation is announced through a shared live region
|
|
72
|
+
(`role="status"`, `aria-live="polite"`; `role="alert"` for errors) that lives at the end of
|
|
73
|
+
`document.body` and survives re-renders.
|
|
74
|
+
|
|
75
|
+
| What happened | What is announced |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Buy Now pressed | "Enter delivery address" |
|
|
78
|
+
| Address saved | "Delivery address updated. Now delivering to …" |
|
|
79
|
+
| Delivery option chosen | "Delivery option updated. Shipping from …, $…" |
|
|
80
|
+
| Delivery method switched | "Shipping selected. 5 options available." |
|
|
81
|
+
| Size chosen | "Size 750 ML selected" |
|
|
82
|
+
| Quantity stepped | "Tito's Handmade Vodka, quantity 4" |
|
|
83
|
+
| Personalization saved / removed | "Personalization saved" / "Personalization removed" |
|
|
84
|
+
| Cart or checkout opened / closed | "Shopping cart opened" / "Shopping cart closed" |
|
|
85
|
+
| Panel closed (product) | "Closed. Back to product options." |
|
|
86
|
+
| Item added / removed, promo applied, totals changed | The new state, e.g. "Promo code SPRING applied, $5.00 off" |
|
|
87
|
+
| Product list filtered or searched | "12 products found, showing 12" |
|
|
88
|
+
| Validation failed | The field name and the error, interrupting (assertive) |
|
|
89
|
+
| Carousel image changed | "Image 2 of 4" |
|
|
90
|
+
|
|
91
|
+
Live regions are never written into a component template: components replace their whole subtree
|
|
92
|
+
on render, and a live region recreated with its text already in place is not announced. Use
|
|
93
|
+
`this.announcer.announce(message)` from a component instead.
|
|
94
|
+
|
|
95
|
+
### Names, roles and states
|
|
96
|
+
|
|
97
|
+
- Dialogs carry `role="dialog"`, an accessible name, and `aria-modal` when they cover the page.
|
|
98
|
+
- Single-select groups are `listbox` / `radiogroup` with `option` / `radio` children carrying
|
|
99
|
+
`aria-selected` / `aria-checked` — not toggle buttons with `aria-pressed`, which would describe
|
|
100
|
+
each choice as an independent on/off switch.
|
|
101
|
+
- Unavailable controls are `aria-disabled` **and** genuinely inert: they cannot be activated.
|
|
102
|
+
- Icon-only buttons take their name from the button's `aria-label`.
|
|
103
|
+
- Visible label text is always contained in the accessible name (2.5.3 Label in Name), so speech
|
|
104
|
+
input works: saying "click See Delivery Options" activates the button that reads that way.
|
|
105
|
+
- Fields whose design has no room for a visible label (promo code, gift card) get an
|
|
106
|
+
`aria-label` instead — no rendered label, no layout change.
|
|
107
|
+
|
|
108
|
+
### Images and icons
|
|
109
|
+
|
|
110
|
+
- Product photos use the product name as their alt text; a photo painted as a CSS background is
|
|
111
|
+
exposed with `role="img"` and the same name.
|
|
112
|
+
- Decorative images inside a named control use `alt=""` so the control is not announced twice.
|
|
113
|
+
- Every inline SVG icon renders `aria-hidden="true" focusable="false"`. Icons that carry meaning
|
|
114
|
+
on their own opt in to a name: `CloseIcon({ label: 'Close' })`.
|
|
115
|
+
|
|
116
|
+
## Colour and contrast
|
|
117
|
+
|
|
118
|
+
Element colours come from your theme, and a brand palette chosen to look right often does not
|
|
119
|
+
clear WCAG: white on a mid-tone brand fill is around 3:1, well under the 4.5:1 body text needs.
|
|
120
|
+
|
|
121
|
+
The SDK therefore **measures your theme at runtime and corrects the foreground when it fails**:
|
|
122
|
+
|
|
123
|
+
1. The surface each element renders on is resolved — a themed token for overlays, otherwise the
|
|
124
|
+
real background behind the element, measured from your page.
|
|
125
|
+
2. Each foreground token is checked against that surface: text, links, warning/error/success,
|
|
126
|
+
input borders and the focus ring, plus on-primary text against `primaryColor`.
|
|
127
|
+
3. Failing tokens are shifted in lightness — same hue, same saturation, away from the background
|
|
128
|
+
— until they pass 4.5:1 (text) or 3:1 (borders, focus ring).
|
|
129
|
+
4. Backgrounds, fills and brand colours are never changed. Compliant themes are untouched.
|
|
130
|
+
|
|
131
|
+
Corrections are applied as inline custom properties on the element host and logged:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Theme colour adjusted for accessibility: defaultTextColor was #FFFFFF (3.02:1 against #E7751E),
|
|
135
|
+
which is below the required 4.5:1. Using #2B2B2B (4.69:1) instead. Set a compliant colour in
|
|
136
|
+
your theme to control this yourself.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**To keep full control of your palette, supply colours that already pass.** Two tokens exist for
|
|
140
|
+
exactly that and are worth setting explicitly:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
customTheme: {
|
|
144
|
+
global: {
|
|
145
|
+
theme: {
|
|
146
|
+
inputBorderColor: '#767C85', // form field boundaries, needs 3:1 on your surface
|
|
147
|
+
focusRingColor: '#1D4ED8' // keyboard focus ring, needs 3:1 on your surface
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The SDK cannot see through a background image: if an element sits on one, surface-relative
|
|
154
|
+
corrections are skipped rather than guessed, and the contrast of that text is yours to get right.
|
|
155
|
+
|
|
156
|
+
### Zoom, reflow and text spacing
|
|
157
|
+
|
|
158
|
+
Elements reflow down to a 320px CSS viewport (400% zoom on a 1280px screen) without horizontal
|
|
159
|
+
scrolling. Labels wrap instead of truncating, so they survive user text-spacing overrides.
|
|
160
|
+
|
|
161
|
+
## Verifying it
|
|
162
|
+
|
|
163
|
+
**Keyboard:** unplug the mouse and complete a purchase. Every control must be reachable, every
|
|
164
|
+
focus ring visible, and focus must never land on `<body>`.
|
|
165
|
+
|
|
166
|
+
**Announcements** without a screen reader — the live regions are plain DOM:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
for (const region of document.querySelectorAll('[data-lce-live-region]')) {
|
|
170
|
+
new MutationObserver(() => console.log('[SR]', region.textContent)).observe(region, {
|
|
171
|
+
childList: true, subtree: true, characterData: true,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Contrast corrections** — inspect the element host:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
document.querySelector('lce-element[product]').getAttribute('style');
|
|
180
|
+
// "--default-text-color: #2b2b2b; --focus-ring-color: #163ca7; …"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Screen readers:** verify with VoiceOver (Safari/macOS), NVDA (Firefox/Windows) or TalkBack
|
|
184
|
+
(Chrome/Android) — the pairings that matter most for a storefront.
|
|
185
|
+
|
|
186
|
+
## See Also
|
|
187
|
+
|
|
188
|
+
- [Theming Guide](./theming.md) — the tokens the contrast guard reads
|
|
189
|
+
- [Best Practices](./best-practices.md)
|
|
190
|
+
- [Browser Support](../reference/browser-support.md)
|
|
@@ -214,7 +214,7 @@ Create a dedicated page for address selection:
|
|
|
214
214
|
<script>
|
|
215
215
|
window.addEventListener('lce:actions.client_ready', async () => {
|
|
216
216
|
await window.LiquidCommerce.elements.injectAddressElement('address');
|
|
217
|
-
});
|
|
217
|
+
}, { once: true });
|
|
218
218
|
|
|
219
219
|
window.addEventListener('lce:actions.address_updated', () => {
|
|
220
220
|
// Redirect to product page after address is set
|
|
@@ -260,7 +260,7 @@ window.addEventListener('lce:actions.client_ready', async () => {
|
|
|
260
260
|
user.savedCoordinates
|
|
261
261
|
);
|
|
262
262
|
}
|
|
263
|
-
});
|
|
263
|
+
}, { once: true });
|
|
264
264
|
```
|
|
265
265
|
|
|
266
266
|
### Location-Based Product Filtering
|
|
@@ -338,7 +338,7 @@ window.addEventListener('lce:actions.client_ready', () => {
|
|
|
338
338
|
// Show address prompt
|
|
339
339
|
showAddressPrompt();
|
|
340
340
|
}
|
|
341
|
-
});
|
|
341
|
+
}, { once: true });
|
|
342
342
|
```
|
|
343
343
|
|
|
344
344
|
### Provide Context
|
|
@@ -6,6 +6,7 @@ The Cart component provides a slide-out drawer for managing shopping cart items,
|
|
|
6
6
|
|
|
7
7
|
The Cart component automatically:
|
|
8
8
|
- Displays cart items with images and details
|
|
9
|
+
- Removes white / near-white backgrounds from item images so products blend into the drawer (automatic, not host-configurable)
|
|
9
10
|
- Groups items by retailer
|
|
10
11
|
- Shows real-time pricing and totals
|
|
11
12
|
- Supports quantity updates
|
|
@@ -240,7 +241,7 @@ Show cart subtotal anywhere on your page:
|
|
|
240
241
|
<script>
|
|
241
242
|
window.addEventListener('lce:actions.client_ready', () => {
|
|
242
243
|
window.LiquidCommerce.elements.ui.cartSubtotal('cart-total');
|
|
243
|
-
});
|
|
244
|
+
}, { once: true });
|
|
244
245
|
</script>
|
|
245
246
|
```
|
|
246
247
|
|
|
@@ -260,7 +261,7 @@ window.addEventListener('lce:actions.client_ready', () => {
|
|
|
260
261
|
window.LiquidCommerce.elements.ui.cartItemsCount('items-count', {
|
|
261
262
|
hideZero: true // Hide when cart is empty
|
|
262
263
|
});
|
|
263
|
-
});
|
|
264
|
+
}, { once: true });
|
|
264
265
|
</script>
|
|
265
266
|
```
|
|
266
267
|
|
|
@@ -342,6 +343,17 @@ window.addEventListener('lce:actions.cart_item_quantity_decrease', (event) => {
|
|
|
342
343
|
});
|
|
343
344
|
```
|
|
344
345
|
|
|
346
|
+
### Engraving Updated
|
|
347
|
+
|
|
348
|
+
Fired when a cart line item's engraving is added or edited from the drawer (see [Engraving](#engraving)):
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
window.addEventListener('lce:actions.cart_item_engraving_updated', (event) => {
|
|
352
|
+
const { cartId, itemId, engravingLines, previousEngravingLines } = event.detail.data;
|
|
353
|
+
console.log(`Engraving updated for ${itemId}:`, engravingLines);
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
345
357
|
### Promo Code Events
|
|
346
358
|
|
|
347
359
|
```javascript
|
|
@@ -546,6 +558,26 @@ Items are automatically grouped by retailer in the cart:
|
|
|
546
558
|
└─────────────────────────────────────┘
|
|
547
559
|
```
|
|
548
560
|
|
|
561
|
+
## Order Minimums
|
|
562
|
+
|
|
563
|
+
Retailers can require a minimum purchase amount before their items can be checked out. The cart enforces this per retailer:
|
|
564
|
+
|
|
565
|
+
- When a retailer's minimum is not met, the cart drawer shows a per-retailer alert reading `+$X needed for order minimum`, where `$X` is the remaining amount required for that retailer.
|
|
566
|
+
- The **Checkout** button is disabled until every retailer's minimum is met. (It is also disabled while the cart is loading/updating or when the cart is empty.)
|
|
567
|
+
|
|
568
|
+
No configuration is required — the minimums are defined by each retailer and enforced automatically.
|
|
569
|
+
|
|
570
|
+
## Engraving
|
|
571
|
+
|
|
572
|
+
For engravable items, shoppers can add or edit engraving directly from a cart line item, without leaving the drawer.
|
|
573
|
+
|
|
574
|
+
- If an engravable item has no engraving yet, the line item shows an **Add personalization** action (with the engraving fee) that opens the engraving form.
|
|
575
|
+
- If the item already has engraving, its engraving is shown on the line item with an **Edit** button that reopens the form.
|
|
576
|
+
|
|
577
|
+
The form respects the item's engraving constraints — `maxLines`, `maxCharsPerLine`, and the per-item `fee`. Saving emits [`lce:actions.cart_item_engraving_updated`](#engraving-updated) with the item's `engravingLines` and `previousEngravingLines`.
|
|
578
|
+
|
|
579
|
+
**Note:** This in-drawer editing is distinct from the `engravingLines` array passed to [`addProduct`](#add-product-to-cart), which pre-fills engraving at the time an item is added.
|
|
580
|
+
|
|
549
581
|
## Checkout Navigation
|
|
550
582
|
|
|
551
583
|
### Checkout Drawer (Default)
|
|
@@ -647,7 +679,7 @@ Pre-load cart on page load for faster access:
|
|
|
647
679
|
window.addEventListener('lce:actions.client_ready', () => {
|
|
648
680
|
// Cart loads automatically, but you can pre-fetch if needed
|
|
649
681
|
window.LiquidCommerce.elements.actions.cart.getDetails();
|
|
650
|
-
});
|
|
682
|
+
}, { once: true });
|
|
651
683
|
```
|
|
652
684
|
|
|
653
685
|
### Provide Visual Feedback
|
package/docs/v1/guides/events.md
CHANGED
|
@@ -16,12 +16,18 @@ window.addEventListener('lce:actions.client_ready', (event) => {
|
|
|
16
16
|
originalEvent,
|
|
17
17
|
actionNamespace,
|
|
18
18
|
eventId,
|
|
19
|
-
timestamp
|
|
19
|
+
timestamp,
|
|
20
|
+
sdkVersion,
|
|
21
|
+
env,
|
|
22
|
+
tenantName,
|
|
23
|
+
tenantCode
|
|
20
24
|
} = metadata;
|
|
21
|
-
});
|
|
25
|
+
}, { once: true });
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
`data` is event-specific. `metadata`
|
|
28
|
+
`data` is event-specific. `metadata` carries the same ten fields on **every** event — including
|
|
29
|
+
`sdkVersion`, `env`, `tenantName` and `tenantCode`, so partner/tenant identification is already
|
|
30
|
+
present and does not need to be re-derived in your analytics layer.
|
|
25
31
|
|
|
26
32
|
## Event Namespaces
|
|
27
33
|
|
|
@@ -43,7 +49,14 @@ window.addEventListener('lce:actions', (event) => {
|
|
|
43
49
|
|
|
44
50
|
### `lce:actions.client_ready`
|
|
45
51
|
|
|
46
|
-
Fires
|
|
52
|
+
Fires when the SDK finishes initializing and is ready for actions.
|
|
53
|
+
|
|
54
|
+
> **Always register this listener with `{ once: true }`.** The specific topic
|
|
55
|
+
> `lce:actions.client_ready` is dispatched **twice** — once synchronously, and once again on a
|
|
56
|
+
> microtask, so that listeners attached immediately after `await Elements(...)` resolves still catch
|
|
57
|
+
> it. Without `{ once: true }`, a handler registered before initialization runs **twice**, which
|
|
58
|
+
> double-injects components and double-counts analytics. (The namespace-level `lce:actions` listener
|
|
59
|
+
> receives it only once.)
|
|
47
60
|
|
|
48
61
|
**Data includes:** `isReady`, `message`, `timestamp`, `version`.
|
|
49
62
|
|
|
@@ -55,7 +68,7 @@ window.addEventListener('lce:actions.client_ready', (event) => {
|
|
|
55
68
|
if (data.isReady) {
|
|
56
69
|
console.log('Elements SDK ready', data.version);
|
|
57
70
|
}
|
|
58
|
-
});
|
|
71
|
+
}, { once: true });
|
|
59
72
|
```
|
|
60
73
|
|
|
61
74
|
## Product Events
|
|
@@ -148,6 +148,8 @@ Products with multiple images display in an interactive carousel:
|
|
|
148
148
|
- Thumbnail preview
|
|
149
149
|
- Lazy loading for performance
|
|
150
150
|
|
|
151
|
+
**Background removal:** Product images (the main detail image and the gallery) automatically have their white/near-white background removed so photos blend into the surrounding page background instead of sitting on a white box. The SDK picks the technique per image based on the host background — blending the white away on light backgrounds, or keying it out to true transparency on dark or transparent backgrounds — and skips SVG artwork, which is already transparent. This is applied automatically and is not currently host-configurable.
|
|
152
|
+
|
|
151
153
|
### Size Selection
|
|
152
154
|
|
|
153
155
|
For products with multiple sizes:
|
|
@@ -206,8 +208,8 @@ For products with multiple retailers:
|
|
|
206
208
|
|
|
207
209
|
**Popup View**
|
|
208
210
|
- "See Delivery Options" button (shows the available fulfillment count, e.g. "See Delivery Options (3)")
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
+
- Opens a plain, scrollable list of the available delivery/retailer options
|
|
212
|
+
- No filtering or search — select a retailer from the list to choose it
|
|
211
213
|
|
|
212
214
|
### Personalization/Engraving
|
|
213
215
|
|
|
@@ -366,6 +368,7 @@ const client = await Elements('YOUR_API_KEY', {
|
|
|
366
368
|
showOnlyMainImage: false, // Show all images or just the main one
|
|
367
369
|
showTitle: true,
|
|
368
370
|
showDescription: true,
|
|
371
|
+
descriptionPosition: 'below', // Description placement: 'above' or 'below'
|
|
369
372
|
showQuantityCounter: true,
|
|
370
373
|
showOffHours: true, // Show when retailer is closed
|
|
371
374
|
quantityCounterStyle: 'outlined', // or 'ghost'
|
|
@@ -376,6 +379,7 @@ const client = await Elements('YOUR_API_KEY', {
|
|
|
376
379
|
addToCartButtonShowTotalPrice: true,
|
|
377
380
|
buyNowButtonText: 'Buy Now',
|
|
378
381
|
preSaleButtonText: 'Pre-Order',
|
|
382
|
+
prioritizeEngraving: false, // Show engraving option before add-to-cart
|
|
379
383
|
noAvailabilityText: 'Not available in your area'
|
|
380
384
|
}
|
|
381
385
|
}
|
|
@@ -260,18 +260,19 @@ Each product card shows:
|
|
|
260
260
|
- Product name
|
|
261
261
|
- Brand
|
|
262
262
|
- Price (or price range for multiple sizes)
|
|
263
|
-
- Rating (if available)
|
|
264
263
|
- Clickable image/card linking to the product detail page (when `productUrl` is configured)
|
|
265
264
|
- "Add to Cart" button (optional)
|
|
266
265
|
- Availability indicator
|
|
267
266
|
|
|
267
|
+
Product card images automatically have their white/near-white backgrounds removed so the product blends into the surrounding card and page background. This treatment is applied at render time and is not host-configurable.
|
|
268
|
+
|
|
268
269
|
### Card Interaction
|
|
269
270
|
|
|
270
271
|
**Click on card:** Navigate to product detail page (if `productUrl` configured — see [Product URL Map](#product-url-map) for partner-owned PDP URLs that aren't derivable from a token).
|
|
271
272
|
|
|
272
273
|
**Quick Add:** Add product to cart directly from list view (if enabled)
|
|
273
274
|
|
|
274
|
-
**Click on image:**
|
|
275
|
+
**Click on image:** Navigate to the configured `productUrl` (only when `productUrl` is set — otherwise the image is not a link)
|
|
275
276
|
|
|
276
277
|
## Customization
|
|
277
278
|
|
|
@@ -514,10 +515,8 @@ The product list component includes:
|
|
|
514
515
|
### Optimization Features
|
|
515
516
|
|
|
516
517
|
- **Image lazy loading**: Images load as they enter viewport
|
|
517
|
-
- **
|
|
518
|
+
- **Progressive loading**: Products load in batches via an `IntersectionObserver` sentinel as you scroll — each page is appended to the grid and loaded cards remain in the DOM
|
|
518
519
|
- **Debounced search**: Reduces API calls during typing
|
|
519
|
-
- **Filter caching**: Caches filter results
|
|
520
|
-
- **Progressive loading**: Loads in batches
|
|
521
520
|
|
|
522
521
|
### Large Catalogs
|
|
523
522
|
|
|
@@ -27,7 +27,12 @@ const client = await Elements('YOUR_API_KEY', {
|
|
|
27
27
|
errorColor: '#dc3545',
|
|
28
28
|
warningColor: '#ffc107',
|
|
29
29
|
successColor: '#28a745',
|
|
30
|
-
drawerBackgroundColor: '#ffffff'
|
|
30
|
+
drawerBackgroundColor: '#ffffff',
|
|
31
|
+
|
|
32
|
+
// Optional. Omit either one and the runtime contrast guard picks a
|
|
33
|
+
// WCAG-compliant value for you; set them to keep these details on-brand.
|
|
34
|
+
inputBorderColor: '#d4d4d8',
|
|
35
|
+
focusRingColor: '#007bff'
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
}
|
|
@@ -53,6 +58,12 @@ customTheme: {
|
|
|
53
58
|
}
|
|
54
59
|
```
|
|
55
60
|
|
|
61
|
+
**Constraints:**
|
|
62
|
+
|
|
63
|
+
- Only Google Fonts are supported. Each font's `name` must be a valid Google Font name (for example, `'Poppins'` or `'Inter'`).
|
|
64
|
+
- `Poppins` is always loaded as the default font regardless of your configuration, so it remains available even if you do not specify it.
|
|
65
|
+
- Font loading requires network access to `fonts.googleapis.com` and `fonts.gstatic.com`. If your environment enforces a Content Security Policy or routes traffic through a proxy, allow these hosts so fonts can load.
|
|
66
|
+
|
|
56
67
|
### Border Radius
|
|
57
68
|
|
|
58
69
|
```javascript
|
|
@@ -77,12 +88,16 @@ customTheme: {
|
|
|
77
88
|
personalizationCardStyle: 'outlined', // or 'filled'
|
|
78
89
|
allowPromoCodes: true,
|
|
79
90
|
inputFieldStyle: 'outlined', // or 'filled'
|
|
91
|
+
enableOrderedProductSizes: true, // enable custom ordering of the size selectors
|
|
92
|
+
orderedProductSizes: ['750ml', '1L', '1.75L'], // order applied to product and product-list size selectors
|
|
80
93
|
poweredByMode: 'light' // or 'dark' (note: showPoweredBy is controlled server-side and cannot be overridden via customTheme)
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
97
|
```
|
|
85
98
|
|
|
99
|
+
`enableOrderedProductSizes` and `orderedProductSizes` control the ordering of the size selectors rendered by the product and product-list components. When `enableOrderedProductSizes` is `true`, size options are sorted to match the sequence in the `orderedProductSizes` string array. Updating either key triggers a targeted rerender of the affected size selectors.
|
|
100
|
+
|
|
86
101
|
## Component Themes
|
|
87
102
|
|
|
88
103
|
### Product Component
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Analytics & Google Tag Manager
|
|
2
|
+
|
|
3
|
+
The SDK ships with a built-in Google Tag Manager (GTM) / Google Analytics 4 (GA4) integration. It automatically pushes GA4 ecommerce events for the interactions your shoppers perform in Elements components — product views, cart changes, checkout steps, purchases, and more.
|
|
4
|
+
|
|
5
|
+
This runs entirely inside the SDK. In most cases you do not need to configure or code anything.
|
|
6
|
+
|
|
7
|
+
## What You Need To Do
|
|
8
|
+
|
|
9
|
+
**Typically nothing.** The integration is automatic in production and staging. The SDK receives its GTM container configuration from the LiquidCommerce platform (per partner) and initializes itself after the DOM is ready.
|
|
10
|
+
|
|
11
|
+
It coexists with a host site's own GTM — see [Coexisting With Your Own GTM](#coexisting-with-your-own-gtm) below.
|
|
12
|
+
|
|
13
|
+
## Supported Environments
|
|
14
|
+
|
|
15
|
+
GTM initialization and every event push are enabled in production and staging. When the SDK is running in development or Builder mode, no container is loaded and no events are sent.
|
|
16
|
+
|
|
17
|
+
Events are suppressed when any of the following is true:
|
|
18
|
+
|
|
19
|
+
- `window` is undefined (server-side rendering).
|
|
20
|
+
- The SDK is running in Builder mode.
|
|
21
|
+
- The environment is `development`.
|
|
22
|
+
|
|
23
|
+
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
|
+
|
|
25
|
+
## How It Works
|
|
26
|
+
|
|
27
|
+
### Container injection
|
|
28
|
+
|
|
29
|
+
When enabled, the SDK injects the GTM container script:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
https://www.googletagmanager.com/gtm.js?id=<containerId>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The script is appended to `<head>` with `async` and `crossorigin="anonymous"`, and the SDK waits for GTM to initialize before flushing events.
|
|
36
|
+
|
|
37
|
+
### dataLayer and gtag
|
|
38
|
+
|
|
39
|
+
The SDK initializes the standard GTM globals **without clobbering an existing setup**:
|
|
40
|
+
|
|
41
|
+
- `window.dataLayer` is created only if it does not already exist.
|
|
42
|
+
- `window.gtag` is defined only if it is not already present.
|
|
43
|
+
|
|
44
|
+
Events are delivered using Google's recommended `window.dataLayer.push()` pattern.
|
|
45
|
+
|
|
46
|
+
### Event queueing
|
|
47
|
+
|
|
48
|
+
Events fired before GTM finishes initializing are queued (up to 100, oldest dropped first) and flushed once the container is ready. Queued events older than 30 seconds at flush time are discarded.
|
|
49
|
+
|
|
50
|
+
## Coexisting With Your Own GTM
|
|
51
|
+
|
|
52
|
+
The SDK detects an already-present partner/host GTM container rather than replacing it. When a host GTM install (an existing `gtag` function or an existing `googletagmanager.com/gtm.js` script tag) is detected, the SDK integrates with it.
|
|
53
|
+
|
|
54
|
+
When both the LiquidCommerce container and a partner container are initialized, events are sent to both using GA4's `send_to` targeting (a dual-container send). Only containers that are actually initialized are added to `send_to`, so events are never routed to a container that failed to load.
|
|
55
|
+
|
|
56
|
+
If the LiquidCommerce container can't be loaded, the SDK falls back — in order — to the partner container, any initialized GTM container on the page, or the basic `dataLayer` — so event tracking degrades gracefully instead of failing.
|
|
57
|
+
|
|
58
|
+
## Source Tracking (`tenant_*`)
|
|
59
|
+
|
|
60
|
+
Every event automatically carries source-tracking fields identifying the tenant and the SDK build:
|
|
61
|
+
|
|
62
|
+
| Field | Value |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `tenant_name` | Partner name |
|
|
65
|
+
| `tenant_code` | Partner code |
|
|
66
|
+
| `tenant_env` | SDK environment |
|
|
67
|
+
| `tenant_source` | SDK package description and version |
|
|
68
|
+
|
|
69
|
+
## Events
|
|
70
|
+
|
|
71
|
+
All events are GA4-shaped and follow the standard ecommerce schema (`items[]`, `value`, `currency` — the SDK uses `USD`, `coupon`, etc.).
|
|
72
|
+
|
|
73
|
+
### Standard GA4 ecommerce events
|
|
74
|
+
|
|
75
|
+
| Event | Fired when |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `view_item` | A shopper views a product |
|
|
78
|
+
| `view_item_list` | A shopper views a list of products |
|
|
79
|
+
| `select_item` | A shopper selects a product from a list |
|
|
80
|
+
| `add_to_cart` | A shopper adds an item to the cart |
|
|
81
|
+
| `view_cart` | A shopper views the cart |
|
|
82
|
+
| `remove_from_cart` | A shopper removes an item from the cart |
|
|
83
|
+
| `begin_checkout` | A shopper begins checkout |
|
|
84
|
+
| `add_shipping_info` | Shipping info is added during checkout |
|
|
85
|
+
| `add_payment_info` | Payment info is added during checkout |
|
|
86
|
+
| `purchase` | A purchase completes |
|
|
87
|
+
|
|
88
|
+
### Custom events
|
|
89
|
+
|
|
90
|
+
| Event | Fired when |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `promo_code_attempt` | A promo code is submitted |
|
|
93
|
+
| `promo_code_applied` | A promo code is successfully applied |
|
|
94
|
+
| `promo_code_failed` | A promo code fails to apply |
|
|
95
|
+
| `gift_card_attempt` | A gift card is submitted |
|
|
96
|
+
| `gift_card_applied` | A gift card is successfully applied |
|
|
97
|
+
| `gift_card_failed` | A gift card fails to apply |
|
|
98
|
+
| `address_updated` | An address is successfully updated |
|
|
99
|
+
| `address_failed` | An address operation fails |
|
|
100
|
+
| `product_no_availability` | A product has no availability for any size/fulfillment |
|
|
101
|
+
| `product_size_no_availability` | A selected size has no availability |
|
|
102
|
+
| `product_fulfillment_no_availability` | A selected fulfillment type has no availability |
|
|
103
|
+
|
|
104
|
+
## Related Docs
|
|
105
|
+
|
|
106
|
+
- [Events Guide](../guides/events.md)
|
|
107
|
+
- [Client API](../api/client.md)
|
|
108
|
+
- [Troubleshooting](./troubleshooting.md)
|
|
@@ -25,7 +25,9 @@ In non-browser environments, initialization is skipped. If you call the SDK on t
|
|
|
25
25
|
it returns `null` and logs a warning instead of throwing.
|
|
26
26
|
|
|
27
27
|
Call `Elements`, `ElementsCheckout`, or `ElementsBuilder` from client-only code (e.g., after mount)
|
|
28
|
-
to render components.
|
|
28
|
+
to render components. `Elements` and `ElementsBuilder` resolve to no-op stubs from the main SSR
|
|
29
|
+
entry; `ElementsCheckout` ships in its own tree-shaken entry with a separate stub, so it is safe to
|
|
30
|
+
import even if you never use the other two.
|
|
29
31
|
|
|
30
32
|
## Polyfills (Legacy Browsers)
|
|
31
33
|
|
|
@@ -29,7 +29,7 @@ try {
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
> A well-formed entry with an invalid product identifier does **not** throw — it renders an error view inside the component and sets an error in the store. `try/catch` here only catches
|
|
32
|
+
> A well-formed entry with an invalid product identifier does **not** throw — it renders an error view inside the component and sets an error in the store. `try/catch` here only catches malformed input: a missing or non-string `identifier`, a `fulfillmentType` other than `shipping`/`onDemand`, or a `quantity` below 1. An **empty array logs a warning and resolves** — it does not throw and emits no event.
|
|
33
33
|
|
|
34
34
|
## Error Isolation
|
|
35
35
|
|
|
@@ -68,6 +68,14 @@ window.addEventListener('lce:actions.checkout_submit_failed', (event) => {
|
|
|
68
68
|
|
|
69
69
|
When a component fails to load, the SDK renders an error view inside the container and logs details to the console.
|
|
70
70
|
|
|
71
|
+
## Global Error Interception
|
|
72
|
+
|
|
73
|
+
On load, the SDK installs its own global handlers by reassigning `window.onerror` and `window.onunhandledrejection`. Each incoming error or rejection is classified by an `isSDKError` heuristic that inspects the error's message, stack, and source — matching a script `src` ending in `/elements.js`, known SDK class and directory patterns, and an `isSdk` flag on `SDKError` instances.
|
|
74
|
+
|
|
75
|
+
When an error is classified as SDK-originated, the handler logs it to the console and returns `true` (for `onerror`) or calls `event.preventDefault()` (for `onunhandledrejection`), so the error is swallowed and never reaches your app. Errors that are not classified as SDK-originated are chained to any handler that was already installed before the SDK loaded.
|
|
76
|
+
|
|
77
|
+
> **Implications:** SDK-originated errors are intentionally suppressed and will **not** surface in your host error monitoring (e.g. Sentry). Conversely, the heuristic is pattern-based and can occasionally match a non-SDK error, causing it to be swallowed too. If you rely on catching failures, use the `*_failed` events and `try/catch` described above rather than global error monitoring.
|
|
78
|
+
|
|
71
79
|
## Related Docs
|
|
72
80
|
|
|
73
81
|
- [Events Guide](../guides/events.md)
|