@occluze/tether 0.7.0 → 0.7.1

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 (132) hide show
  1. package/README.md +21 -21
  2. package/dist/esm/chunks/{account-UIPWJQ3Y.js → account-CXMA5RPZ.js} +1 -1
  3. package/dist/esm/chunks/{account-pages-6ROA6Y4V.js → account-pages-IZO7FEOZ.js} +1 -1
  4. package/dist/esm/chunks/{analytics-2V632IRV.js → analytics-PG5FPRDN.js} +1 -1
  5. package/dist/esm/chunks/{chunk-4GCXM764.js → chunk-4HD4J5JC.js} +1 -1
  6. package/dist/esm/chunks/{chunk-BFTZR4OB.js → chunk-6TFMLT4A.js} +2 -2
  7. package/dist/esm/chunks/{chunk-T44MUL4Y.js → chunk-BBVUVPMO.js} +1 -1
  8. package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
  9. package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
  10. package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
  11. package/dist/esm/chunks/{chunk-5Z5O3767.js → chunk-N3V45JPS.js} +1 -1
  12. package/dist/esm/chunks/{chunk-YLUULATY.js → chunk-RPMBCAGS.js} +1 -1
  13. package/dist/esm/chunks/{chunk-MPDWM2NN.js → chunk-VFNHIGCS.js} +1 -1
  14. package/dist/esm/chunks/{chunk-6OZJCAGZ.js → chunk-WT7RGGJU.js} +1 -1
  15. package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
  16. package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
  17. package/dist/esm/chunks/{diagnose-3FEKRIEK.js → diagnose-XCEBQKBO.js} +1 -1
  18. package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
  19. package/dist/esm/chunks/{markets-ui-FD6NI2OG.js → markets-ui-M4IQ62CT.js} +1 -1
  20. package/dist/esm/chunks/search-A5DS2IOS.js +1 -0
  21. package/dist/esm/index.js +1 -1
  22. package/dist/esm/server.js +2 -2
  23. package/dist/tether.account-pages.js +13 -3
  24. package/dist/tether.account-pages.min.js +1 -1
  25. package/dist/tether.account.js +13 -3
  26. package/dist/tether.account.min.js +1 -1
  27. package/dist/tether.analytics.js +18 -3
  28. package/dist/tether.analytics.min.js +1 -1
  29. package/dist/tether.diagnose.js +18 -3
  30. package/dist/tether.diagnose.min.js +2 -2
  31. package/dist/tether.extras.js +19 -6
  32. package/dist/tether.extras.min.js +1 -1
  33. package/dist/tether.js +162 -112
  34. package/dist/tether.markets.js +18 -3
  35. package/dist/tether.markets.min.js +1 -1
  36. package/dist/tether.min.js +3 -3
  37. package/dist/tether.min.js.map +4 -4
  38. package/dist/tether.search.js +18 -3
  39. package/dist/tether.search.min.js +1 -1
  40. package/dist/types/server/src/shared/cart-id.d.ts +2 -0
  41. package/dist/types/src/cart/mutations.d.ts +6 -0
  42. package/dist/types/src/cart/store.d.ts +5 -2
  43. package/dist/types/src/cart.d.ts +2 -2
  44. package/dist/types/src/client.d.ts +53 -19
  45. package/dist/types/src/config.d.ts +6 -1
  46. package/dist/types/src/index.d.ts +2 -2
  47. package/dist/types/src/product-shape.d.ts +3 -1
  48. package/package.json +1 -2
  49. package/dist/esm/chunks/chunk-56FCLJJD.js +0 -1
  50. package/dist/esm/chunks/chunk-A5SYCVQM.js +0 -1
  51. package/dist/esm/chunks/chunk-BA4LQJJQ.js +0 -1
  52. package/dist/esm/chunks/chunk-C5JYQCWH.js +0 -1
  53. package/dist/esm/chunks/extras-6POFBNOP.js +0 -1
  54. package/dist/esm/chunks/search-UJ73YBQ5.js +0 -1
  55. package/src/account/addresses-view.ts +0 -58
  56. package/src/account/answers.ts +0 -79
  57. package/src/account/markup.ts +0 -36
  58. package/src/account/operations.ts +0 -145
  59. package/src/account/order-lines.ts +0 -75
  60. package/src/account/order-list.ts +0 -107
  61. package/src/account/order-page.ts +0 -137
  62. package/src/account/page-answers.ts +0 -317
  63. package/src/account/page-events.ts +0 -253
  64. package/src/account/server-client.ts +0 -183
  65. package/src/account/subscriptions-view.ts +0 -115
  66. package/src/account-api.ts +0 -98
  67. package/src/account-marks.ts +0 -35
  68. package/src/account-pages.ts +0 -81
  69. package/src/account.ts +0 -284
  70. package/src/amounts.ts +0 -64
  71. package/src/analytics-shopify.ts +0 -246
  72. package/src/analytics.ts +0 -315
  73. package/src/api-version.ts +0 -79
  74. package/src/astro.ts +0 -272
  75. package/src/cart/cart-shape.ts +0 -25
  76. package/src/cart/checkout.ts +0 -101
  77. package/src/cart/drawer.ts +0 -125
  78. package/src/cart/mutations.ts +0 -237
  79. package/src/cart/render.ts +0 -266
  80. package/src/cart/store.ts +0 -279
  81. package/src/cart.ts +0 -345
  82. package/src/client.ts +0 -391
  83. package/src/collection.ts +0 -175
  84. package/src/config-sections.ts +0 -284
  85. package/src/config.ts +0 -480
  86. package/src/controls/cart-controls.ts +0 -123
  87. package/src/controls/discount.ts +0 -75
  88. package/src/controls/extras.ts +0 -54
  89. package/src/controls/navigation.ts +0 -77
  90. package/src/diagnose-markup.ts +0 -298
  91. package/src/diagnose.ts +0 -294
  92. package/src/dom.ts +0 -268
  93. package/src/extras.ts +0 -136
  94. package/src/fragments.ts +0 -261
  95. package/src/globals.d.ts +0 -15
  96. package/src/images.ts +0 -91
  97. package/src/index.ts +0 -415
  98. package/src/initial.ts +0 -101
  99. package/src/lazy-iife.ts +0 -197
  100. package/src/lazy.ts +0 -42
  101. package/src/loader.ts +0 -363
  102. package/src/market-codes.ts +0 -6
  103. package/src/markets-ui.ts +0 -227
  104. package/src/markets.ts +0 -246
  105. package/src/markup.ts +0 -127
  106. package/src/mounting.ts +0 -264
  107. package/src/page-market.ts +0 -110
  108. package/src/part-integrity.ts +0 -7
  109. package/src/parts/account-pages.ts +0 -7
  110. package/src/parts/account.ts +0 -7
  111. package/src/parts/analytics.ts +0 -7
  112. package/src/parts/diagnose.ts +0 -7
  113. package/src/parts/extras.ts +0 -7
  114. package/src/parts/markets.ts +0 -7
  115. package/src/parts/search.ts +0 -7
  116. package/src/presets/barba.ts +0 -194
  117. package/src/product-events.ts +0 -115
  118. package/src/product-render.ts +0 -218
  119. package/src/product-shape.ts +0 -49
  120. package/src/product-state.ts +0 -318
  121. package/src/product.ts +0 -308
  122. package/src/repeaters.ts +0 -161
  123. package/src/search.ts +0 -305
  124. package/src/selling-plans.ts +0 -145
  125. package/src/server.ts +0 -211
  126. package/src/shopify-values.ts +0 -91
  127. package/src/status.ts +0 -106
  128. package/src/storage.ts +0 -34
  129. package/src/store-html.ts +0 -359
  130. package/src/storefront/catalog.ts +0 -196
  131. package/src/storefront/json-ld.ts +0 -144
  132. package/src/variant-render.ts +0 -139
