@liquidcommerce/elements-sdk 2.6.0-beta.95 → 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.
- package/README.md +20 -4
- package/dist/index.checkout.esm.js +7970 -7403
- package/dist/index.esm.js +12979 -12066
- package/dist/types/core/a11y/glyph-button.d.ts +8 -0
- package/dist/types/core/a11y/index.d.ts +2 -0
- package/dist/types/core/a11y/single-select.d.ts +16 -0
- package/dist/types/modules/address/address-display.component.d.ts +3 -1
- 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-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-list/components/card-components/product-quantity-selector.d.ts +2 -1
- package/dist/types/modules/theme-provider/services/contrast-guard.service.d.ts +19 -0
- package/dist/types/modules/ui-components/input/input.component.d.ts +1 -0
- package/dist/types/modules/ui-components/lce-element/lce-element.component.d.ts +9 -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/client.md +4 -3
- package/docs/v1/api/configuration.md +13 -0
- package/docs/v1/api/ui-helpers.md +2 -2
- package/docs/v1/getting-started/concepts.md +24 -7
- package/docs/v1/getting-started/installation.md +2 -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 +3 -3
- package/docs/v1/guides/events.md +18 -5
- package/docs/v1/guides/theming.md +6 -1
- package/docs/v1/integration/vanilla-js.md +1 -1
- package/docs/v1/reference/error-handling.md +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
@@ -241,7 +241,7 @@ Show cart subtotal anywhere on your page:
|
|
|
241
241
|
<script>
|
|
242
242
|
window.addEventListener('lce:actions.client_ready', () => {
|
|
243
243
|
window.LiquidCommerce.elements.ui.cartSubtotal('cart-total');
|
|
244
|
-
});
|
|
244
|
+
}, { once: true });
|
|
245
245
|
</script>
|
|
246
246
|
```
|
|
247
247
|
|
|
@@ -261,7 +261,7 @@ window.addEventListener('lce:actions.client_ready', () => {
|
|
|
261
261
|
window.LiquidCommerce.elements.ui.cartItemsCount('items-count', {
|
|
262
262
|
hideZero: true // Hide when cart is empty
|
|
263
263
|
});
|
|
264
|
-
});
|
|
264
|
+
}, { once: true });
|
|
265
265
|
</script>
|
|
266
266
|
```
|
|
267
267
|
|
|
@@ -679,7 +679,7 @@ Pre-load cart on page load for faster access:
|
|
|
679
679
|
window.addEventListener('lce:actions.client_ready', () => {
|
|
680
680
|
// Cart loads automatically, but you can pre-fetch if needed
|
|
681
681
|
window.LiquidCommerce.elements.actions.cart.getDetails();
|
|
682
|
-
});
|
|
682
|
+
}, { once: true });
|
|
683
683
|
```
|
|
684
684
|
|
|
685
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
|
|
@@ -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
|
}
|
|
@@ -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
|
|
package/package.json
CHANGED