@occluze/tether 0.6.7 → 0.7.0

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 (250) hide show
  1. package/README.md +11 -11
  2. package/dist/esm/astro.js +2 -2
  3. package/dist/esm/chunks/account-UIPWJQ3Y.js +1 -0
  4. package/dist/esm/chunks/account-pages-6ROA6Y4V.js +1 -0
  5. package/dist/esm/chunks/analytics-2V632IRV.js +1 -0
  6. package/dist/esm/chunks/chunk-4GCXM764.js +1 -0
  7. package/dist/esm/chunks/chunk-56FCLJJD.js +1 -0
  8. package/dist/esm/chunks/chunk-5Z5O3767.js +1 -0
  9. package/dist/esm/chunks/chunk-6OZJCAGZ.js +1 -0
  10. package/dist/esm/chunks/chunk-A5SYCVQM.js +1 -0
  11. package/dist/esm/chunks/chunk-BA4LQJJQ.js +1 -0
  12. package/dist/esm/chunks/chunk-BFTZR4OB.js +4 -0
  13. package/dist/esm/chunks/chunk-C5JYQCWH.js +1 -0
  14. package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
  15. package/dist/esm/chunks/chunk-MPDWM2NN.js +1 -0
  16. package/dist/esm/chunks/chunk-T44MUL4Y.js +1 -0
  17. package/dist/esm/chunks/chunk-YLUULATY.js +1 -0
  18. package/dist/esm/chunks/diagnose-3FEKRIEK.js +14 -0
  19. package/dist/esm/chunks/extras-6POFBNOP.js +1 -0
  20. package/dist/esm/chunks/markets-ui-FD6NI2OG.js +1 -0
  21. package/dist/esm/chunks/search-UJ73YBQ5.js +1 -0
  22. package/dist/esm/index.js +1 -1
  23. package/dist/esm/server.js +2 -1
  24. package/dist/presets/barba.esm.js +55 -39
  25. package/dist/tether.account-pages.js +781 -495
  26. package/dist/tether.account-pages.min.js +1 -1
  27. package/dist/tether.account.js +462 -381
  28. package/dist/tether.account.min.js +1 -1
  29. package/dist/tether.analytics.js +299 -194
  30. package/dist/tether.analytics.min.js +1 -1
  31. package/dist/tether.diagnose.js +354 -262
  32. package/dist/tether.diagnose.min.js +2 -2
  33. package/dist/tether.extras.js +82 -45
  34. package/dist/tether.extras.min.js +1 -1
  35. package/dist/tether.js +3653 -2116
  36. package/dist/tether.markets.js +238 -150
  37. package/dist/tether.markets.min.js +1 -1
  38. package/dist/tether.min.js +3 -3
  39. package/dist/tether.min.js.map +4 -4
  40. package/dist/tether.search.js +207 -136
  41. package/dist/tether.search.min.js +1 -1
  42. package/dist/types/server/src/shared/account-orders.d.ts +44 -0
  43. package/dist/types/server/src/shared/account-routes.d.ts +153 -0
  44. package/dist/types/server/src/shared/api-versions.d.ts +13 -0
  45. package/dist/types/server/src/shared/error-message.d.ts +2 -0
  46. package/dist/types/server/src/shared/json.d.ts +25 -0
  47. package/dist/types/server/src/shared/market-codes.d.ts +16 -0
  48. package/dist/types/src/account/addresses-view.d.ts +5 -0
  49. package/dist/types/src/account/answers.d.ts +40 -0
  50. package/dist/types/src/account/markup.d.ts +19 -0
  51. package/dist/types/src/account/operations.d.ts +41 -0
  52. package/dist/types/src/account/order-lines.d.ts +13 -0
  53. package/dist/types/src/account/order-list.d.ts +12 -0
  54. package/dist/types/src/account/order-page.d.ts +6 -0
  55. package/dist/types/src/account/page-answers.d.ts +182 -0
  56. package/dist/types/src/account/page-events.d.ts +2 -0
  57. package/dist/types/src/account/server-client.d.ts +60 -0
  58. package/dist/types/src/account/subscriptions-view.d.ts +2 -0
  59. package/dist/types/src/account-api.d.ts +18 -0
  60. package/dist/types/src/account-marks.d.ts +15 -0
  61. package/dist/types/src/account-pages.d.ts +8 -0
  62. package/dist/types/src/account.d.ts +64 -0
  63. package/dist/types/src/amounts.d.ts +12 -0
  64. package/dist/types/src/analytics-shopify.d.ts +41 -0
  65. package/dist/types/src/analytics.d.ts +2 -0
  66. package/dist/types/src/api-version.d.ts +28 -0
  67. package/dist/types/src/astro.d.ts +42 -0
  68. package/dist/types/src/cart/cart-shape.d.ts +43 -0
  69. package/dist/types/src/cart/checkout.d.ts +16 -0
  70. package/dist/types/src/cart/drawer.d.ts +18 -0
  71. package/dist/types/src/cart/mutations.d.ts +79 -0
  72. package/dist/types/src/cart/render.d.ts +17 -0
  73. package/dist/types/src/cart/store.d.ts +182 -0
  74. package/dist/types/src/cart.d.ts +106 -0
  75. package/dist/types/src/client.d.ts +100 -0
  76. package/dist/types/src/collection.d.ts +25 -0
  77. package/dist/types/src/config-sections.d.ts +40 -0
  78. package/dist/types/src/config.d.ts +185 -0
  79. package/dist/types/src/controls/cart-controls.d.ts +2 -0
  80. package/dist/types/src/controls/discount.d.ts +2 -0
  81. package/dist/types/src/controls/extras.d.ts +5 -0
  82. package/dist/types/src/controls/navigation.d.ts +12 -0
  83. package/dist/types/src/diagnose-markup.d.ts +19 -0
  84. package/dist/types/src/diagnose.d.ts +60 -0
  85. package/dist/types/src/dom.d.ts +75 -0
  86. package/dist/types/src/extras.d.ts +21 -0
  87. package/dist/types/src/fragments.d.ts +158 -0
  88. package/dist/types/src/globals.d.ts +15 -0
  89. package/dist/types/src/images.d.ts +20 -0
  90. package/dist/types/src/index.d.ts +167 -0
  91. package/dist/types/src/initial.d.ts +56 -0
  92. package/dist/types/src/lazy.d.ts +28 -0
  93. package/dist/types/src/loader.d.ts +84 -0
  94. package/dist/types/src/market-codes.d.ts +6 -0
  95. package/dist/types/src/markets-ui.d.ts +32 -0
  96. package/dist/types/src/markets.d.ts +72 -0
  97. package/dist/types/src/markup.d.ts +131 -0
  98. package/dist/types/src/mounting.d.ts +46 -0
  99. package/dist/types/src/page-market.d.ts +27 -0
  100. package/dist/types/src/presets/barba.d.ts +56 -0
  101. package/dist/types/src/product-events.d.ts +2 -0
  102. package/dist/types/src/product-render.d.ts +17 -0
  103. package/dist/types/src/product-shape.d.ts +88 -0
  104. package/dist/types/src/product-state.d.ts +110 -0
  105. package/dist/types/src/product.d.ts +66 -0
  106. package/dist/types/src/repeaters.d.ts +16 -0
  107. package/dist/types/src/search.d.ts +33 -0
  108. package/dist/types/src/selling-plans.d.ts +64 -0
  109. package/dist/types/src/server.d.ts +88 -0
  110. package/dist/types/src/shopify-values.d.ts +46 -0
  111. package/dist/types/src/status.d.ts +33 -0
  112. package/dist/types/src/storage.d.ts +12 -0
  113. package/dist/types/src/store-html.d.ts +39 -0
  114. package/dist/types/src/storefront/catalog.d.ts +38 -0
  115. package/dist/types/src/storefront/json-ld.d.ts +20 -0
  116. package/dist/types/src/variant-render.d.ts +14 -0
  117. package/package.json +42 -22
  118. package/src/account/addresses-view.ts +58 -0
  119. package/src/account/answers.ts +79 -0
  120. package/src/account/markup.ts +36 -0
  121. package/src/account/operations.ts +145 -0
  122. package/src/account/order-lines.ts +75 -0
  123. package/src/account/order-list.ts +107 -0
  124. package/src/account/order-page.ts +137 -0
  125. package/src/account/page-answers.ts +317 -0
  126. package/src/account/page-events.ts +253 -0
  127. package/src/account/server-client.ts +183 -0
  128. package/src/account/subscriptions-view.ts +115 -0
  129. package/src/account-api.ts +98 -0
  130. package/src/account-marks.ts +35 -0
  131. package/src/account-pages.ts +81 -0
  132. package/src/account.ts +284 -0
  133. package/src/amounts.ts +64 -0
  134. package/src/analytics-shopify.ts +246 -0
  135. package/src/analytics.ts +315 -0
  136. package/src/api-version.ts +79 -0
  137. package/src/astro.ts +272 -0
  138. package/src/cart/cart-shape.ts +25 -0
  139. package/src/cart/checkout.ts +101 -0
  140. package/src/cart/drawer.ts +125 -0
  141. package/src/cart/mutations.ts +237 -0
  142. package/src/cart/render.ts +266 -0
  143. package/src/cart/store.ts +279 -0
  144. package/src/cart.ts +345 -0
  145. package/src/client.ts +391 -0
  146. package/src/collection.ts +175 -0
  147. package/src/config-sections.ts +284 -0
  148. package/src/config.ts +480 -0
  149. package/src/controls/cart-controls.ts +123 -0
  150. package/src/controls/discount.ts +75 -0
  151. package/src/controls/extras.ts +54 -0
  152. package/src/controls/navigation.ts +77 -0
  153. package/src/diagnose-markup.ts +298 -0
  154. package/src/diagnose.ts +294 -0
  155. package/src/dom.ts +268 -0
  156. package/src/extras.ts +136 -0
  157. package/src/fragments.ts +261 -0
  158. package/src/globals.d.ts +15 -0
  159. package/src/images.ts +91 -0
  160. package/src/index.ts +415 -0
  161. package/src/initial.ts +101 -0
  162. package/src/lazy-iife.ts +197 -0
  163. package/src/lazy.ts +42 -0
  164. package/src/loader.ts +363 -0
  165. package/src/market-codes.ts +6 -0
  166. package/src/markets-ui.ts +227 -0
  167. package/src/markets.ts +246 -0
  168. package/src/markup.ts +127 -0
  169. package/src/mounting.ts +264 -0
  170. package/src/page-market.ts +110 -0
  171. package/src/part-integrity.ts +7 -0
  172. package/src/parts/account-pages.ts +7 -0
  173. package/src/parts/account.ts +7 -0
  174. package/src/parts/analytics.ts +7 -0
  175. package/src/parts/diagnose.ts +7 -0
  176. package/src/parts/extras.ts +7 -0
  177. package/src/parts/markets.ts +7 -0
  178. package/src/parts/search.ts +7 -0
  179. package/src/presets/barba.ts +194 -0
  180. package/src/product-events.ts +115 -0
  181. package/src/product-render.ts +218 -0
  182. package/src/product-shape.ts +49 -0
  183. package/src/product-state.ts +318 -0
  184. package/src/product.ts +308 -0
  185. package/src/repeaters.ts +161 -0
  186. package/src/search.ts +305 -0
  187. package/src/selling-plans.ts +145 -0
  188. package/src/server.ts +211 -0
  189. package/src/shopify-values.ts +91 -0
  190. package/src/status.ts +106 -0
  191. package/src/storage.ts +34 -0
  192. package/src/store-html.ts +359 -0
  193. package/src/storefront/catalog.ts +196 -0
  194. package/src/storefront/json-ld.ts +144 -0
  195. package/src/variant-render.ts +139 -0
  196. package/dist/esm/chunks/account-Q6E2TFEV.js +0 -1
  197. package/dist/esm/chunks/account-pages-UZXR4R5F.js +0 -1
  198. package/dist/esm/chunks/analytics-IJBKATUP.js +0 -1
  199. package/dist/esm/chunks/chunk-4QJKLPYR.js +0 -1
  200. package/dist/esm/chunks/chunk-64TUD3QR.js +0 -1
  201. package/dist/esm/chunks/chunk-AJLPU2JC.js +0 -4
  202. package/dist/esm/chunks/chunk-FIOZPPC5.js +0 -1
  203. package/dist/esm/chunks/chunk-K2YEQAIH.js +0 -1
  204. package/dist/esm/chunks/chunk-KIDH76B7.js +0 -1
  205. package/dist/esm/chunks/chunk-PAYT5ZD7.js +0 -1
  206. package/dist/esm/chunks/chunk-RREHGH4A.js +0 -1
  207. package/dist/esm/chunks/chunk-SSJT43CJ.js +0 -1
  208. package/dist/esm/chunks/chunk-VC46IEJQ.js +0 -1
  209. package/dist/esm/chunks/diagnose-3WVAWNTX.js +0 -14
  210. package/dist/esm/chunks/extras-7I7WJX35.js +0 -1
  211. package/dist/esm/chunks/markets-ui-545XLFTE.js +0 -1
  212. package/dist/esm/chunks/search-NILMGYXG.js +0 -1
  213. package/src/account-pages.js +0 -318
  214. package/src/account.js +0 -385
  215. package/src/analytics.js +0 -232
  216. package/src/api-version.js +0 -35
  217. package/src/astro.js +0 -101
  218. package/src/cart.js +0 -559
  219. package/src/client.js +0 -131
  220. package/src/collection.js +0 -114
  221. package/src/config.js +0 -85
  222. package/src/diagnose.js +0 -246
  223. package/src/dom.js +0 -288
  224. package/src/extras.js +0 -63
  225. package/src/fragments.js +0 -144
  226. package/src/index.js +0 -395
  227. package/src/initial.js +0 -75
  228. package/src/lazy-iife.js +0 -46
  229. package/src/lazy.js +0 -18
  230. package/src/loader.js +0 -198
  231. package/src/markets-ui.js +0 -90
  232. package/src/markets.js +0 -133
  233. package/src/mounting.js +0 -154
  234. package/src/parts/account-pages.js +0 -3
  235. package/src/parts/account.js +0 -3
  236. package/src/parts/analytics.js +0 -3
  237. package/src/parts/diagnose.js +0 -3
  238. package/src/parts/extras.js +0 -3
  239. package/src/parts/markets.js +0 -3
  240. package/src/parts/search.js +0 -3
  241. package/src/presets/barba.js +0 -100
  242. package/src/product.js +0 -570
  243. package/src/search.js +0 -153
  244. package/src/selling-plans.js +0 -94
  245. package/src/server.js +0 -252
  246. package/src/status.js +0 -66
  247. package/types/astro.d.ts +0 -17
  248. package/types/barba.d.ts +0 -16
  249. package/types/index.d.ts +0 -426
  250. package/types/server.d.ts +0 -110
