@occluze/tether 0.6.8 → 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 (215) hide show
  1. package/README.md +28 -28
  2. package/dist/esm/astro.js +2 -2
  3. package/dist/esm/chunks/account-CXMA5RPZ.js +1 -0
  4. package/dist/esm/chunks/account-pages-IZO7FEOZ.js +1 -0
  5. package/dist/esm/chunks/analytics-PG5FPRDN.js +1 -0
  6. package/dist/esm/chunks/chunk-4HD4J5JC.js +1 -0
  7. package/dist/esm/chunks/chunk-6TFMLT4A.js +4 -0
  8. package/dist/esm/chunks/chunk-BBVUVPMO.js +1 -0
  9. package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
  10. package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
  11. package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
  12. package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
  13. package/dist/esm/chunks/chunk-N3V45JPS.js +1 -0
  14. package/dist/esm/chunks/chunk-RPMBCAGS.js +1 -0
  15. package/dist/esm/chunks/chunk-VFNHIGCS.js +1 -0
  16. package/dist/esm/chunks/chunk-WT7RGGJU.js +1 -0
  17. package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
  18. package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
  19. package/dist/esm/chunks/diagnose-XCEBQKBO.js +14 -0
  20. package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
  21. package/dist/esm/chunks/markets-ui-M4IQ62CT.js +1 -0
  22. package/dist/esm/chunks/search-A5DS2IOS.js +1 -0
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/server.js +2 -2
  25. package/dist/presets/barba.esm.js +19 -23
  26. package/dist/tether.account-pages.js +332 -367
  27. package/dist/tether.account-pages.min.js +1 -1
  28. package/dist/tether.account.js +253 -492
  29. package/dist/tether.account.min.js +1 -1
  30. package/dist/tether.analytics.js +82 -89
  31. package/dist/tether.analytics.min.js +1 -1
  32. package/dist/tether.diagnose.js +52 -34
  33. package/dist/tether.diagnose.min.js +2 -2
  34. package/dist/tether.extras.js +42 -37
  35. package/dist/tether.extras.min.js +1 -1
  36. package/dist/tether.js +1361 -935
  37. package/dist/tether.markets.js +89 -91
  38. package/dist/tether.markets.min.js +1 -1
  39. package/dist/tether.min.js +0 -0
  40. package/dist/tether.min.js.map +4 -4
  41. package/dist/tether.search.js +60 -69
  42. package/dist/tether.search.min.js +1 -1
  43. package/dist/types/server/src/shared/account-orders.d.ts +44 -0
  44. package/dist/types/server/src/shared/account-routes.d.ts +153 -0
  45. package/dist/types/server/src/shared/api-versions.d.ts +13 -0
  46. package/dist/types/server/src/shared/cart-id.d.ts +2 -0
  47. package/dist/types/server/src/shared/error-message.d.ts +2 -0
  48. package/dist/types/server/src/shared/json.d.ts +25 -0
  49. package/dist/types/server/src/shared/market-codes.d.ts +16 -0
  50. package/dist/types/src/account/addresses-view.d.ts +5 -0
  51. package/dist/types/src/account/answers.d.ts +40 -0
  52. package/dist/types/src/account/markup.d.ts +19 -0
  53. package/dist/types/src/account/operations.d.ts +41 -0
  54. package/dist/types/src/account/order-lines.d.ts +13 -0
  55. package/dist/types/src/account/order-list.d.ts +12 -0
  56. package/dist/types/src/account/order-page.d.ts +6 -0
  57. package/dist/types/src/account/page-answers.d.ts +182 -0
  58. package/dist/types/src/account/page-events.d.ts +2 -0
  59. package/dist/types/src/account/server-client.d.ts +60 -0
  60. package/dist/types/src/account/subscriptions-view.d.ts +2 -0
  61. package/dist/types/src/account-api.d.ts +18 -0
  62. package/dist/types/src/account-marks.d.ts +15 -0
  63. package/dist/types/src/account-pages.d.ts +8 -0
  64. package/dist/types/src/account.d.ts +64 -0
  65. package/dist/types/src/amounts.d.ts +12 -0
  66. package/dist/types/src/analytics-shopify.d.ts +41 -0
  67. package/dist/types/src/analytics.d.ts +2 -0
  68. package/dist/types/src/api-version.d.ts +28 -0
  69. package/dist/types/src/astro.d.ts +42 -0
  70. package/dist/types/src/cart/cart-shape.d.ts +43 -0
  71. package/dist/types/src/cart/checkout.d.ts +16 -0
  72. package/dist/types/src/cart/drawer.d.ts +18 -0
  73. package/dist/types/src/cart/mutations.d.ts +85 -0
  74. package/dist/types/src/cart/render.d.ts +17 -0
  75. package/dist/types/src/cart/store.d.ts +185 -0
  76. package/dist/types/src/cart.d.ts +106 -0
  77. package/dist/types/src/client.d.ts +134 -0
  78. package/dist/types/src/collection.d.ts +25 -0
  79. package/dist/types/src/config-sections.d.ts +40 -0
  80. package/dist/types/src/config.d.ts +190 -0
  81. package/dist/types/src/controls/cart-controls.d.ts +2 -0
  82. package/dist/types/src/controls/discount.d.ts +2 -0
  83. package/dist/types/src/controls/extras.d.ts +5 -0
  84. package/dist/types/src/controls/navigation.d.ts +12 -0
  85. package/dist/types/src/diagnose-markup.d.ts +19 -0
  86. package/dist/types/src/diagnose.d.ts +60 -0
  87. package/dist/types/src/dom.d.ts +75 -0
  88. package/dist/types/src/extras.d.ts +21 -0
  89. package/dist/types/src/fragments.d.ts +158 -0
  90. package/dist/types/src/globals.d.ts +15 -0
  91. package/dist/types/src/images.d.ts +20 -0
  92. package/dist/types/src/index.d.ts +167 -0
  93. package/dist/types/src/initial.d.ts +56 -0
  94. package/dist/types/src/lazy.d.ts +28 -0
  95. package/dist/types/src/loader.d.ts +84 -0
  96. package/dist/types/src/market-codes.d.ts +6 -0
  97. package/dist/types/src/markets-ui.d.ts +32 -0
  98. package/dist/types/src/markets.d.ts +72 -0
  99. package/dist/types/src/markup.d.ts +131 -0
  100. package/dist/types/src/mounting.d.ts +46 -0
  101. package/dist/types/src/page-market.d.ts +27 -0
  102. package/dist/types/src/presets/barba.d.ts +56 -0
  103. package/dist/types/src/product-events.d.ts +2 -0
  104. package/dist/types/src/product-render.d.ts +17 -0
  105. package/dist/types/src/product-shape.d.ts +90 -0
  106. package/dist/types/src/product-state.d.ts +110 -0
  107. package/dist/types/src/product.d.ts +66 -0
  108. package/dist/types/src/repeaters.d.ts +16 -0
  109. package/dist/types/src/search.d.ts +33 -0
  110. package/dist/types/src/selling-plans.d.ts +64 -0
  111. package/dist/types/src/server.d.ts +88 -0
  112. package/dist/types/src/shopify-values.d.ts +46 -0
  113. package/dist/types/src/status.d.ts +33 -0
  114. package/dist/types/src/storage.d.ts +12 -0
  115. package/dist/types/src/store-html.d.ts +39 -0
  116. package/dist/types/src/storefront/catalog.d.ts +38 -0
  117. package/dist/types/src/storefront/json-ld.d.ts +20 -0
  118. package/dist/types/src/variant-render.d.ts +14 -0
  119. package/package.json +24 -24
  120. package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
  121. package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
  122. package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
  123. package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
  124. package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
  125. package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
  126. package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
  127. package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
  128. package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
  129. package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
  130. package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
  131. package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
  132. package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
  133. package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
  134. package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
  135. package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
  136. package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
  137. package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
  138. package/dist/esm/chunks/search-OU75RNQL.js +0 -1
  139. package/src/account/addresses-view.js +0 -92
  140. package/src/account/browser-session.js +0 -359
  141. package/src/account/markup.js +0 -48
  142. package/src/account/operations.js +0 -180
  143. package/src/account/order-lines.js +0 -96
  144. package/src/account/order-list.js +0 -91
  145. package/src/account/order-page.js +0 -192
  146. package/src/account/page-events.js +0 -288
  147. package/src/account/server-client.js +0 -172
  148. package/src/account/shared.js +0 -111
  149. package/src/account/shopify-auth.js +0 -150
  150. package/src/account/subscriptions-view.js +0 -158
  151. package/src/account-api.js +0 -118
  152. package/src/account-links.js +0 -35
  153. package/src/account-pages.js +0 -89
  154. package/src/account.js +0 -342
  155. package/src/amounts.js +0 -36
  156. package/src/analytics-shopify.js +0 -241
  157. package/src/analytics.js +0 -352
  158. package/src/api-version.js +0 -85
  159. package/src/astro.js +0 -266
  160. package/src/cart/checkout.js +0 -97
  161. package/src/cart/drawer.js +0 -138
  162. package/src/cart/mutations.js +0 -234
  163. package/src/cart/render.js +0 -294
  164. package/src/cart/store.js +0 -326
  165. package/src/cart.js +0 -391
  166. package/src/client.js +0 -267
  167. package/src/collection.js +0 -183
  168. package/src/config.js +0 -349
  169. package/src/controls/cart-controls.js +0 -155
  170. package/src/controls/discount.js +0 -96
  171. package/src/controls/extras.js +0 -63
  172. package/src/controls/navigation.js +0 -83
  173. package/src/diagnose-markup.js +0 -333
  174. package/src/diagnose.js +0 -295
  175. package/src/dom.js +0 -287
  176. package/src/extras.js +0 -145
  177. package/src/fragments.js +0 -237
  178. package/src/images.js +0 -104
  179. package/src/index.js +0 -353
  180. package/src/initial.js +0 -87
  181. package/src/lazy-iife.js +0 -116
  182. package/src/lazy.js +0 -39
  183. package/src/loader.js +0 -337
  184. package/src/market-codes.js +0 -35
  185. package/src/markets-ui.js +0 -204
  186. package/src/markets.js +0 -334
  187. package/src/markup.js +0 -64
  188. package/src/mounting.js +0 -277
  189. package/src/page-market.js +0 -116
  190. package/src/parts/account-pages.js +0 -6
  191. package/src/parts/account.js +0 -6
  192. package/src/parts/analytics.js +0 -6
  193. package/src/parts/diagnose.js +0 -6
  194. package/src/parts/extras.js +0 -6
  195. package/src/parts/markets.js +0 -6
  196. package/src/parts/search.js +0 -6
  197. package/src/presets/barba.js +0 -190
  198. package/src/product-events.js +0 -130
  199. package/src/product-render.js +0 -257
  200. package/src/product-state.js +0 -318
  201. package/src/product.js +0 -344
  202. package/src/repeaters.js +0 -173
  203. package/src/search.js +0 -323
  204. package/src/selling-plans.js +0 -154
  205. package/src/server.js +0 -215
  206. package/src/shopify-values.js +0 -89
  207. package/src/status.js +0 -126
  208. package/src/store-html.js +0 -0
  209. package/src/storefront/catalog.js +0 -199
  210. package/src/storefront/json-ld.js +0 -167
  211. package/src/variant-render.js +0 -165
  212. package/types/astro.d.ts +0 -28
  213. package/types/barba.d.ts +0 -16
  214. package/types/index.d.ts +0 -577
  215. package/types/server.d.ts +0 -182