package/src/cart.ts DELETED
@@ -1,345 +0,0 @@
1
- /**
2
- * The cart, as one object: `Tether.cart`. Every change goes through one queue (fast clicks never race) and resolves
3
- * with the cart Shopify sent back. The state lives in `cart/store.ts`, the Cart API calls in `cart/mutations.ts`, the
4
- * drawer in `cart/drawer.ts`, the document rendering in `cart/render.ts` and the checkout in `cart/checkout.ts`.
5
- */
6
- import { cfg, reportError } from './config.ts';
7
- import { TetherError } from './client.ts';
8
- import { emit, toAttributes } from './dom.ts';
9
- import {
10
- fetchCart,
11
- isCartGone,
12
- mutateCart,
13
- removeLines,
14
- setLineQuantity,
15
- toLineInput,
16
- toLineUpdate,
17
- updateLines,
18
- } from './cart/mutations.ts';
19
- import { checkout, setCheckoutResolver } from './cart/checkout.ts';
20
- import { drawers, isOpen, openCart, closeCart, toggleCart, trapFocus } from './cart/drawer.ts';
21
- import {
22
- announce,
23
- cartStatus,
24
- commit,
25
- currentCart,
26
- currentWarnings,
27
- enqueue,
28
- forgetSavedCart,
29
- keepQuietly,
30
- pendingQuantities,
31
- pendingQuantityOf,
32
- render,
33
- savedCartId,
34
- settleLoad,
35
- showLastKnownCount,
36
- subscribe,
37
- } from './cart/store.ts';
38
- import type { CartWarning } from './config.ts';
39
- import type { Attributes, LineSpec, CartLineInput, LinePatch } from './cart/mutations.ts';
40
- import type { ShopifyCart, ShopifyCartLine, CartStatus } from './cart/store.ts';
41
-
42
- export { trapFocus };
43
-
44
- type DiscountCodes = string[] | string | ((current: string[]) => string[]);
45
-
46
- const currentLines = (): readonly ShopifyCartLine[] => currentCart()?.lines.nodes ?? [];
47
-
48
- /**
49
- * +1 / -1 for a line. Quick clicks add up: each builds on the quantity the click before asked for, which shows at once
50
- * (`tether-line="quantity"`, `quantityOf`) until Shopify has answered. Only the latest target is sent; the ones it
51
- * replaced are skipped.
52
- */
53
- function changeQuantity(lineId: string, delta: number): Promise<ShopifyCart | null> {
54
- const line = currentLines().find((candidate) => candidate.id === lineId);
55
- if (!line) return Promise.resolve(currentCart());
56
- const target = Math.max(0, pendingQuantityOf(line) + delta);
57
- const job = { quantity: target };
58
- pendingQuantities.set(lineId, job);
59
- announce('pending');
60
- const isLatest = () => pendingQuantities.get(lineId) === job;
61
- const sent = enqueue(async () => {
62
- if (!isLatest()) return currentCart();
63
- const now = currentLines().find((candidate) => candidate.id === lineId);
64
- // A + then a -: Shopify already has the quantity.
65
- if (now?.quantity === target) return currentCart();
66
- const cart = await setLineQuantity(lineId, target);
67
- // A newer click is on its way: keep this answer, but show totals and count once that one is in, instead of
68
- // stepping through every reply.
69
- if (!isLatest()) {
70
- keepQuietly(cart);
71
- return cart;
72
- }
73
- return commit(cart, target > 0 ? 'update' : 'remove');
74
- });
75
- return sent.finally(() => {
76
- if (!isLatest()) return;
77
- pendingQuantities.delete(lineId);
78
- announce('settled');
79
- });
80
- }
81
-
82
- /**
83
- * The codes the cart has once a change is its turn in the queue.
84
- *
85
- * @param codes A function gets the codes as they are by then.
86
- */
87
- function discountCodesFrom(codes: DiscountCodes): string[] {
88
- const current = (currentCart()?.discountCodes ?? []).map((discount) => discount.code);
89
- const list = typeof codes === 'function' ? codes(current) : codes;
90
- return (Array.isArray(list) ? list : [list]).map((code) => code.trim()).filter(Boolean);
91
- }
92
-
93
- /** @returns The cart's attributes with `attributes` merged in; `''` removes a key. */
94
- function mergedAttributes(attributes: Attributes): { key: string; value: string }[] {
95
- const merged = new Map((currentCart()?.attributes ?? []).map((attribute) => [attribute.key, attribute.value]));
96
- toAttributes(attributes).forEach((attribute) => merged.set(attribute.key, attribute.value));
97
- return [...merged].filter(([, value]) => value !== '').map(([key, value]) => ({ key, value }));
98
- }
99
-
100
- /**
101
- * Loads the saved cart. A cart Shopify no longer has is forgotten; one it can't be asked about (the network, the
102
- * token) is kept, because it is probably fine.
103
- */
104
- async function init(): Promise<ShopifyCart | null> {
105
- const id = savedCartId();
106
- if (!id) {
107
- settleLoad({ kind: 'loaded', cart: null });
108
- return null;
109
- }
110
- showLastKnownCount();
111
- try {
112
- const cart = await fetchCart(id);
113
- if (!cart) forgetSavedCart();
114
- settleLoad({ kind: 'loaded', cart });
115
- } catch (error) {
116
- reportError(error, 'cart load failed');
117
- if (isCartGone(error)) forgetSavedCart();
118
- settleLoad({ kind: 'failed', error });
119
- }
120
- return currentCart();
121
- }
122
-
123
- export const Cart = {
124
- init,
125
- /** The saved cart's id. */
126
- id: savedCartId,
127
-
128
- get status(): CartStatus {
129
- return cartStatus();
130
- },
131
- get state(): ShopifyCart | null {
132
- return currentCart();
133
- },
134
- /** The cart's lines, a copy: changing it changes nothing in the cart. */
135
- get lines(): ShopifyCartLine[] {
136
- return [...currentLines()];
137
- },
138
- get count(): number {
139
- return currentCart()?.totalQuantity ?? 0;
140
- },
141
- /** True once the saved cart, or that there is none, is known. */
142
- get loaded(): boolean {
143
- return cartStatus().kind !== 'unknown';
144
- },
145
- /**
146
- * The failure of the last load or change, until a change succeeds. Requests fail with an `Error`; anything else
147
- * thrown is wrapped, so the public type holds.
148
- */
149
- get lastError(): Error | null {
150
- const current = cartStatus();
151
- if (current.kind !== 'error') return null;
152
- return current.error instanceof Error ? current.error : new Error(String(current.error));
153
- },
154
- /** Shopify's notes on the last change, a copy. */
155
- get warnings(): CartWarning[] {
156
- return [...currentWarnings()];
157
- },
158
- /** Line id → the quantity quick clicks asked for. */
159
- get pendingQty(): ReadonlyMap<string, { quantity: number }> {
160
- return pendingQuantities;
161
- },
162
-
163
- /**
164
- * Sets where a logged-in customer checks out, or clears it with `null`: the account part's resolver answers the
165
- * customer's checkout URL for the cart (`null` uses the cart's own).
166
- */
167
- setCheckoutResolver,
168
-
169
- /**
170
- * Forgets the saved cart on this device, as if there were none: the page shows an empty cart and
171
- * `tether:cart-updated` fires with reason `load`. The next add starts a new cart.
172
- */
173
- forgetSaved(): void {
174
- forgetSavedCart();
175
- settleLoad({ kind: 'loaded', cart: null });
176
- },
177
-
178
- render,
179
- subscribe,
180
-
181
- /**
182
- * `add(variantId, 2)`, `add({ variantId, quantity, sellingPlanId, attributes })`, or `add([line, line])` for
183
- * bundles in one request. Creates the cart when there is none.
184
- *
185
- * @throws {TetherError} (rejected) When the add fails, or Shopify answers it without a cart.
186
- */
187
- add(
188
- input: string | number | LineSpec | (string | number | LineSpec)[],
189
- quantity = 1,
190
- options: Omit<LineSpec, 'variantId' | 'quantity'> = {},
191
- ): Promise<ShopifyCart> {
192
- const lines: CartLineInput[] = (Array.isArray(input) ? input : [input]).map((line) =>
193
- toLineInput(line, quantity, options),
194
- );
195
- return enqueue(async () => {
196
- const cart = await mutateCart({
197
- name: 'cartLinesAdd',
198
- args: '$lines: [CartLineInput!]!',
199
- inner: 'lines: $lines',
200
- variables: { lines },
201
- createInput: { lines },
202
- });
203
- // A mutation answers without a cart only beside user errors, which reject above; an add always has one.
204
- if (!cart) throw new TetherError('Shopify answered the add without a cart');
205
- commit(cart, 'add');
206
- emit('added', { cart, lines });
207
- if (cfg.openCartOnAdd) openCart();
208
- return cart;
209
- });
210
- },
211
-
212
- /**
213
- * `updateLine(lineId, 3)` or `updateLine(lineId, { quantity, sellingPlanId, attributes })`.
214
- *
215
- * @param patch A quantity of 0 or less removes the line.
216
- */
217
- updateLine(lineId: string, patch: number | LinePatch): Promise<ShopifyCart | null> {
218
- const quantity = typeof patch === 'number' ? patch : patch.quantity;
219
- if (quantity !== undefined && quantity <= 0) return Cart.removeLine(lineId);
220
- const line = toLineUpdate(lineId, patch);
221
- return enqueue(async () => commit(await updateLines([line]), 'update'));
222
- },
223
-
224
- changeQuantity,
225
-
226
- /** The quantity to show: the one asked for while an update is on its way. */
227
- quantityOf: pendingQuantityOf,
228
-
229
- removeLine(lineIds: string | string[]): Promise<ShopifyCart | null> {
230
- const ids = Array.isArray(lineIds) ? lineIds : [lineIds];
231
- return enqueue(async () => commit(await removeLines(ids), 'remove'));
232
- },
233
-
234
- clear(): Promise<ShopifyCart | null> {
235
- const ids = currentLines().map((line) => line.id);
236
- if (!ids.length) return Promise.resolve(currentCart());
237
- return Cart.removeLine(ids);
238
- },
239
-
240
- setNote(note: string | null | undefined): Promise<ShopifyCart | null> {
241
- const variables = { note: note ?? '' };
242
- return enqueue(async () =>
243
- commit(
244
- await mutateCart({
245
- name: 'cartNoteUpdate',
246
- args: '$note: String!',
247
- inner: 'note: $note',
248
- variables,
249
- createInput: variables,
250
- }),
251
- 'note',
252
- ),
253
- );
254
- },
255
-
256
- /**
257
- * Cart attributes, merged with the cart's (as it is when the change runs, so quick calls don't drop each other).
258
- *
259
- * @param attributes The value `''` removes a key.
260
- */
261
- setAttributes(attributes: Attributes): Promise<ShopifyCart | null> {
262
- return enqueue(async () => {
263
- const variables = { attributes: mergedAttributes(attributes) };
264
- const cart = await mutateCart({
265
- name: 'cartAttributesUpdate',
266
- args: '$attributes: [AttributeInput!]!',
267
- inner: 'attributes: $attributes',
268
- variables,
269
- createInput: variables,
270
- });
271
- return commit(cart, 'attributes');
272
- });
273
- },
274
-
275
- /** Replaces the discount codes; `[]` removes them all. */
276
- setDiscountCodes(codes: DiscountCodes): Promise<ShopifyCart | null> {
277
- return enqueue(async () => {
278
- const variables = { discountCodes: discountCodesFrom(codes) };
279
- const cart = await mutateCart({
280
- name: 'cartDiscountCodesUpdate',
281
- args: '$discountCodes: [String!]!',
282
- inner: 'discountCodes: $discountCodes',
283
- variables,
284
- createInput: variables,
285
- });
286
- return commit(cart, 'discount');
287
- });
288
- },
289
-
290
- /**
291
- * Adds a code. A code Shopify doesn't accept is removed again, so it doesn't warn on every later change.
292
- *
293
- * @throws {TetherError} `INVALID_DISCOUNT` (rejected) when Shopify doesn't accept the code.
294
- */
295
- async applyDiscount(code: string): Promise<ShopifyCart | null> {
296
- const cart = await Cart.setDiscountCodes((current) => [...new Set([...current, code])]);
297
- const wanted = code.toLowerCase();
298
- const applied = (cart?.discountCodes ?? []).find((discount) => discount.code.toLowerCase() === wanted);
299
- if (applied?.applicable) return cart;
300
- try {
301
- await Cart.removeDiscount(code);
302
- } catch (error) {
303
- reportError(error, 'removing a discount code Shopify refused');
304
- }
305
- throw new TetherError(`Discount code not valid: ${code}`, { code: 'INVALID_DISCOUNT' });
306
- },
307
-
308
- removeDiscount(code: string): Promise<ShopifyCart | null> {
309
- const unwanted = code.toLowerCase();
310
- return Cart.setDiscountCodes((current) => current.filter((existing) => existing.toLowerCase() !== unwanted));
311
- },
312
-
313
- /**
314
- * Sets who is buying; a country changes the cart's currency and prices to that market's.
315
- *
316
- * @param options `create: false` forgets an expired cart instead of creating an empty one.
317
- */
318
- setBuyerIdentity(
319
- buyerIdentity: { countryCode?: string; email?: string; phone?: string },
320
- { create = true }: { create?: boolean } = {},
321
- ): Promise<ShopifyCart | null> {
322
- return enqueue(async () => {
323
- const cart = await mutateCart({
324
- name: 'cartBuyerIdentityUpdate',
325
- args: '$buyerIdentity: CartBuyerIdentityInput!',
326
- inner: 'buyerIdentity: $buyerIdentity',
327
- variables: { buyerIdentity },
328
- createInput: create ? { buyerIdentity } : null,
329
- }).catch((error: unknown) => {
330
- // mutateCart has forgotten the cart Shopify no longer has; without `create` that leaves no cart.
331
- if (create || !isCartGone(error)) throw error;
332
- return null;
333
- });
334
- return commit(cart, 'buyer-identity');
335
- });
336
- },
337
-
338
- checkout,
339
-
340
- drawers,
341
- isOpen,
342
- open: openCart,
343
- close: closeCart,
344
- toggle: toggleCart,
345
- };