@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.
- package/README.md +11 -11
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-UIPWJQ3Y.js +1 -0
- package/dist/esm/chunks/account-pages-6ROA6Y4V.js +1 -0
- package/dist/esm/chunks/analytics-2V632IRV.js +1 -0
- package/dist/esm/chunks/chunk-4GCXM764.js +1 -0
- package/dist/esm/chunks/chunk-56FCLJJD.js +1 -0
- package/dist/esm/chunks/chunk-5Z5O3767.js +1 -0
- package/dist/esm/chunks/chunk-6OZJCAGZ.js +1 -0
- package/dist/esm/chunks/chunk-A5SYCVQM.js +1 -0
- package/dist/esm/chunks/chunk-BA4LQJJQ.js +1 -0
- package/dist/esm/chunks/chunk-BFTZR4OB.js +4 -0
- package/dist/esm/chunks/chunk-C5JYQCWH.js +1 -0
- package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
- package/dist/esm/chunks/chunk-MPDWM2NN.js +1 -0
- package/dist/esm/chunks/chunk-T44MUL4Y.js +1 -0
- package/dist/esm/chunks/chunk-YLUULATY.js +1 -0
- package/dist/esm/chunks/diagnose-3FEKRIEK.js +14 -0
- package/dist/esm/chunks/extras-6POFBNOP.js +1 -0
- package/dist/esm/chunks/markets-ui-FD6NI2OG.js +1 -0
- package/dist/esm/chunks/search-UJ73YBQ5.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +2 -1
- package/dist/presets/barba.esm.js +55 -39
- package/dist/tether.account-pages.js +781 -495
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +462 -381
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +299 -194
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +354 -262
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +82 -45
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +3653 -2116
- package/dist/tether.markets.js +238 -150
- package/dist/tether.markets.min.js +1 -1
- package/dist/tether.min.js +3 -3
- package/dist/tether.min.js.map +4 -4
- package/dist/tether.search.js +207 -136
- package/dist/tether.search.min.js +1 -1
- package/dist/types/server/src/shared/account-orders.d.ts +44 -0
- package/dist/types/server/src/shared/account-routes.d.ts +153 -0
- package/dist/types/server/src/shared/api-versions.d.ts +13 -0
- package/dist/types/server/src/shared/error-message.d.ts +2 -0
- package/dist/types/server/src/shared/json.d.ts +25 -0
- package/dist/types/server/src/shared/market-codes.d.ts +16 -0
- package/dist/types/src/account/addresses-view.d.ts +5 -0
- package/dist/types/src/account/answers.d.ts +40 -0
- package/dist/types/src/account/markup.d.ts +19 -0
- package/dist/types/src/account/operations.d.ts +41 -0
- package/dist/types/src/account/order-lines.d.ts +13 -0
- package/dist/types/src/account/order-list.d.ts +12 -0
- package/dist/types/src/account/order-page.d.ts +6 -0
- package/dist/types/src/account/page-answers.d.ts +182 -0
- package/dist/types/src/account/page-events.d.ts +2 -0
- package/dist/types/src/account/server-client.d.ts +60 -0
- package/dist/types/src/account/subscriptions-view.d.ts +2 -0
- package/dist/types/src/account-api.d.ts +18 -0
- package/dist/types/src/account-marks.d.ts +15 -0
- package/dist/types/src/account-pages.d.ts +8 -0
- package/dist/types/src/account.d.ts +64 -0
- package/dist/types/src/amounts.d.ts +12 -0
- package/dist/types/src/analytics-shopify.d.ts +41 -0
- package/dist/types/src/analytics.d.ts +2 -0
- package/dist/types/src/api-version.d.ts +28 -0
- package/dist/types/src/astro.d.ts +42 -0
- package/dist/types/src/cart/cart-shape.d.ts +43 -0
- package/dist/types/src/cart/checkout.d.ts +16 -0
- package/dist/types/src/cart/drawer.d.ts +18 -0
- package/dist/types/src/cart/mutations.d.ts +79 -0
- package/dist/types/src/cart/render.d.ts +17 -0
- package/dist/types/src/cart/store.d.ts +182 -0
- package/dist/types/src/cart.d.ts +106 -0
- package/dist/types/src/client.d.ts +100 -0
- package/dist/types/src/collection.d.ts +25 -0
- package/dist/types/src/config-sections.d.ts +40 -0
- package/dist/types/src/config.d.ts +185 -0
- package/dist/types/src/controls/cart-controls.d.ts +2 -0
- package/dist/types/src/controls/discount.d.ts +2 -0
- package/dist/types/src/controls/extras.d.ts +5 -0
- package/dist/types/src/controls/navigation.d.ts +12 -0
- package/dist/types/src/diagnose-markup.d.ts +19 -0
- package/dist/types/src/diagnose.d.ts +60 -0
- package/dist/types/src/dom.d.ts +75 -0
- package/dist/types/src/extras.d.ts +21 -0
- package/dist/types/src/fragments.d.ts +158 -0
- package/dist/types/src/globals.d.ts +15 -0
- package/dist/types/src/images.d.ts +20 -0
- package/dist/types/src/index.d.ts +167 -0
- package/dist/types/src/initial.d.ts +56 -0
- package/dist/types/src/lazy.d.ts +28 -0
- package/dist/types/src/loader.d.ts +84 -0
- package/dist/types/src/market-codes.d.ts +6 -0
- package/dist/types/src/markets-ui.d.ts +32 -0
- package/dist/types/src/markets.d.ts +72 -0
- package/dist/types/src/markup.d.ts +131 -0
- package/dist/types/src/mounting.d.ts +46 -0
- package/dist/types/src/page-market.d.ts +27 -0
- package/dist/types/src/presets/barba.d.ts +56 -0
- package/dist/types/src/product-events.d.ts +2 -0
- package/dist/types/src/product-render.d.ts +17 -0
- package/dist/types/src/product-shape.d.ts +88 -0
- package/dist/types/src/product-state.d.ts +110 -0
- package/dist/types/src/product.d.ts +66 -0
- package/dist/types/src/repeaters.d.ts +16 -0
- package/dist/types/src/search.d.ts +33 -0
- package/dist/types/src/selling-plans.d.ts +64 -0
- package/dist/types/src/server.d.ts +88 -0
- package/dist/types/src/shopify-values.d.ts +46 -0
- package/dist/types/src/status.d.ts +33 -0
- package/dist/types/src/storage.d.ts +12 -0
- package/dist/types/src/store-html.d.ts +39 -0
- package/dist/types/src/storefront/catalog.d.ts +38 -0
- package/dist/types/src/storefront/json-ld.d.ts +20 -0
- package/dist/types/src/variant-render.d.ts +14 -0
- package/package.json +42 -22
- package/src/account/addresses-view.ts +58 -0
- package/src/account/answers.ts +79 -0
- package/src/account/markup.ts +36 -0
- package/src/account/operations.ts +145 -0
- package/src/account/order-lines.ts +75 -0
- package/src/account/order-list.ts +107 -0
- package/src/account/order-page.ts +137 -0
- package/src/account/page-answers.ts +317 -0
- package/src/account/page-events.ts +253 -0
- package/src/account/server-client.ts +183 -0
- package/src/account/subscriptions-view.ts +115 -0
- package/src/account-api.ts +98 -0
- package/src/account-marks.ts +35 -0
- package/src/account-pages.ts +81 -0
- package/src/account.ts +284 -0
- package/src/amounts.ts +64 -0
- package/src/analytics-shopify.ts +246 -0
- package/src/analytics.ts +315 -0
- package/src/api-version.ts +79 -0
- package/src/astro.ts +272 -0
- package/src/cart/cart-shape.ts +25 -0
- package/src/cart/checkout.ts +101 -0
- package/src/cart/drawer.ts +125 -0
- package/src/cart/mutations.ts +237 -0
- package/src/cart/render.ts +266 -0
- package/src/cart/store.ts +279 -0
- package/src/cart.ts +345 -0
- package/src/client.ts +391 -0
- package/src/collection.ts +175 -0
- package/src/config-sections.ts +284 -0
- package/src/config.ts +480 -0
- package/src/controls/cart-controls.ts +123 -0
- package/src/controls/discount.ts +75 -0
- package/src/controls/extras.ts +54 -0
- package/src/controls/navigation.ts +77 -0
- package/src/diagnose-markup.ts +298 -0
- package/src/diagnose.ts +294 -0
- package/src/dom.ts +268 -0
- package/src/extras.ts +136 -0
- package/src/fragments.ts +261 -0
- package/src/globals.d.ts +15 -0
- package/src/images.ts +91 -0
- package/src/index.ts +415 -0
- package/src/initial.ts +101 -0
- package/src/lazy-iife.ts +197 -0
- package/src/lazy.ts +42 -0
- package/src/loader.ts +363 -0
- package/src/market-codes.ts +6 -0
- package/src/markets-ui.ts +227 -0
- package/src/markets.ts +246 -0
- package/src/markup.ts +127 -0
- package/src/mounting.ts +264 -0
- package/src/page-market.ts +110 -0
- package/src/part-integrity.ts +7 -0
- package/src/parts/account-pages.ts +7 -0
- package/src/parts/account.ts +7 -0
- package/src/parts/analytics.ts +7 -0
- package/src/parts/diagnose.ts +7 -0
- package/src/parts/extras.ts +7 -0
- package/src/parts/markets.ts +7 -0
- package/src/parts/search.ts +7 -0
- package/src/presets/barba.ts +194 -0
- package/src/product-events.ts +115 -0
- package/src/product-render.ts +218 -0
- package/src/product-shape.ts +49 -0
- package/src/product-state.ts +318 -0
- package/src/product.ts +308 -0
- package/src/repeaters.ts +161 -0
- package/src/search.ts +305 -0
- package/src/selling-plans.ts +145 -0
- package/src/server.ts +211 -0
- package/src/shopify-values.ts +91 -0
- package/src/status.ts +106 -0
- package/src/storage.ts +34 -0
- package/src/store-html.ts +359 -0
- package/src/storefront/catalog.ts +196 -0
- package/src/storefront/json-ld.ts +144 -0
- package/src/variant-render.ts +139 -0
- package/dist/esm/chunks/account-Q6E2TFEV.js +0 -1
- package/dist/esm/chunks/account-pages-UZXR4R5F.js +0 -1
- package/dist/esm/chunks/analytics-IJBKATUP.js +0 -1
- package/dist/esm/chunks/chunk-4QJKLPYR.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-KIDH76B7.js +0 -1
- package/dist/esm/chunks/chunk-PAYT5ZD7.js +0 -1
- package/dist/esm/chunks/chunk-RREHGH4A.js +0 -1
- package/dist/esm/chunks/chunk-SSJT43CJ.js +0 -1
- package/dist/esm/chunks/chunk-VC46IEJQ.js +0 -1
- package/dist/esm/chunks/diagnose-3WVAWNTX.js +0 -14
- package/dist/esm/chunks/extras-7I7WJX35.js +0 -1
- package/dist/esm/chunks/markets-ui-545XLFTE.js +0 -1
- package/dist/esm/chunks/search-NILMGYXG.js +0 -1
- package/src/account-pages.js +0 -318
- package/src/account.js +0 -385
- package/src/analytics.js +0 -232
- package/src/api-version.js +0 -35
- package/src/astro.js +0 -101
- package/src/cart.js +0 -559
- package/src/client.js +0 -131
- package/src/collection.js +0 -114
- package/src/config.js +0 -85
- package/src/diagnose.js +0 -246
- package/src/dom.js +0 -288
- package/src/extras.js +0 -63
- package/src/fragments.js +0 -144
- package/src/index.js +0 -395
- package/src/initial.js +0 -75
- package/src/lazy-iife.js +0 -46
- package/src/lazy.js +0 -18
- package/src/loader.js +0 -198
- package/src/markets-ui.js +0 -90
- package/src/markets.js +0 -133
- package/src/mounting.js +0 -154
- package/src/parts/account-pages.js +0 -3
- package/src/parts/account.js +0 -3
- package/src/parts/analytics.js +0 -3
- package/src/parts/diagnose.js +0 -3
- package/src/parts/extras.js +0 -3
- package/src/parts/markets.js +0 -3
- package/src/parts/search.js +0 -3
- package/src/presets/barba.js +0 -100
- package/src/product.js +0 -570
- package/src/search.js +0 -153
- package/src/selling-plans.js +0 -94
- package/src/server.js +0 -252
- package/src/status.js +0 -66
- package/types/astro.d.ts +0 -17
- package/types/barba.d.ts +0 -16
- package/types/index.d.ts +0 -426
- 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
|
-
}
|