package/src/index.js DELETED
@@ -1,353 +0,0 @@
1
- /**
2
- * Tether's entry: `window.Tether`, `init()`, `mount()` and the start of the plain `<script>` setup.
3
- *
4
- * <script>window.TETHER_CONFIG = { domain: 'x.myshopify.com', token: '...' }</script>
5
- * <script src="tether.min.js" defer></script>
6
- *
7
- * With page transitions (Barba, Swup): set `autoMount: false`, call `Tether.init()` once and
8
- * `Tether.mount(container)` after every page enter.
9
- */
10
- import { cfg, configure, isConfigured, log, warn, reportError } from './config.js';
11
- import { gql, TetherError, withContext } from './client.js';
12
- import { Cart } from './cart.js';
13
- import { scopes } from './product.js';
14
- import { ListScope, listSelector } from './collection.js';
15
- import {
16
- getProduct,
17
- getProducts,
18
- getRecommendations,
19
- clearProductCache,
20
- primeProduct,
21
- normalizeProductKey,
22
- } from './loader.js';
23
- import * as plans from './selling-plans.js';
24
- import { resolveProductKey } from './status.js';
25
- import { sel, emit, formatMoney, imageUrl } from './dom.js';
26
- import { loadPart, coreModules } from './lazy.js';
27
- import {
28
- mountedLists,
29
- liveLists,
30
- pruneLive,
31
- within,
32
- mountProduct,
33
- hydrateFromPage,
34
- settleMarket,
35
- trackMarket,
36
- } from './mounting.js';
37
- import { applyStartMarket, setCountry } from './page-market.js';
38
- import { accountApi, mountAccountIfUsed } from './account-api.js';
39
- import { bindCartControls } from './controls/cart-controls.js';
40
- import { bindDiscountControls } from './controls/discount.js';
41
- import { addGroup, bindExtrasControls } from './controls/extras.js';
42
- import { bindNavigation, rememberDocumentMount } from './controls/navigation.js';
43
- /** @import { TetherConfig, TetherOptions, MarketsConfig } from './config.js' */
44
- /** @import { ShopifyCart } from './cart/store.js' */
45
- /** @import { ProductScope } from './product.js' */
46
- /** @import { DiagnoseOptions } from './diagnose.js' */
47
-
48
- export { addGroup, setCountry };
49
-
50
- export const VERSION = '0.6.8';
51
-
52
- /** The events the analytics part reports, buffered from `init()` until the part has loaded. */
53
- const ANALYTICS_EVENTS = ['product-ready', 'list-ready', 'cart-updated', 'cart-toggled', 'checkout', 'search'];
54
-
55
- /** Milliseconds `observe` waits after the last added node, so a burst of additions mounts once. */
56
- const OBSERVE_DEBOUNCE_MS = 50;
57
-
58
- /**
59
- * @typedef {object} TetherGlobals What the page sets on `window` for Tether, and `window.Tether` itself.
60
- * @property {TetherOptions} [TETHER_CONFIG] The site's config; `configure()` validates it.
61
- * @property {string} [TETHER_INTEGRATION] Set by a framework integration (Astro) that starts Tether itself.
62
- * @property {TetherApi} [Tether]
63
- */
64
-
65
- /** @returns {Window & TetherGlobals} */
66
- const pageGlobals = () => window;
67
-
68
- /** @type {Promise<TetherApi | null> | null} */
69
- let initPromise = null;
70
-
71
- /** @type {MutationObserver | null} */
72
- let observer = null;
73
-
74
- /** @type {Event[] | null} The analytics part's queue, created once by the first `init()` with `analytics`. */
75
- let analyticsQueue = null;
76
-
77
- /** @type {boolean} The document-wide listeners are bound once, by the first configured `init()`. */
78
- let controlsBound = false;
79
-
80
- /**
81
- * Mounts the lists of `root` and the product scopes in them as they render.
82
- *
83
- * @param {ParentNode} root
84
- * @returns {number} The lists mounted.
85
- */
86
- function mountLists(root) {
87
- let count = 0;
88
- within(root, listSelector()).forEach((el) => {
89
- if (mountedLists.has(el)) return;
90
- mountedLists.add(el);
91
- count++;
92
- const list = new ListScope(el);
93
- liveLists.add(list);
94
- el.addEventListener(`${cfg.prefix}:list-rendered`, (event) => {
95
- // The list emits this CustomEvent once its products are in the page.
96
- mount(/** @type {CustomEvent<{ container: ParentNode }>} */ (event).detail.container);
97
- });
98
- void trackMarket(list.load());
99
- });
100
- return count;
101
- }
102
-
103
- /**
104
- * Loads each optional part `root` uses: the country select, customer accounts and search as you type.
105
- *
106
- * @param {ParentNode} root
107
- */
108
- function mountParts(root) {
109
- if (within(root, `${sel('country-select')},${sel('country-bind')}`).length) {
110
- loadPart('markets')
111
- .then((part) => part.renderCountrySelects(root))
112
- .catch((/** @type {unknown} */ error) => {
113
- reportError(error, 'markets');
114
- });
115
- }
116
- mountAccountIfUsed(root);
117
- if (within(root, sel('search')).length) {
118
- loadPart('search')
119
- .then((part) => {
120
- part.mountSearch(root);
121
- })
122
- .catch((/** @type {unknown} */ error) => {
123
- reportError(error, 'search');
124
- });
125
- }
126
- }
127
-
128
- /**
129
- * Mounts every product scope and product list inside `root`. Safe to call repeatedly.
130
- *
131
- * @param {ParentNode} [root]
132
- * @returns {number} The scopes and lists newly mounted.
133
- */
134
- export function mount(root = document) {
135
- if (!isConfigured()) return 0;
136
- if (root === document) rememberDocumentMount();
137
- // Page transitions detach old roots: they aren't kept alive.
138
- pruneLive();
139
- // Products the server rendered into the page need no request.
140
- hydrateFromPage(root.ownerDocument ?? document);
141
- let count = mountLists(root);
142
- within(root, sel('product')).forEach((el) => {
143
- if (mountProduct(el)) count++;
144
- });
145
- mountParts(root);
146
- settleMarket();
147
- Cart.render();
148
- log(`mounted ${count} scope(s)`, root);
149
- return count;
150
- }
151
-
152
- /** Call before a page transition removes the page: closes the cart drawer. */
153
- export function unmount() {
154
- Cart.close();
155
- }
156
-
157
- /**
158
- * @param {Element | null | undefined} el
159
- * @returns {ProductScope | null} The product scope `el` is in.
160
- */
161
- export function getScope(el) {
162
- const root = el?.closest(sel('product'));
163
- return root ? (scopes.get(root) ?? null) : null;
164
- }
165
-
166
- function bindControls() {
167
- if (controlsBound) return;
168
- controlsBound = true;
169
- bindCartControls();
170
- bindDiscountControls();
171
- bindExtrasControls();
172
- bindNavigation({ ready: () => initPromise ?? Promise.resolve(null), mount, unmount });
173
- }
174
-
175
- /** Buffers Tether's events until the analytics part has loaded, so none are missed. */
176
- function startAnalyticsQueue() {
177
- /** @type {Event[]} */
178
- const queue = [];
179
- analyticsQueue = queue;
180
- ANALYTICS_EVENTS.forEach((name) => {
181
- document.addEventListener(`${cfg.prefix}:${name}`, (event) => queue.push(event));
182
- });
183
- document.addEventListener('astro:page-load', () => queue.push(new CustomEvent(`${cfg.prefix}:page`)));
184
- loadPart('analytics')
185
- .then((part) => part.startAnalytics(queue))
186
- .catch((/** @type {unknown} */ error) => {
187
- reportError(error, 'analytics');
188
- });
189
- }
190
-
191
- /**
192
- * A saved cart from another market moves to this one, so its prices match the page's.
193
- *
194
- * @param {ShopifyCart | null} cart
195
- * @returns {Promise<ShopifyCart | null>}
196
- */
197
- async function cartInPageMarket(cart) {
198
- const country = cfg.country?.toUpperCase();
199
- const cartCountry = cart?.buyerIdentity.countryCode;
200
- if (!cart || !country || !cartCountry || cartCountry === country) return cart;
201
- try {
202
- return await Cart.setBuyerIdentity({ countryCode: country }, { create: false });
203
- } catch (error) {
204
- reportError(error, 'cart country');
205
- return cart;
206
- }
207
- }
208
-
209
- /**
210
- * Configures Tether (`window.TETHER_CONFIG` with `overrides` over it), applies the visitor's market and loads the
211
- * cart. A second call without overrides returns the first one's promise.
212
- *
213
- * @param {TetherOptions} [overrides]
214
- * @returns {Promise<TetherApi | null>} `null` without a domain and token, when Tether stays idle.
215
- */
216
- export function init(overrides = {}) {
217
- if (initPromise && !Object.keys(overrides).length) return initPromise;
218
- configure({ ...pageGlobals().TETHER_CONFIG, ...overrides });
219
- if (!isConfigured()) {
220
- warn('TETHER_CONFIG is missing domain or token. Tether is idle.');
221
- initPromise = Promise.resolve(null);
222
- return initPromise;
223
- }
224
- bindControls();
225
- if (cfg.observe) startObserver();
226
- if (cfg.analytics && !analyticsQueue) startAnalyticsQueue();
227
- initPromise = applyStartMarket(overrides)
228
- .then(() => Cart.init())
229
- .then(cartInPageMarket)
230
- .then((cart) => {
231
- emit('ready', { cart });
232
- return api;
233
- });
234
- return initPromise;
235
- }
236
-
237
- /** `observe`: mounts the elements added to the page later. */
238
- function startObserver() {
239
- if (observer || typeof MutationObserver === 'undefined') return;
240
- /** @type {ReturnType<typeof setTimeout> | undefined} */
241
- let timer;
242
- /** @type {Set<Element>} */
243
- const queued = new Set();
244
- observer = new MutationObserver((mutations) => {
245
- mutations.forEach((mutation) => {
246
- mutation.addedNodes.forEach((node) => {
247
- if (node instanceof window.Element) queued.add(node);
248
- });
249
- });
250
- clearTimeout(timer);
251
- timer = setTimeout(() => {
252
- queued.forEach((node) => {
253
- if (node.isConnected) mount(node);
254
- });
255
- queued.clear();
256
- }, OBSERVE_DEBOUNCE_MS);
257
- });
258
- observer.observe(document.body, { childList: true, subtree: true });
259
- }
260
-
261
- /**
262
- * @param {MarketsConfig | null} [markets] Default `markets` of the config.
263
- */
264
- const detectMarket = (markets) => loadPart('markets').then((part) => part.detectMarket(markets ?? cfg.markets));
265
-
266
- const getLocalization = () => loadPart('markets').then((part) => part.getLocalization());
267
-
268
- /** @param {Omit<DiagnoseOptions, 'version'>} [options] */
269
- const diagnose = (options) => loadPart('diagnose').then((part) => part.diagnose({ ...options, version: VERSION }));
270
-
271
- /**
272
- * Listens to the Tether event `<prefix>:<name>`.
273
- *
274
- * @param {string} name
275
- * @param {(detail: unknown, event: CustomEvent) => void} listener
276
- * @returns {() => void} Stops listening.
277
- */
278
- function on(name, listener) {
279
- /** @param {Event} event */
280
- const handler = (event) => {
281
- const custom = /** @type {CustomEvent} Tether's events are CustomEvents. */ (event);
282
- listener(custom.detail, custom);
283
- };
284
- document.addEventListener(`${cfg.prefix}:${name}`, handler);
285
- return () => {
286
- document.removeEventListener(`${cfg.prefix}:${name}`, handler);
287
- };
288
- }
289
-
290
- export const api = {
291
- VERSION,
292
- init,
293
- mount,
294
- unmount,
295
- /** @returns {TetherConfig} */
296
- get config() {
297
- return cfg;
298
- },
299
- /** @returns {Promise<TetherApi | null>} */
300
- get ready() {
301
- return initPromise ?? Promise.resolve(null);
302
- },
303
- query: gql,
304
- withContext,
305
- cart: Cart,
306
- getProduct,
307
- getProducts,
308
- getRecommendations,
309
- getScope,
310
- addGroup,
311
- account: accountApi,
312
- primeProduct,
313
- normalizeProductKey,
314
- clearProductCache,
315
- formatMoney,
316
- imageUrl,
317
- plans,
318
- TetherError,
319
- resolveProductKey,
320
- setCountry,
321
- detectMarket,
322
- getLocalization,
323
- diagnose,
324
- __core: coreModules,
325
- // The `<script>` build's parts register here when their file has loaded.
326
- __parts: /** @type {Record<string, unknown>} */ ({}),
327
- on,
328
- };
329
-
330
- /** @typedef {typeof api} TetherApi */
331
-
332
- export default api;
333
-
334
- /**
335
- * The plain `<script>` setup starts by itself, unless `autoMount: false` or a framework integration (Astro), which
336
- * starts Tether itself.
337
- */
338
- function autoStart() {
339
- const globals = pageGlobals();
340
- globals.Tether = api;
341
- if (!globals.TETHER_CONFIG || globals.TETHER_CONFIG.autoMount === false || globals.TETHER_INTEGRATION) return;
342
- const start = () => {
343
- init()
344
- .then(() => mount(document))
345
- .catch((/** @type {unknown} */ error) => {
346
- reportError(error, 'init');
347
- });
348
- };
349
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true });
350
- else start();
351
- }
352
-
353
- if (typeof window !== 'undefined' && typeof document !== 'undefined') autoStart();
package/src/initial.js DELETED
@@ -1,87 +0,0 @@
1
- /**
2
- * What the browser script shows first for a product, as plain data, so a server renders the same and nothing flashes
3
- * when Tether takes over. Public as `initialState()` of `@occluze/tether/server`; the rules are `product-state.js`'s,
4
- * the ones the browser scope uses.
5
- */
6
- import { DEFAULTS } from './config.js';
7
- import { initialChoice, isValueAvailable, productView } from './product-state.js';
8
- import { allPlans, allocationFor, planPrice, planSavings } from './selling-plans.js';
9
- /** @import { Money } from './shopify-values.js' */
10
- /** @import { Product, ProductVariant } from './fragments.js' */
11
- /** @import { PlanInGroup } from './selling-plans.js' */
12
- /** @import { Selection, ShowCondition } from './product-state.js' */
13
-
14
- /**
15
- * @typedef {object} InitialOptions
16
- * @property {string | null | undefined} [variant] The page's `?variant=`; the first variant for sale without.
17
- * @property {string | null | undefined} [sellingPlan] The page's `?selling_plan=`: subscribes on that plan.
18
- * @property {string | null | undefined} [defaultPurchase] `subscription` subscribes on the first plan.
19
- * @property {number | undefined} [lowStock] `low-stock` at this many or fewer.
20
- */
21
- /**
22
- * @typedef {object} InitialPlan A plan in the plan list (`tether-plan-list`).
23
- * @property {string} id
24
- * @property {string} name
25
- * @property {Money | null} price On this plan, for the variant.
26
- * @property {number} savings In %, 0 without savings.
27
- * @property {boolean} selected `is-selected`: the line is bought on this plan.
28
- * @property {boolean} available The variant is on this plan; `is-unavailable` when not.
29
- */
30
- /**
31
- * @typedef {object} InitialState
32
- * @property {ProductVariant} variant
33
- * @property {Selection} selected Option name → value of the variant: `is-selected` on those buttons.
34
- * @property {'one-time' | 'subscription'} purchase
35
- * @property {PlanInGroup | null} plan The plan the line is bought on, `null` when bought once.
36
- * @property {InitialPlan[]} plans
37
- * @property {number} savings The offered plan's savings in % (`tether-bind="subscription-savings"`), 0 without.
38
- * @property {Money} price
39
- * @property {Money | null} compareAtPrice Only when on sale.
40
- * @property {boolean} available
41
- * @property {Record<ShowCondition, boolean>} show The `tether-show` conditions: `hidden={!state.show['out-of-stock']}`.
42
- * @property {Record<string, string[]>} unavailable Per option, the values without a variant for sale (given the other
43
- * selected values), as plain data for frameworks that serialise props.
44
- * @property {(name: string, value: string) => boolean} isUnavailable The same as a function: `is-unavailable`.
45
- */
46
-
47
- /**
48
- * @param {Product | null | undefined} product
49
- * @param {InitialOptions} [options]
50
- * @returns {InitialState | null} `null` without a product or variants.
51
- */
52
- export function initialState(product, options = {}) {
53
- if (!product) return null;
54
- const { variant, sellingPlan, defaultPurchase = DEFAULTS.defaultPurchase, lowStock = DEFAULTS.lowStock } = options;
55
- const choice = initialChoice(product, { variant, sellingPlan, defaultPurchase });
56
- const view = productView(product, { ...choice, quantity: 1 }, lowStock);
57
- if (!view) return null;
58
- const { selected, isSubscription } = view;
59
- const isUnavailable = (/** @type {string} */ name, /** @type {string} */ value) =>
60
- !isValueAvailable(product, selected, name, value);
61
- return {
62
- variant: view.variant,
63
- selected,
64
- purchase: view.purchase.kind,
65
- plan: isSubscription ? view.plan : null,
66
- plans: allPlans(product).map((plan) => ({
67
- id: plan.id,
68
- name: plan.name,
69
- price: planPrice(view.variant, plan.id),
70
- savings: planSavings(view.variant, plan.id).percentage,
71
- selected: isSubscription && plan.id === view.purchase.planId,
72
- available: Boolean(allocationFor(view.variant, plan.id)),
73
- })),
74
- savings: view.savings.percentage,
75
- price: view.price,
76
- compareAtPrice: view.compareAtPrice,
77
- available: view.variant.availableForSale,
78
- show: view.show,
79
- unavailable: Object.fromEntries(
80
- product.options.map((option) => [
81
- option.name,
82
- option.optionValues.map((value) => value.name).filter((value) => isUnavailable(option.name, value)),
83
- ]),
84
- ),
85
- isUnavailable,
86
- };
87
- }
package/src/lazy-iife.js DELETED
@@ -1,116 +0,0 @@
1
- /**
2
- * `<script>` build: the optional parts are separate files next to tether(.min).js (tether.markets.min.js), loaded on
3
- * first use. `partsUrl` sets another folder. A part registers itself in `window.Tether.__parts` and uses the core
4
- * modules in `window.Tether.__core` instead of bundling copies of them.
5
- */
6
- import * as config from './config.js';
7
- import * as client from './client.js';
8
- import * as dom from './dom.js';
9
- import * as fragments from './fragments.js';
10
- import * as loader from './loader.js';
11
- import * as sellingPlans from './selling-plans.js';
12
- import * as status from './status.js';
13
- import * as cart from './cart.js';
14
- import * as product from './product.js';
15
- import * as collection from './collection.js';
16
- import * as mounting from './mounting.js';
17
- import * as marketCodes from './market-codes.js';
18
- import * as shopifyValues from './shopify-values.js';
19
- import * as storeHtml from './store-html.js';
20
- /** @import { Parts, PartName } from './lazy.js' */
21
-
22
- const { cfg } = config;
23
-
24
- /**
25
- * The modules a part may use, shared instead of bundled twice (scripts/build.mjs maps a part's imports to these).
26
- *
27
- * @type {Record<string, object>}
28
- */
29
- export const coreModules = {
30
- 'config.js': config,
31
- 'client.js': client,
32
- 'dom.js': dom,
33
- 'fragments.js': fragments,
34
- 'loader.js': loader,
35
- 'selling-plans.js': sellingPlans,
36
- 'status.js': status,
37
- 'cart.js': cart,
38
- 'product.js': product,
39
- 'collection.js': collection,
40
- 'mounting.js': mounting,
41
- 'market-codes.js': marketCodes,
42
- 'shopify-values.js': shopifyValues,
43
- 'store-html.js': storeHtml,
44
- };
45
-
46
- const MINIFIED_SCRIPT = /\.min\.js(\?|$)/;
47
- const coreScript = typeof document === 'undefined' ? '' : (document.currentScript?.getAttribute('src') ?? '');
48
- const coreScriptUrl = coreScript ? new URL(coreScript, document.baseURI).href : '';
49
-
50
- /** @type {Map<PartName, Promise<unknown>>} Parts loaded or loading. */
51
- const parts = new Map();
52
-
53
- /**
54
- * The folder parts load from: `partsUrl`, else the core script's own folder. A `partsUrl` must be https or on this
55
- * site, because the parts run with the same access as the core (the account part holds customer sessions).
56
- *
57
- * @throws {Error} For a `partsUrl` that is neither.
58
- */
59
- function partsFolder() {
60
- if (!cfg.partsUrl) return coreScriptUrl.replace(/[^/]*$/, '');
61
- const folder = new URL(cfg.partsUrl, document.baseURI);
62
- if (folder.protocol !== 'https:' && folder.origin !== window.location.origin) {
63
- throw new Error(`partsUrl must be https or on this site: ${folder.href}`);
64
- }
65
- return folder.href;
66
- }
67
-
68
- /**
69
- * @param {PartName} name
70
- * @param {string} url
71
- * @returns {Promise<unknown>}
72
- */
73
- function loadScript(name, url) {
74
- return new Promise((resolve, reject) => {
75
- const script = document.createElement('script');
76
- script.src = url;
77
- script.async = true;
78
- script.onload = () => {
79
- const { Tether } = /** @type {Window & { Tether?: { __parts?: Record<string, unknown> } }} */ (window);
80
- const part = Tether?.__parts?.[name];
81
- if (part) resolve(part);
82
- else reject(new Error(`${url} did not register`));
83
- };
84
- script.onerror = () => {
85
- parts.delete(name);
86
- reject(new Error(`Could not load ${url}. Host it next to the Tether script, or set partsUrl.`));
87
- };
88
- document.head.appendChild(script);
89
- });
90
- }
91
-
92
- /**
93
- * @template {PartName} Name
94
- * @param {Name} name
95
- * @returns {Promise<Parts[Name]>}
96
- * @throws {Error} (rejected) When the part's file can't be loaded or doesn't register.
97
- */
98
- export function loadPart(name) {
99
- let loading = parts.get(name);
100
- if (!loading) {
101
- let folder;
102
- try {
103
- folder = partsFolder();
104
- } catch (error) {
105
- return Promise.reject(error instanceof Error ? error : new Error(String(error)));
106
- }
107
- const suffix = MINIFIED_SCRIPT.test(coreScriptUrl) || !coreScriptUrl ? '.min' : '';
108
- loading = loadScript(name, `${folder.replace(/\/?$/, '/')}tether.${name}${suffix}.js`);
109
- parts.set(name, loading);
110
- }
111
- // A part's file is built from the module `Parts[Name]` names (scripts/build.mjs), and registers that module.
112
- return /** @type {Promise<Parts[Name]>} */ (loading);
113
- }
114
-
115
- // Parts that load other parts (account → account-pages) use this same loader.
116
- coreModules['lazy.js'] = { loadPart, coreModules };
package/src/lazy.js DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Optional parts that most pages never need, loaded on first use. In the ESM and Node builds they are dynamic
3
- * imports, which bundlers split into their own chunks; the `<script>` build swaps this file for `lazy-iife.js`
4
- * (scripts/build.mjs).
5
- */
6
-
7
- /**
8
- * @typedef {{
9
- * diagnose: typeof import('./diagnose.js'),
10
- * markets: typeof import('./markets-ui.js'),
11
- * search: typeof import('./search.js'),
12
- * extras: typeof import('./extras.js'),
13
- * account: typeof import('./account.js'),
14
- * 'account-pages': typeof import('./account-pages.js'),
15
- * analytics: typeof import('./analytics.js'),
16
- * }} Parts
17
- */
18
- /** @typedef {keyof Parts} PartName */
19
-
20
- /** @type {{ [Name in PartName]: () => Promise<Parts[Name]> }} */
21
- const PARTS = {
22
- diagnose: () => import('./diagnose.js'),
23
- markets: () => import('./markets-ui.js'),
24
- search: () => import('./search.js'),
25
- extras: () => import('./extras.js'),
26
- account: () => import('./account.js'),
27
- 'account-pages': () => import('./account-pages.js'),
28
- analytics: () => import('./analytics.js'),
29
- };
30
-
31
- /**
32
- * @template {PartName} Name
33
- * @param {Name} name
34
- * @returns {Promise<Parts[Name]>}
35
- */
36
- export const loadPart = (name) => PARTS[name]();
37
-
38
- /** Only the `<script>` build shares its modules with separately loaded parts. */
39
- export const coreModules = undefined;