@occluze/tether 0.6.6 → 0.6.8
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 +3 -3
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-C3NP6HFB.js +1 -0
- package/dist/esm/chunks/account-pages-WLK374FO.js +1 -0
- package/dist/esm/chunks/analytics-G3SAWMNL.js +1 -0
- package/dist/esm/chunks/chunk-27PJJZGC.js +1 -0
- package/dist/esm/chunks/chunk-2FSXS55U.js +1 -0
- package/dist/esm/chunks/chunk-FCSWQDVV.js +1 -0
- package/dist/esm/chunks/chunk-FOMSMIAM.js +1 -0
- package/dist/esm/chunks/chunk-GZJTCLLW.js +1 -0
- package/dist/esm/chunks/chunk-HEQILZVR.js +1 -0
- package/dist/esm/chunks/chunk-JOOEXYF3.js +4 -0
- package/dist/esm/chunks/chunk-MPTQYLSM.js +1 -0
- package/dist/esm/chunks/chunk-NEHZ37TU.js +3 -0
- package/dist/esm/chunks/chunk-V74KGEJF.js +1 -0
- package/dist/esm/chunks/chunk-VT4CDUJJ.js +1 -0
- package/dist/esm/chunks/chunk-YJLWX2HK.js +1 -0
- package/dist/esm/chunks/diagnose-6VVGZNTY.js +14 -0
- package/dist/esm/chunks/extras-GJVRDFM3.js +1 -0
- package/dist/esm/chunks/markets-ui-UNB4PTA7.js +1 -0
- package/dist/esm/chunks/search-OU75RNQL.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +2 -1
- package/dist/presets/barba.esm.js +56 -36
- package/dist/tether.account-pages.js +776 -432
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +655 -321
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +318 -191
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +334 -243
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +78 -33
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +3171 -1998
- package/dist/tether.markets.js +231 -126
- package/dist/tether.markets.min.js +1 -1
- package/dist/tether.min.js +0 -0
- package/dist/tether.min.js.map +4 -4
- package/dist/tether.search.js +221 -126
- package/dist/tether.search.min.js +1 -1
- package/package.json +23 -4
- package/src/account/addresses-view.js +92 -0
- package/src/account/browser-session.js +359 -0
- package/src/account/markup.js +48 -0
- package/src/account/operations.js +180 -0
- package/src/account/order-lines.js +96 -0
- package/src/account/order-list.js +91 -0
- package/src/account/order-page.js +192 -0
- package/src/account/page-events.js +288 -0
- package/src/account/server-client.js +172 -0
- package/src/account/shared.js +111 -0
- package/src/account/shopify-auth.js +150 -0
- package/src/account/subscriptions-view.js +158 -0
- package/src/account-api.js +118 -0
- package/src/account-links.js +35 -0
- package/src/account-pages.js +68 -284
- package/src/account.js +281 -317
- package/src/amounts.js +36 -0
- package/src/analytics-shopify.js +241 -0
- package/src/analytics.js +304 -184
- package/src/api-version.js +75 -25
- package/src/astro.js +233 -68
- package/src/cart/checkout.js +97 -0
- package/src/cart/drawer.js +138 -0
- package/src/cart/mutations.js +234 -0
- package/src/cart/render.js +294 -0
- package/src/cart/store.js +326 -0
- package/src/cart.js +322 -481
- package/src/client.js +214 -78
- package/src/collection.js +126 -57
- package/src/config.js +323 -59
- package/src/controls/cart-controls.js +155 -0
- package/src/controls/discount.js +96 -0
- package/src/controls/extras.js +63 -0
- package/src/controls/navigation.js +83 -0
- package/src/diagnose-markup.js +333 -0
- package/src/diagnose.js +251 -202
- package/src/dom.js +219 -220
- package/src/extras.js +119 -37
- package/src/fragments.js +115 -22
- package/src/images.js +104 -0
- package/src/index.js +245 -285
- package/src/initial.js +78 -66
- package/src/lazy-iife.js +92 -22
- package/src/lazy.js +25 -4
- package/src/loader.js +263 -124
- package/src/market-codes.js +35 -0
- package/src/markets-ui.js +169 -55
- package/src/markets.js +286 -85
- package/src/markup.js +64 -0
- package/src/mounting.js +214 -91
- package/src/page-market.js +116 -0
- package/src/parts/account-pages.js +4 -1
- package/src/parts/account.js +4 -1
- package/src/parts/analytics.js +4 -1
- package/src/parts/diagnose.js +4 -1
- package/src/parts/extras.js +4 -1
- package/src/parts/markets.js +4 -1
- package/src/parts/search.js +4 -1
- package/src/presets/barba.js +120 -30
- package/src/product-events.js +130 -0
- package/src/product-render.js +257 -0
- package/src/product-state.js +318 -0
- package/src/product.js +233 -459
- package/src/repeaters.js +173 -0
- package/src/search.js +277 -107
- package/src/selling-plans.js +107 -47
- package/src/server.js +168 -205
- package/src/shopify-values.js +89 -0
- package/src/status.js +99 -39
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +199 -0
- package/src/storefront/json-ld.js +167 -0
- package/src/variant-render.js +165 -0
- package/types/astro.d.ts +13 -2
- package/types/index.d.ts +178 -27
- package/types/server.d.ts +89 -17
- package/dist/esm/chunks/account-QUBHQG3C.js +0 -1
- package/dist/esm/chunks/account-pages-VWDHF6WA.js +0 -1
- package/dist/esm/chunks/analytics-SLATWPLC.js +0 -1
- package/dist/esm/chunks/chunk-64TUD3QR.js +0 -1
- package/dist/esm/chunks/chunk-AJLPU2JC.js +0 -4
- package/dist/esm/chunks/chunk-FIOZPPC5.js +0 -1
- package/dist/esm/chunks/chunk-K2YEQAIH.js +0 -1
- package/dist/esm/chunks/chunk-L2TRRKYV.js +0 -1
- package/dist/esm/chunks/chunk-RREHGH4A.js +0 -1
- package/dist/esm/chunks/chunk-VC46IEJQ.js +0 -1
- package/dist/esm/chunks/chunk-VCPI7MJ2.js +0 -1
- package/dist/esm/chunks/chunk-VPVJPNB4.js +0 -1
- package/dist/esm/chunks/chunk-ZIUIWEON.js +0 -1
- package/dist/esm/chunks/diagnose-VXQBSD3A.js +0 -14
- package/dist/esm/chunks/extras-R4X34VQL.js +0 -1
- package/dist/esm/chunks/markets-ui-F3R5T5EL.js +0 -1
- package/dist/esm/chunks/search-NILMGYXG.js +0 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cart on this page: what Shopify last said about it (`CartStatus`), the quantities quick clicks asked for, the
|
|
3
|
+
* customer it belongs to, and the one queue every change goes through. A change of what the page shows goes through
|
|
4
|
+
* here, so the document (`renderCart`) and the listeners (`tether:cart-updated`) always see the store's cart.
|
|
5
|
+
*/
|
|
6
|
+
import { cfg } from '../config.js';
|
|
7
|
+
import { emit } from '../dom.js';
|
|
8
|
+
import { isOpen } from './drawer.js';
|
|
9
|
+
import { renderCart, quantityOf, showSavedCount } from './render.js';
|
|
10
|
+
/** @import { CartWarning } from '../config.js' */
|
|
11
|
+
/** @import { Image } from '../fragments.js' */
|
|
12
|
+
/** @import { Money } from '../shopify-values.js' */
|
|
13
|
+
|
|
14
|
+
/** @typedef {{ key: string, value: string }} Attribute */
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} CartVariant A line's `merchandise`: Shopify's `Merchandise` union has only `ProductVariant`.
|
|
17
|
+
* @property {string} id
|
|
18
|
+
* @property {string} title
|
|
19
|
+
* @property {string | null} sku
|
|
20
|
+
* @property {boolean} availableForSale
|
|
21
|
+
* @property {boolean} requiresShipping
|
|
22
|
+
* @property {number | null} [quantityAvailable] Absent when the store's token can't read inventory.
|
|
23
|
+
* @property {Image | null} image
|
|
24
|
+
* @property {Money} price
|
|
25
|
+
* @property {Money | null} compareAtPrice
|
|
26
|
+
* @property {{ name: string, value: string }[]} selectedOptions
|
|
27
|
+
* @property {{ id: string, title: string, handle: string, vendor: string, featuredImage: Image | null }} product
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} LinePlanAllocation
|
|
31
|
+
* @property {{ id: string, name: string, description: string | null, recurringDeliveries: boolean,
|
|
32
|
+
* options: { name: string, value: string }[] }} sellingPlan
|
|
33
|
+
* @property {Money} checkoutChargeAmount
|
|
34
|
+
* @property {{ price: Money, compareAtPrice: Money, perDeliveryPrice: Money }[]} priceAdjustments
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} ShopifyCartLine
|
|
38
|
+
* @property {string} id
|
|
39
|
+
* @property {number} quantity
|
|
40
|
+
* @property {Attribute[]} attributes
|
|
41
|
+
* @property {{ totalAmount: Money, subtotalAmount: Money, amountPerQuantity: Money,
|
|
42
|
+
* compareAtAmountPerQuantity: Money | null }} cost
|
|
43
|
+
* @property {{ discountedAmount: Money }[]} discountAllocations
|
|
44
|
+
* @property {LinePlanAllocation | null} sellingPlanAllocation
|
|
45
|
+
* @property {CartVariant} merchandise
|
|
46
|
+
* @property {{ id: string, quantity: number, merchandise: { id: string, title: string,
|
|
47
|
+
* product: { title: string, handle: string } } }[]} [lineComponents] Only on a bundle (`ComponentizableCartLine`).
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {object} ShopifyCart The fields of `fragment TetherCart`.
|
|
51
|
+
* @property {string} id
|
|
52
|
+
* @property {string} checkoutUrl
|
|
53
|
+
* @property {number} totalQuantity
|
|
54
|
+
* @property {string | null} note
|
|
55
|
+
* @property {string} createdAt
|
|
56
|
+
* @property {string} updatedAt
|
|
57
|
+
* @property {Attribute[]} attributes
|
|
58
|
+
* @property {{ countryCode: string | null, email: string | null }} buyerIdentity
|
|
59
|
+
* @property {{ code: string, applicable: boolean }[]} discountCodes
|
|
60
|
+
* @property {{ subtotalAmount: Money, totalAmount: Money, checkoutChargeAmount: Money }} cost
|
|
61
|
+
* @property {{ nodes: ShopifyCartLine[] }} lines
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @typedef {{ kind: 'unknown' }
|
|
66
|
+
* | { kind: 'empty' }
|
|
67
|
+
* | { kind: 'ready', cart: ShopifyCart }
|
|
68
|
+
* | { kind: 'error', error: unknown, cart: ShopifyCart | null }} CartStatus
|
|
69
|
+
* `unknown` until the saved cart has loaded (the page's own markup shows); `empty` without a cart (none saved, or
|
|
70
|
+
* Shopify no longer has it); `error` after a failed load or change, with the cart as Shopify last sent it.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/** @typedef {{ quantity: number }} PendingQuantity What one quick +/- click asked for; a newer click replaces it. */
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @typedef {object} CustomerLink What the account part sets while a customer is logged in.
|
|
77
|
+
* @property {string | null} accessToken The Customer Account API token: carts created now belong to the customer.
|
|
78
|
+
* @property {(() => Promise<string | null>) | null} beforeCheckout The account's own checkout URL; `null` from it, or
|
|
79
|
+
* no function, uses the cart's.
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {object} CartMeta The second argument of a `subscribe` listener.
|
|
84
|
+
* @property {boolean} open
|
|
85
|
+
* @property {number} count
|
|
86
|
+
* @property {boolean} loaded
|
|
87
|
+
* @property {readonly CartWarning[]} warnings
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
/** @type {CartStatus} */
|
|
91
|
+
let status = { kind: 'unknown' };
|
|
92
|
+
|
|
93
|
+
/** @type {readonly CartWarning[]} Shopify's notes on the last change. */
|
|
94
|
+
let warnings = [];
|
|
95
|
+
|
|
96
|
+
/** @type {Map<string, PendingQuantity>} Line id → what quick +/- clicks asked for, until Shopify has answered. */
|
|
97
|
+
export const pendingQuantities = new Map();
|
|
98
|
+
|
|
99
|
+
/** @type {CustomerLink} */
|
|
100
|
+
export const customer = { accessToken: null, beforeCheckout: null };
|
|
101
|
+
|
|
102
|
+
/** @returns {CartStatus} */
|
|
103
|
+
export const cartStatus = () => status;
|
|
104
|
+
|
|
105
|
+
/** @returns {ShopifyCart | null} The cart as Shopify last sent it. */
|
|
106
|
+
export function currentCart() {
|
|
107
|
+
switch (status.kind) {
|
|
108
|
+
case 'ready':
|
|
109
|
+
case 'error':
|
|
110
|
+
return status.cart;
|
|
111
|
+
case 'unknown':
|
|
112
|
+
case 'empty':
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @returns {readonly CartWarning[]} */
|
|
118
|
+
export const currentWarnings = () => warnings;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {ShopifyCart | null} cart
|
|
122
|
+
* @returns {CartStatus}
|
|
123
|
+
*/
|
|
124
|
+
const statusOf = (cart) => (cart ? { kind: 'ready', cart } : { kind: 'empty' });
|
|
125
|
+
|
|
126
|
+
const seenKey = () => `${cfg.storageKey}:seen`;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* localStorage throws in private mode and where the visitor blocks site data: the cart then lasts for this page.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} key
|
|
132
|
+
* @returns {string | null}
|
|
133
|
+
*/
|
|
134
|
+
function readStorageIfAllowed(key) {
|
|
135
|
+
try {
|
|
136
|
+
return localStorage.getItem(key);
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Like `readStorageIfAllowed`: without storage, nothing is remembered.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} key
|
|
146
|
+
* @param {string | null} value `null` removes the key.
|
|
147
|
+
*/
|
|
148
|
+
function writeStorageIfAllowed(key, value) {
|
|
149
|
+
try {
|
|
150
|
+
if (value === null) localStorage.removeItem(key);
|
|
151
|
+
else localStorage.setItem(key, value);
|
|
152
|
+
} catch {
|
|
153
|
+
// Private mode or blocked storage: see readStorageIfAllowed.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @returns {string | null} The id of the visitor's cart, kept across pages. */
|
|
158
|
+
export const savedCartId = () => readStorageIfAllowed(cfg.storageKey) || null;
|
|
159
|
+
|
|
160
|
+
/** Forgets the saved cart, which Shopify no longer has. */
|
|
161
|
+
export function forgetSavedCart() {
|
|
162
|
+
writeStorageIfAllowed(cfg.storageKey, null);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Keeps a cart Shopify sent without showing it, for an answer a newer change will replace.
|
|
167
|
+
*
|
|
168
|
+
* @param {ShopifyCart | null} cart
|
|
169
|
+
*/
|
|
170
|
+
export function keepQuietly(cart) {
|
|
171
|
+
status = statusOf(cart);
|
|
172
|
+
if (cart) writeStorageIfAllowed(cfg.storageKey, cart.id);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Renders the document with the store's cart. */
|
|
176
|
+
export function render() {
|
|
177
|
+
renderCart({
|
|
178
|
+
cart: currentCart(),
|
|
179
|
+
warnings,
|
|
180
|
+
pending: pendingQuantities,
|
|
181
|
+
failed: status.kind === 'error',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {string} reason The `reason` of `tether:cart-updated`.
|
|
187
|
+
*/
|
|
188
|
+
export function announce(reason) {
|
|
189
|
+
render();
|
|
190
|
+
emit('cart-updated', { cart: currentCart(), reason });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Keeps a cart Shopify sent after a change, shows it, and tells this page and the visitor's other tabs.
|
|
195
|
+
*
|
|
196
|
+
* @param {ShopifyCart | null} cart
|
|
197
|
+
* @param {string} reason
|
|
198
|
+
* @returns {ShopifyCart | null} `cart`.
|
|
199
|
+
*/
|
|
200
|
+
export function commit(cart, reason) {
|
|
201
|
+
keepQuietly(cart);
|
|
202
|
+
// Other tabs reload the cart on this storage event; the next page shows the count before its cart has loaded.
|
|
203
|
+
writeStorageIfAllowed(seenKey(), `${Date.now()}|${cart?.totalQuantity ?? 0}`);
|
|
204
|
+
announce(reason);
|
|
205
|
+
return cart;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The outcome of loading the saved cart: the cart, `null` when there is none, or the failure.
|
|
210
|
+
*
|
|
211
|
+
* @param {{ kind: 'loaded', cart: ShopifyCart | null } | { kind: 'failed', error: unknown }} outcome
|
|
212
|
+
*/
|
|
213
|
+
export function settleLoad(outcome) {
|
|
214
|
+
status = outcome.kind === 'loaded' ? statusOf(outcome.cart) : { kind: 'error', error: outcome.error, cart: null };
|
|
215
|
+
announce('load');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Sets or clears the failure without rendering, for `Tether.cart.lastError`, which the public types declare writable.
|
|
220
|
+
*
|
|
221
|
+
* @param {unknown} error `null` or `undefined` clears it.
|
|
222
|
+
*/
|
|
223
|
+
export function setFailure(error) {
|
|
224
|
+
const cart = currentCart();
|
|
225
|
+
status = error == null ? statusOf(cart) : { kind: 'error', error, cart };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Until the saved cart has loaded, the counts show the count it had on the last page instead of the page's 0. */
|
|
229
|
+
export function showLastKnownCount() {
|
|
230
|
+
if (status.kind !== 'unknown') return;
|
|
231
|
+
const count = readStorageIfAllowed(seenKey())?.split('|')[1];
|
|
232
|
+
if (count) showSavedCount(count);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Sets Shopify's notes on the last change: each message once, in the site's words (`warningText`).
|
|
237
|
+
*
|
|
238
|
+
* @param {readonly CartWarning[]} notes
|
|
239
|
+
*/
|
|
240
|
+
export function setWarnings(notes) {
|
|
241
|
+
warnings = notes
|
|
242
|
+
.filter((note, index) => notes.findIndex((other) => other.message === note.message) === index)
|
|
243
|
+
.map((note) => ({ ...note, message: cfg.warningText?.(note) || note.message }));
|
|
244
|
+
if (warnings.length) emit('cart-warning', { warnings });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** The queue only waits for a task: its result, or failure, reaches the caller through the promise `run` returns. */
|
|
248
|
+
const waitOnly = () => undefined;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @returns {<T>(task: () => Promise<T>) => Promise<T>} A runner that starts each task once the one before has
|
|
252
|
+
* settled, whether it succeeded or failed.
|
|
253
|
+
*/
|
|
254
|
+
export function serial() {
|
|
255
|
+
/** @type {Promise<unknown>} */
|
|
256
|
+
let previous = Promise.resolve();
|
|
257
|
+
return (task) => {
|
|
258
|
+
const run = previous.then(task);
|
|
259
|
+
previous = run.then(waitOnly, waitOnly);
|
|
260
|
+
return run;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const runInOrder = serial();
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Runs a cart change after the ones before it, so quick clicks never race. A failure sets the `error` status (shown by
|
|
268
|
+
* `tether-show="cart-error"` until the next change succeeds), emits `tether:cart-error` and rejects.
|
|
269
|
+
*
|
|
270
|
+
* @template T
|
|
271
|
+
* @param {() => Promise<T>} task
|
|
272
|
+
* @returns {Promise<T>}
|
|
273
|
+
*/
|
|
274
|
+
export function enqueue(task) {
|
|
275
|
+
return runInOrder(task).then(
|
|
276
|
+
(value) => {
|
|
277
|
+
if (status.kind === 'error') {
|
|
278
|
+
status = statusOf(status.cart);
|
|
279
|
+
render();
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
},
|
|
283
|
+
(/** @type {unknown} */ error) => {
|
|
284
|
+
status = { kind: 'error', error, cart: currentCart() };
|
|
285
|
+
render();
|
|
286
|
+
emit('cart-error', { error });
|
|
287
|
+
throw error;
|
|
288
|
+
},
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @param {ShopifyCartLine} line
|
|
294
|
+
* @returns {number} The quantity to show for `line`.
|
|
295
|
+
*/
|
|
296
|
+
export const pendingQuantityOf = (line) => quantityOf(line, pendingQuantities);
|
|
297
|
+
|
|
298
|
+
/** @returns {CartMeta} */
|
|
299
|
+
const meta = () => ({
|
|
300
|
+
open: isOpen(),
|
|
301
|
+
count: currentCart()?.totalQuantity ?? 0,
|
|
302
|
+
loaded: status.kind !== 'unknown',
|
|
303
|
+
warnings,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Calls `listener` now and after every change of the cart or the drawer.
|
|
308
|
+
*
|
|
309
|
+
* @param {(cart: ShopifyCart | null, meta: CartMeta) => void} listener
|
|
310
|
+
* @returns {() => void} Stops listening.
|
|
311
|
+
*/
|
|
312
|
+
export function subscribe(listener) {
|
|
313
|
+
const events = ['cart-updated', 'cart-toggled'].map((name) => `${cfg.prefix}:${name}`);
|
|
314
|
+
const call = () => {
|
|
315
|
+
listener(currentCart(), meta());
|
|
316
|
+
};
|
|
317
|
+
events.forEach((name) => {
|
|
318
|
+
document.addEventListener(name, call);
|
|
319
|
+
});
|
|
320
|
+
call();
|
|
321
|
+
return () => {
|
|
322
|
+
events.forEach((name) => {
|
|
323
|
+
document.removeEventListener(name, call);
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
}
|