package/src/dom.js DELETED
@@ -1,288 +0,0 @@
1
- /**
2
- * DOM helpers. Attribute names go through attr() so the prefix is configurable
3
- * (e.g. prefix: 'wfs' for markup that uses wfs-* attributes).
4
- */
5
- import { cfg, warn } from './config.js';
6
-
7
- export const attr = (name) => `${cfg.prefix}-${name}`;
8
- export const sel = (name, value) =>
9
- value === undefined ? `[${attr(name)}]` : `[${attr(name)}="${value}"]`;
10
- export const get = (el, name) => el.getAttribute(attr(name));
11
- export const has = (el, name) => el.hasAttribute(attr(name));
12
- export const dataKey = () => `data-${cfg.prefix}-key`;
13
-
14
- /**
15
- * productPath '/product' -> /product/<handle>.
16
- * Templates: '/product?handle={handle}' (one static page for every product,
17
- * with tether-product="url:handle"), '/p/{id}' (numeric product id).
18
- */
19
- export function productUrl(handle, id) {
20
- const path = cfg.productPath || '/product';
21
- if (!/\{(handle|id)\}/.test(path)) return `${path.replace(/\/$/, '')}/${handle}`;
22
- const numeric = id ? String(id).split('/').pop() : '';
23
- return path.replace(/\{handle\}/g, encodeURIComponent(handle || '')).replace(/\{id\}/g, encodeURIComponent(numeric));
24
- }
25
-
26
- export const qsa = (root, s) => (root ? [...root.querySelectorAll(s)] : []);
27
-
28
- /** Elements in `root` whose nearest `scopeSelector` ancestor is `root` itself. */
29
- export const scoped = (root, s, scopeSelector) =>
30
- qsa(root, s).filter((el) => el.closest(scopeSelector) === root);
31
-
32
- export function emit(name, detail, target = document) {
33
- target.dispatchEvent(new CustomEvent(`${cfg.prefix}:${name}`, { detail, bubbles: true }));
34
- }
35
-
36
- /** 30 -> "30%" (en, nl) or "30 %" (de, fr), in the configured locale. */
37
- export function formatPercent(n, locale = cfg.locale) {
38
- if (!n) return '';
39
- try { return new Intl.NumberFormat(locale || undefined, { style: 'percent', maximumFractionDigits: 0 }).format(n / 100); } catch { return `${n}%`; }
40
- }
41
-
42
- export function formatMoney(money) {
43
- if (!money) return '';
44
- if (typeof cfg.formatMoney === 'function') return cfg.formatMoney(money, cfg.locale);
45
- const amount = parseFloat(money.amount);
46
- if (Number.isNaN(amount)) return '';
47
- try {
48
- return new Intl.NumberFormat(cfg.locale || undefined, {
49
- style: 'currency', currency: money.currencyCode,
50
- }).format(amount);
51
- } catch {
52
- return `${money.currencyCode} ${amount.toFixed(2)}`;
53
- }
54
- }
55
-
56
- export function formatDate(iso) {
57
- if (!iso) return '';
58
- try { return new Date(iso).toLocaleDateString(cfg.locale || undefined); } catch { return ''; }
59
- }
60
-
61
- /** Shopify CDN resize via max-width / max-height / crop attributes on the element. */
62
- /**
63
- * Shopify CDN image at a size: imageUrl(url, { width: 800 }). Use the same width as the element's
64
- * max-width attribute, so the browser script keeps the image instead of loading it again.
65
- */
66
- export function imageUrl(url, { width, height, crop } = {}) {
67
- if (!url) return url;
68
- try {
69
- const u = new URL(url);
70
- if (width) u.searchParams.set('width', String(width));
71
- if (height) u.searchParams.set('height', String(height));
72
- if (crop) u.searchParams.set('crop', crop);
73
- return u.toString();
74
- } catch { return url; }
75
- }
76
-
77
- export function cdnUrl(url, el) {
78
- if (!url || !el) return url;
79
- // max-width (Webflow's custom attribute), or valid HTML: tether-max-width / data-max-width.
80
- const g = (k) => el.getAttribute(k) ?? el.getAttribute(`${cfg.prefix}-${k}`) ?? el.getAttribute(`data-${k}`);
81
- return imageUrl(url, { width: g('max-width'), height: g('max-height'), crop: g('max-width') || g('max-height') ? g('crop') : null });
82
- }
83
-
84
- /** Same image, whatever size parameters: the server-rendered one is kept (no second download). */
85
- export const sameImage = (a, b) => Boolean(a && b) && a.split('?')[0] === b.split('?')[0];
86
-
87
- export function setImage(el, url, alt) {
88
- if (!url) return;
89
- const src = cdnUrl(url, el);
90
- if (el.tagName === 'IMG' && sameImage(el.getAttribute('src'), src)) {
91
- if (alt != null && !el.getAttribute('alt')) el.setAttribute('alt', alt);
92
- return;
93
- }
94
- if (el.tagName === 'IMG') {
95
- el.removeAttribute('srcset');
96
- el.removeAttribute('sizes');
97
- el.setAttribute('src', src);
98
- if (alt != null) el.setAttribute('alt', alt);
99
- if (!el.getAttribute('loading')) el.setAttribute('loading', 'lazy');
100
- } else {
101
- el.style.backgroundImage = `url("${src}")`;
102
- }
103
- }
104
-
105
- export function setText(el, v) {
106
- const text = v == null ? '' : String(v);
107
- if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') el.value = text;
108
- else el.textContent = text;
109
- }
110
-
111
- export const readText = (el) => (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' ? el.value : el.textContent);
112
-
113
- /** setText that reports changes to cfg.onValueChange (for scramble/roll effects). */
114
- export function setTextTracked(el, v) {
115
- const before = readText(el);
116
- setText(el, v);
117
- const after = readText(el);
118
- if (before !== after && el.dataset.tetherSeen && typeof cfg.onValueChange === 'function') {
119
- try { cfg.onValueChange(el, before); } catch { /* ignore */ }
120
- }
121
- el.dataset.tetherSeen = '1';
122
- }
123
-
124
- export function show(el, visible) {
125
- const k = 'tetherPrevDisplay';
126
- if (visible) {
127
- if (el.dataset.tetherShown !== undefined) return; // already visible, nothing to check
128
- el.dataset.tetherShown = '';
129
- el.removeAttribute('hidden');
130
- if (el.dataset[k] !== undefined) {
131
- el.style.display = el.dataset[k];
132
- delete el.dataset[k];
133
- } else if (el.style.display === 'none') {
134
- el.style.display = '';
135
- }
136
- // Hidden before the script ran (a class, an inline display:none, a hidden attribute): show it
137
- // as block or the value of tether-display.
138
- if (typeof getComputedStyle === 'function' && getComputedStyle(el).display === 'none') {
139
- el.style.display = el.getAttribute(`${cfg.prefix}-display`) || 'block';
140
- }
141
- } else {
142
- delete el.dataset.tetherShown;
143
- // Remember a real display value to restore, never 'none'.
144
- if (el.dataset[k] === undefined && el.style.display !== 'none') el.dataset[k] = el.style.display || '';
145
- el.style.display = 'none';
146
- }
147
- }
148
-
149
- export function setSelected(el, selected) {
150
- el.classList.toggle('is-selected', selected);
151
- el.setAttribute('aria-pressed', selected ? 'true' : 'false');
152
- if (el.tagName === 'INPUT' && (el.type === 'radio' || el.type === 'checkbox')) el.checked = selected;
153
- const input = el.querySelector?.('input[type="radio"],input[type="checkbox"]');
154
- if (input) input.checked = selected;
155
- }
156
-
157
- // ---------------------------------------------------------------------------
158
- // Repeaters: the first child in Webflow is the template, the rest is removed.
159
- // ---------------------------------------------------------------------------
160
-
161
- const templates = new WeakMap();
162
-
163
- /** True once a repeater's template has been captured (it may be empty after that). */
164
- export const hasTemplate = (container) => Boolean(templates.get(container));
165
-
166
- function template(container) {
167
- if (!templates.has(container)) {
168
- const tpl = container.firstElementChild?.cloneNode(true) ?? null;
169
- templates.set(container, tpl);
170
- container.innerHTML = '';
171
- }
172
- return templates.get(container);
173
- }
174
-
175
- /** Rebuilds the list on every render. Fine for images, tags, options. */
176
- export function repeat(container, items, render) {
177
- const tpl = template(container);
178
- if (!tpl) { if (items.length) warn('repeater without a template child', container); return; }
179
- container.innerHTML = '';
180
- items.forEach((item, i) => {
181
- const node = tpl.cloneNode(true);
182
- render(node, item, i);
183
- container.appendChild(node);
184
- });
185
- }
186
-
187
- /**
188
- * Keyed repeater: keeps existing nodes, inserts new ones, removes old ones.
189
- * Used for cart lines, so values can animate and lines can slide in/out.
190
- */
191
- export function repeatKeyed(container, items, keyOf, render, { onEnter, onLeave } = {}) {
192
- const tpl = template(container);
193
- if (!tpl) { warn('repeater without a template child', container); return; }
194
-
195
- const KEY = dataKey();
196
- const LEAVING = `data-${cfg.prefix}-leaving`;
197
- const live = () => [...container.children].filter((n) => n.hasAttribute(KEY) && !n.hasAttribute(LEAVING));
198
-
199
- const existing = new Map(live().map((n) => [n.getAttribute(KEY), n]));
200
- const wanted = [];
201
- const fresh = new Set();
202
-
203
- items.forEach((item, i) => {
204
- const k = String(keyOf(item, i));
205
- let node = existing.get(k);
206
- if (node) existing.delete(k);
207
- else {
208
- node = tpl.cloneNode(true);
209
- node.setAttribute(KEY, k);
210
- fresh.add(node);
211
- }
212
- render(node, item, i, fresh.has(node));
213
- wanted.push(node);
214
- });
215
-
216
- const staying = wanted.filter((n) => !fresh.has(n));
217
- const current = live().filter((n) => staying.includes(n));
218
- const orderOk = current.length === staying.length && current.every((n, i) => n === staying[i]);
219
-
220
- if (!orderOk) {
221
- existing.forEach((n) => n.remove());
222
- wanted.forEach((n) => container.appendChild(n));
223
- return;
224
- }
225
-
226
- wanted.forEach((node, i) => {
227
- if (!fresh.has(node)) return;
228
- const prev = wanted[i - 1];
229
- if (prev && prev.parentNode === container) prev.after(node);
230
- else container.prepend(node);
231
- if (onEnter) { try { onEnter(node); } catch { /* ignore */ } }
232
- });
233
-
234
- existing.forEach((node) => {
235
- node.setAttribute(LEAVING, '');
236
- node.removeAttribute(KEY);
237
- node.style.pointerEvents = 'none';
238
- node.inert = true;
239
- const done = () => node.remove();
240
- if (onLeave) { try { onLeave(node, done); } catch { done(); } } else done();
241
- });
242
- }
243
-
244
- // ---------------------------------------------------------------------------
245
- // Layout notifications: async renders change page height after your
246
- // page-transition hooks ran. Debounced, then cfg.onLayoutChange or a default.
247
- // ---------------------------------------------------------------------------
248
-
249
- let layoutTimer = null;
250
-
251
- export function notifyLayoutChanged(reason) {
252
- clearTimeout(layoutTimer);
253
- layoutTimer = setTimeout(() => {
254
- if (typeof cfg.onLayoutChange === 'function') cfg.onLayoutChange(reason);
255
- else if (window.ScrollTrigger?.refresh) window.ScrollTrigger.refresh();
256
- emit('layout-changed', { reason });
257
- }, 120);
258
- }
259
-
260
- export function watchImages(root, reason) {
261
- const imgs = qsa(root, 'img').filter((i) => !i.complete);
262
- if (!imgs.length) return;
263
- let left = imgs.length;
264
- const done = () => { if (--left === 0) notifyLayoutChanged(`${reason}:images`); };
265
- imgs.forEach((img) => {
266
- img.addEventListener('load', done, { once: true });
267
- img.addEventListener('error', done, { once: true });
268
- });
269
- }
270
-
271
- export function busy(el, fn) {
272
- if (el.getAttribute('aria-busy') === 'true') return Promise.resolve();
273
- el.setAttribute('aria-busy', 'true');
274
- return Promise.resolve().then(fn).finally(() => el.removeAttribute('aria-busy'));
275
- }
276
-
277
- export function parseJSONAttr(el, name) {
278
- const raw = el && get(el, name);
279
- if (!raw) return null;
280
- try { return JSON.parse(raw); } catch { warn(`invalid JSON in ${attr(name)}`, el); return null; }
281
- }
282
-
283
- /** {a: 1} or [{key, value}] -> [{key, value}] as Shopify wants it. */
284
- export function toAttributes(obj) {
285
- if (!obj) return [];
286
- if (Array.isArray(obj)) return obj.map((a) => ({ key: String(a.key), value: String(a.value ?? '') }));
287
- return Object.entries(obj).map(([key, value]) => ({ key, value: String(value ?? '') }));
288
- }
package/src/extras.js DELETED
@@ -1,63 +0,0 @@
1
- /**
2
- * Optional part with features that load when first needed: bundles (on the first "add set"
3
- * click), tab sync (on the first change in another tab) and shareable variant links (on the
4
- * first variant change, with variantUrl or tether-variant-url).
5
- */
6
- import { cfg, warn, reportError } from './config.js';
7
- import { sel, get, has, qsa, emit, parseJSONAttr, toAttributes } from './dom.js';
8
- import { scopes } from './product.js';
9
- import { Cart } from './cart.js';
10
-
11
- /**
12
- * [tether-group] + [tether-group-add]: adds every product scope inside the group in one request.
13
- * Bundles, build-a-box, "complete the set". Lines get attribute _group (and tether-line-attributes).
14
- */
15
- export async function addGroup(btn) {
16
- const group = btn.closest(sel('group'));
17
- if (!group) return null;
18
- const name = get(group, 'group');
19
- const extra = toAttributes(parseJSONAttr(group, 'line-attributes'));
20
- const groupAttrs = name ? [{ key: '_group', value: name }, ...extra] : extra;
21
- const lines = qsa(group, sel('product'))
22
- .map((el) => scopes.get(el))
23
- .filter((s) => s && s.variant?.availableForSale && s.quantity > 0)
24
- .map((s) => {
25
- const l = s.getLine();
26
- l.attributes = [...(l.attributes || []), ...groupAttrs];
27
- return l;
28
- });
29
- if (!lines.length) { warn('group-add: nothing selected'); return null; }
30
- try {
31
- const cart = await Cart.add(lines);
32
- emit('group-added', { group: name, lines }, group);
33
- return cart;
34
- } catch (err) { reportError(err, 'group add'); return null; }
35
- }
36
-
37
- /** Another tab changed the cart (reload it here) or the visitor's market (follow it). */
38
- export function onStorage(e) {
39
- if (e.key === cfg.storageKey || e.key === `${cfg.storageKey}:seen`) Cart.init();
40
- else if (e.key === `${cfg.prefix}:market:${cfg.domain}` && e.newValue) {
41
- try {
42
- const m = JSON.parse(e.newValue);
43
- if (m?.country) window.Tether?.setCountry(m.country, { language: m.language ?? null, locale: m.locale, remember: false }).catch(() => {});
44
- } catch { /* ignore */ }
45
- }
46
- }
47
-
48
- const urlKeys = new WeakMap(); // scope root -> last variant|plan, so the first render doesn't write
49
-
50
- /** ?variant= (and selling_plan=) follow the visitor's choice. */
51
- export function writeVariantUrl(root, { variant, sellingPlanId }) {
52
- const key = `${variant.id}|${sellingPlanId || ''}`;
53
- const before = urlKeys.get(root);
54
- urlKeys.set(root, key);
55
- if (!before || before === key) return;
56
- if (!has(root, 'variant-url') && !(cfg.variantUrl && document.querySelector(sel('product')) === root)) return;
57
- try {
58
- const u = new URL(window.location.href);
59
- u.searchParams.set('variant', variant.id.split('/').pop());
60
- if (sellingPlanId) u.searchParams.set('selling_plan', sellingPlanId.split('/').pop()); else u.searchParams.delete('selling_plan');
61
- window.history.replaceState(window.history.state, '', u);
62
- } catch { /* ignore */ }
63
- }
package/src/fragments.js DELETED
@@ -1,144 +0,0 @@
1
- /**
2
- * GraphQL fragments. Built at call time because counts and metafields come
3
- * from config. Operations put these AFTER the operation (see withContext).
4
- */
5
- import { cfg } from './config.js';
6
-
7
- const MONEY = 'amount currencyCode';
8
- const IMAGE = 'url altText width height';
9
-
10
- export function metafieldIdentifiers(o = cfg) {
11
- return (o.metafields || []).map((m) => {
12
- if (typeof m === 'string') {
13
- const i = m.indexOf('.');
14
- return { namespace: m.slice(0, i), key: m.slice(i + 1) };
15
- }
16
- return m;
17
- }).filter((m) => m.namespace && m.key);
18
- }
19
-
20
- function metafieldsField(o = cfg) {
21
- const ids = metafieldIdentifiers(o);
22
- if (!ids.length) return '';
23
- const list = ids
24
- .map((m) => `{namespace: ${JSON.stringify(m.namespace)}, key: ${JSON.stringify(m.key)}}`)
25
- .join(', ');
26
- return `metafields(identifiers: [${list}]) {
27
- namespace key type value
28
- reference { ... on MediaImage { image { ${IMAGE} } } }
29
- }`;
30
- }
31
-
32
- export const SELLING_PLAN_FIELDS = `
33
- id name description recurringDeliveries
34
- options { name value }
35
- priceAdjustments {
36
- orderCount
37
- adjustmentValue {
38
- __typename
39
- ... on SellingPlanPercentagePriceAdjustment { adjustmentPercentage }
40
- ... on SellingPlanFixedAmountPriceAdjustment { adjustmentAmount { ${MONEY} } }
41
- ... on SellingPlanFixedPriceAdjustment { price { ${MONEY} } }
42
- }
43
- }`;
44
-
45
- export const ALLOCATION_FIELDS = `
46
- sellingPlan { id }
47
- checkoutChargeAmount { ${MONEY} }
48
- remainingBalanceChargeAmount { ${MONEY} }
49
- priceAdjustments {
50
- price { ${MONEY} }
51
- compareAtPrice { ${MONEY} }
52
- perDeliveryPrice { ${MONEY} }
53
- unitPrice { ${MONEY} }
54
- }`;
55
-
56
- /** The product fields Tether renders. `o` = config (counts, metafields); the server module passes its own. */
57
- export function productFragment(o = cfg) {
58
- return `
59
- fragment TetherProduct on Product {
60
- id handle title description descriptionHtml productType vendor tags
61
- availableForSale publishedAt onlineStoreUrl requiresSellingPlan
62
- seo { title description }
63
- featuredImage { ${IMAGE} }
64
- images(first: ${o.imagesFirst}) { nodes { ${IMAGE} } }
65
- options { name optionValues { name swatch { color image { previewImage { ${IMAGE} } } } } }
66
- priceRange { minVariantPrice { ${MONEY} } maxVariantPrice { ${MONEY} } }
67
- compareAtPriceRange { minVariantPrice { ${MONEY} } maxVariantPrice { ${MONEY} } }
68
- ${metafieldsField(o)}
69
- sellingPlanGroups(first: ${o.sellingPlanGroupsFirst}) {
70
- nodes {
71
- name appName
72
- options { name values }
73
- sellingPlans(first: ${o.sellingPlansFirst}) { nodes { ${SELLING_PLAN_FIELDS} } }
74
- }
75
- }
76
- variants(first: ${o.variantsFirst}) {
77
- nodes {
78
- id title availableForSale currentlyNotInStock quantityAvailable sku barcode
79
- selectedOptions { name value }
80
- image { ${IMAGE} }
81
- price { ${MONEY} }
82
- compareAtPrice { ${MONEY} }
83
- unitPrice { ${MONEY} }
84
- sellingPlanAllocations(first: ${o.sellingPlansFirst}) { nodes { ${ALLOCATION_FIELDS} } }
85
- }
86
- }
87
- }`;
88
- }
89
-
90
- export function cartFragment() {
91
- return `
92
- fragment TetherCart on Cart {
93
- id checkoutUrl totalQuantity note createdAt updatedAt
94
- attributes { key value }
95
- buyerIdentity { countryCode email }
96
- discountCodes { code applicable }
97
- cost {
98
- subtotalAmount { ${MONEY} }
99
- totalAmount { ${MONEY} }
100
- checkoutChargeAmount { ${MONEY} }
101
- }
102
- lines(first: 100) {
103
- nodes {
104
- id quantity
105
- attributes { key value }
106
- cost {
107
- totalAmount { ${MONEY} }
108
- subtotalAmount { ${MONEY} }
109
- amountPerQuantity { ${MONEY} }
110
- compareAtAmountPerQuantity { ${MONEY} }
111
- }
112
- discountAllocations { discountedAmount { ${MONEY} } }
113
- sellingPlanAllocation {
114
- sellingPlan { id name description recurringDeliveries options { name value } }
115
- checkoutChargeAmount { ${MONEY} }
116
- priceAdjustments { price { ${MONEY} } compareAtPrice { ${MONEY} } perDeliveryPrice { ${MONEY} } }
117
- }
118
- merchandise {
119
- ... on ProductVariant {
120
- id title sku availableForSale requiresShipping quantityAvailable
121
- image { ${IMAGE} }
122
- price { ${MONEY} }
123
- compareAtPrice { ${MONEY} }
124
- selectedOptions { name value }
125
- product { id title handle vendor featuredImage { ${IMAGE} } }
126
- }
127
- }
128
- ... on ComponentizableCartLine {
129
- lineComponents {
130
- id quantity
131
- merchandise { ... on ProductVariant { id title product { title handle } } }
132
- }
133
- }
134
- }
135
- }
136
- }`;
137
- }
138
-
139
- export function collectionFields() {
140
- return `
141
- id handle title description descriptionHtml
142
- image { ${IMAGE} }
143
- seo { title description }`;
144
- }