@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.
- package/README.md +28 -28
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-CXMA5RPZ.js +1 -0
- package/dist/esm/chunks/account-pages-IZO7FEOZ.js +1 -0
- package/dist/esm/chunks/analytics-PG5FPRDN.js +1 -0
- package/dist/esm/chunks/chunk-4HD4J5JC.js +1 -0
- package/dist/esm/chunks/chunk-6TFMLT4A.js +4 -0
- package/dist/esm/chunks/chunk-BBVUVPMO.js +1 -0
- package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
- package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
- package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
- package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
- package/dist/esm/chunks/chunk-N3V45JPS.js +1 -0
- package/dist/esm/chunks/chunk-RPMBCAGS.js +1 -0
- package/dist/esm/chunks/chunk-VFNHIGCS.js +1 -0
- package/dist/esm/chunks/chunk-WT7RGGJU.js +1 -0
- package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
- package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
- package/dist/esm/chunks/diagnose-XCEBQKBO.js +14 -0
- package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
- package/dist/esm/chunks/markets-ui-M4IQ62CT.js +1 -0
- package/dist/esm/chunks/search-A5DS2IOS.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +2 -2
- package/dist/presets/barba.esm.js +19 -23
- package/dist/tether.account-pages.js +332 -367
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +253 -492
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +82 -89
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +52 -34
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +42 -37
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +1361 -935
- package/dist/tether.markets.js +89 -91
- package/dist/tether.markets.min.js +1 -1
- package/dist/tether.min.js +0 -0
- package/dist/tether.min.js.map +4 -4
- package/dist/tether.search.js +60 -69
- 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/cart-id.d.ts +2 -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 +85 -0
- package/dist/types/src/cart/render.d.ts +17 -0
- package/dist/types/src/cart/store.d.ts +185 -0
- package/dist/types/src/cart.d.ts +106 -0
- package/dist/types/src/client.d.ts +134 -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 +190 -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 +90 -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 +24 -24
- package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
- package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
- package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
- package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
- package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
- package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
- package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
- package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
- package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
- package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
- package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
- package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
- package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
- package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
- package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
- package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
- package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
- package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
- package/dist/esm/chunks/search-OU75RNQL.js +0 -1
- package/src/account/addresses-view.js +0 -92
- package/src/account/browser-session.js +0 -359
- package/src/account/markup.js +0 -48
- package/src/account/operations.js +0 -180
- package/src/account/order-lines.js +0 -96
- package/src/account/order-list.js +0 -91
- package/src/account/order-page.js +0 -192
- package/src/account/page-events.js +0 -288
- package/src/account/server-client.js +0 -172
- package/src/account/shared.js +0 -111
- package/src/account/shopify-auth.js +0 -150
- package/src/account/subscriptions-view.js +0 -158
- package/src/account-api.js +0 -118
- package/src/account-links.js +0 -35
- package/src/account-pages.js +0 -89
- package/src/account.js +0 -342
- package/src/amounts.js +0 -36
- package/src/analytics-shopify.js +0 -241
- package/src/analytics.js +0 -352
- package/src/api-version.js +0 -85
- package/src/astro.js +0 -266
- package/src/cart/checkout.js +0 -97
- package/src/cart/drawer.js +0 -138
- package/src/cart/mutations.js +0 -234
- package/src/cart/render.js +0 -294
- package/src/cart/store.js +0 -326
- package/src/cart.js +0 -391
- package/src/client.js +0 -267
- package/src/collection.js +0 -183
- package/src/config.js +0 -349
- package/src/controls/cart-controls.js +0 -155
- package/src/controls/discount.js +0 -96
- package/src/controls/extras.js +0 -63
- package/src/controls/navigation.js +0 -83
- package/src/diagnose-markup.js +0 -333
- package/src/diagnose.js +0 -295
- package/src/dom.js +0 -287
- package/src/extras.js +0 -145
- package/src/fragments.js +0 -237
- package/src/images.js +0 -104
- package/src/index.js +0 -353
- package/src/initial.js +0 -87
- package/src/lazy-iife.js +0 -116
- package/src/lazy.js +0 -39
- package/src/loader.js +0 -337
- package/src/market-codes.js +0 -35
- package/src/markets-ui.js +0 -204
- package/src/markets.js +0 -334
- package/src/markup.js +0 -64
- package/src/mounting.js +0 -277
- package/src/page-market.js +0 -116
- package/src/parts/account-pages.js +0 -6
- package/src/parts/account.js +0 -6
- package/src/parts/analytics.js +0 -6
- package/src/parts/diagnose.js +0 -6
- package/src/parts/extras.js +0 -6
- package/src/parts/markets.js +0 -6
- package/src/parts/search.js +0 -6
- package/src/presets/barba.js +0 -190
- package/src/product-events.js +0 -130
- package/src/product-render.js +0 -257
- package/src/product-state.js +0 -318
- package/src/product.js +0 -344
- package/src/repeaters.js +0 -173
- package/src/search.js +0 -323
- package/src/selling-plans.js +0 -154
- package/src/server.js +0 -215
- package/src/shopify-values.js +0 -89
- package/src/status.js +0 -126
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +0 -199
- package/src/storefront/json-ld.js +0 -167
- package/src/variant-render.js +0 -165
- package/types/astro.d.ts +0 -28
- package/types/barba.d.ts +0 -16
- package/types/index.d.ts +0 -577
- package/types/server.d.ts +0 -182
package/src/page-market.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The market the page uses: a page built for one market (`<meta name="tether-page-country">`) wins, then the visitor's
|
|
3
|
-
* saved or detected market, then the configured one. Init applies it to `cfg` once. Astro's ClientRouter keeps the
|
|
4
|
-
* script running across pages, so each arrival asks again: the next page may be built for another market, or for none.
|
|
5
|
-
*/
|
|
6
|
-
import { cfg, log, reportError } from './config.js';
|
|
7
|
-
import { loadPart } from './lazy.js';
|
|
8
|
-
import { toCountry, toLanguage } from './market-codes.js';
|
|
9
|
-
import { readSavedMarket } from './markets.js';
|
|
10
|
-
/** @import { TetherOptions } from './config.js' */
|
|
11
|
-
/** @import { Market, DetectedMarket } from './markets.js' */
|
|
12
|
-
/** @import { SetCountryOptions } from './markets-ui.js' */
|
|
13
|
-
/** @import { ShopifyCart } from './cart/store.js' */
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {object} ConfiguredMarket The market of the config, before a page's or visitor's market applied.
|
|
17
|
-
* @property {string | undefined} country
|
|
18
|
-
* @property {string | undefined} language
|
|
19
|
-
* @property {string | undefined} locale
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/** @type {ConfiguredMarket} */
|
|
23
|
-
let configuredMarket = { country: undefined, language: undefined, locale: undefined };
|
|
24
|
-
|
|
25
|
-
/** Whether the page arrived at last was built for its own market, which the next page without one switches back from. */
|
|
26
|
-
let lastPageHadOwnMarket = false;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* `<meta name="tether-page-country" content="DE">`, with `tether-page-language` and `tether-page-locale`. Never
|
|
30
|
-
* remembered: it belongs to the page, not to the visitor.
|
|
31
|
-
*
|
|
32
|
-
* @returns {Market | null} `null` when the page has no valid country of its own.
|
|
33
|
-
*/
|
|
34
|
-
function pageMarket() {
|
|
35
|
-
/** @param {string} name */
|
|
36
|
-
const meta = (name) =>
|
|
37
|
-
document.querySelector(`meta[name="${cfg.prefix}-page-${name}"]`)?.getAttribute('content') || undefined;
|
|
38
|
-
const country = toCountry(meta('country'));
|
|
39
|
-
if (!country) return null;
|
|
40
|
-
return { country, language: toLanguage(meta('language')) ?? undefined, locale: meta('locale') };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Without `markets.detect` only the visitor's saved choice applies, read without loading the markets part.
|
|
45
|
-
*
|
|
46
|
-
* @returns {Promise<Market | DetectedMarket | null>}
|
|
47
|
-
*/
|
|
48
|
-
async function visitorMarket() {
|
|
49
|
-
const markets = cfg.markets ?? {};
|
|
50
|
-
if (markets.detect === false) return null;
|
|
51
|
-
if (!markets.detect) return markets.remember === false ? null : readSavedMarket();
|
|
52
|
-
try {
|
|
53
|
-
return (await loadPart('markets')).detectMarket(markets);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
reportError(error, 'markets');
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Sets `cfg`'s market for the first page, over the configured one.
|
|
62
|
-
*
|
|
63
|
-
* @param {TetherOptions} overrides What `init()` was called with: a `locale` there wins over the visitor's.
|
|
64
|
-
* @returns {Promise<void>}
|
|
65
|
-
*/
|
|
66
|
-
export async function applyStartMarket(overrides) {
|
|
67
|
-
configuredMarket = { country: cfg.country, language: cfg.language, locale: cfg.locale };
|
|
68
|
-
const page = pageMarket();
|
|
69
|
-
lastPageHadOwnMarket = page !== null;
|
|
70
|
-
if (page) {
|
|
71
|
-
cfg.country = page.country;
|
|
72
|
-
cfg.language = page.language;
|
|
73
|
-
if (page.locale) cfg.locale = page.locale;
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const market = await visitorMarket();
|
|
77
|
-
if (!market) return;
|
|
78
|
-
if (market.country) cfg.country = market.country;
|
|
79
|
-
if (market.language) cfg.language = market.language;
|
|
80
|
-
if (market.locale && !overrides.locale) cfg.locale = market.locale;
|
|
81
|
-
log('market', market);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @param {string | undefined} a
|
|
86
|
-
* @param {string | undefined} b
|
|
87
|
-
*/
|
|
88
|
-
const sameCode = (a, b) => (a ?? '').toUpperCase() === (b ?? '').toUpperCase();
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* The market to switch to on arriving at the current page. A page built for a market switches to it; the first page
|
|
92
|
-
* without one after such a page switches back to the visitor's own choice, else to the configured market.
|
|
93
|
-
*
|
|
94
|
-
* @returns {{ country: string, language: string | undefined, locale: string | undefined } | null} `null` when the page
|
|
95
|
-
* stays in the market in use.
|
|
96
|
-
*/
|
|
97
|
-
export function marketForArrival() {
|
|
98
|
-
const page = pageMarket();
|
|
99
|
-
const leftOwnMarket = page === null && lastPageHadOwnMarket;
|
|
100
|
-
lastPageHadOwnMarket = page !== null;
|
|
101
|
-
const target = page ?? (leftOwnMarket ? (readSavedMarket() ?? configuredMarket) : null);
|
|
102
|
-
if (!target?.country) return null;
|
|
103
|
-
if (sameCode(target.country, cfg.country) && sameCode(target.language, cfg.language)) return null;
|
|
104
|
-
return { country: target.country, language: target.language, locale: target.locale };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* `Tether.setCountry('DE')`: loads the markets part on first use.
|
|
109
|
-
*
|
|
110
|
-
* @param {string} code
|
|
111
|
-
* @param {SetCountryOptions} [options]
|
|
112
|
-
* @returns {Promise<ShopifyCart | null>} The cart once everything is in the new market.
|
|
113
|
-
*/
|
|
114
|
-
export function setCountry(code, options) {
|
|
115
|
-
return loadPart('markets').then((part) => part.setCountry(code, options));
|
|
116
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// The <script> build's tether.account-pages.js: registers the part where the core script's loader (lazy-iife.js) finds it.
|
|
2
|
-
import * as part from '../account-pages.js';
|
|
3
|
-
|
|
4
|
-
const { Tether } = /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */ (window);
|
|
5
|
-
if (!Tether) throw new Error('tether.account-pages.js runs after the Tether script, which loads it');
|
|
6
|
-
Tether.__parts['account-pages'] = part;
|
package/src/parts/account.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// The <script> build's tether.account.js: registers the part where the core script's loader (lazy-iife.js) finds it.
|
|
2
|
-
import * as part from '../account.js';
|
|
3
|
-
|
|
4
|
-
const { Tether } = /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */ (window);
|
|
5
|
-
if (!Tether) throw new Error('tether.account.js runs after the Tether script, which loads it');
|
|
6
|
-
Tether.__parts['account'] = part;
|
package/src/parts/analytics.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// The <script> build's tether.analytics.js: registers the part where the core script's loader (lazy-iife.js) finds it.
|
|
2
|
-
import * as part from '../analytics.js';
|
|
3
|
-
|
|
4
|
-
const { Tether } = /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */ (window);
|
|
5
|
-
if (!Tether) throw new Error('tether.analytics.js runs after the Tether script, which loads it');
|
|
6
|
-
Tether.__parts['analytics'] = part;
|
package/src/parts/diagnose.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// The <script> build's tether.diagnose.js: registers the part where the core script's loader (lazy-iife.js) finds it.
|
|
2
|
-
import * as part from '../diagnose.js';
|
|
3
|
-
|
|
4
|
-
const { Tether } = /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */ (window);
|
|
5
|
-
if (!Tether) throw new Error('tether.diagnose.js runs after the Tether script, which loads it');
|
|
6
|
-
Tether.__parts['diagnose'] = part;
|
package/src/parts/extras.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// The <script> build's tether.extras.js: registers the part where the core script's loader (lazy-iife.js) finds it.
|
|
2
|
-
import * as part from '../extras.js';
|
|
3
|
-
|
|
4
|
-
const { Tether } = /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */ (window);
|
|
5
|
-
if (!Tether) throw new Error('tether.extras.js runs after the Tether script, which loads it');
|
|
6
|
-
Tether.__parts['extras'] = part;
|
package/src/parts/markets.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// The <script> build's tether.markets.js: registers the part where the core script's loader (lazy-iife.js) finds it.
|
|
2
|
-
import * as part from '../markets-ui.js';
|
|
3
|
-
|
|
4
|
-
const { Tether } = /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */ (window);
|
|
5
|
-
if (!Tether) throw new Error('tether.markets.js runs after the Tether script, which loads it');
|
|
6
|
-
Tether.__parts['markets'] = part;
|
package/src/parts/search.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// The <script> build's tether.search.js: registers the part where the core script's loader (lazy-iife.js) finds it.
|
|
2
|
-
import * as part from '../search.js';
|
|
3
|
-
|
|
4
|
-
const { Tether } = /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */ (window);
|
|
5
|
-
if (!Tether) throw new Error('tether.search.js runs after the Tether script, which loads it');
|
|
6
|
-
Tether.__parts['search'] = part;
|
package/src/presets/barba.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preset for sites with page transitions (Barba, Swup), smooth scroll (Lenis) and GSAP. Nothing is read from globals
|
|
3
|
-
* except GSAP and ScrollTrigger by default: pass in what your site has.
|
|
4
|
-
*
|
|
5
|
-
* import Tether from "@occluze/tether";
|
|
6
|
-
* import { barbaPreset } from "@occluze/tether/presets/barba";
|
|
7
|
-
*
|
|
8
|
-
* Tether.init({
|
|
9
|
-
* ...window.TETHER_CONFIG,
|
|
10
|
-
* ...barbaPreset({
|
|
11
|
-
* lenis: () => myLenis, // resized after async renders
|
|
12
|
-
* lockScroll: (drawer) => myLock.lock(drawer), // when the cart opens
|
|
13
|
-
* unlockScroll: () => myLock.unlock(), // when the cart closes
|
|
14
|
-
* onValueChange: (el, previous) => {}, // e.g. a text scramble
|
|
15
|
-
* }),
|
|
16
|
-
* });
|
|
17
|
-
*
|
|
18
|
-
* // once per page container, e.g. in Barba's beforeEnter AND once():
|
|
19
|
-
* Tether.mount(container);
|
|
20
|
-
* // when leaving a page:
|
|
21
|
-
* Tether.unmount();
|
|
22
|
-
*/
|
|
23
|
-
/** @import { TetherConfig } from '../config.js' */
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* GSAP isn't a dependency, so the two calls this preset makes are described here.
|
|
27
|
-
*
|
|
28
|
-
* @typedef {Record<string, string | number | (() => void)>} TweenVars
|
|
29
|
-
* @typedef {object} Gsap
|
|
30
|
-
* @property {(target: HTMLElement, from: TweenVars, to: TweenVars) => unknown} fromTo
|
|
31
|
-
* @property {(target: HTMLElement, to: TweenVars) => unknown} to
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @typedef {object} BarbaPresetOptions Each getter may also be the value itself.
|
|
36
|
-
* @property {() => unknown} [gsap] Default `window.gsap`.
|
|
37
|
-
* @property {(() => unknown) | null} [lenis] Resized after async renders.
|
|
38
|
-
* @property {() => unknown} [scrollTrigger] Default `window.ScrollTrigger`, refreshed after async renders.
|
|
39
|
-
* @property {((drawer: HTMLElement) => void) | null} [lockScroll] When the cart opens.
|
|
40
|
-
* @property {(() => void) | null} [unlockScroll] When the cart closes.
|
|
41
|
-
* @property {(() => void) | null} [onResize] After a cart line animated in or out.
|
|
42
|
-
* @property {((el: HTMLElement, previousText: string) => void) | null} [onValueChange]
|
|
43
|
-
* @property {boolean} [animateLines] Animate cart lines in and out (default true).
|
|
44
|
-
* @property {number} [lineDuration] Seconds.
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @typedef {Pick<TetherConfig, 'autoMount' | 'onLayoutChange' | 'onCartOpen' | 'onCartClose' | 'onLineEnter'
|
|
49
|
-
* | 'onLineLeave' | 'onValueChange'>} BarbaPresetConfig
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
const LINE_DURATION_SECONDS = 0.3;
|
|
53
|
-
/** @returns {TweenVars} A new object each time: GSAP writes into the vars it is given. */
|
|
54
|
-
const collapsed = () => ({
|
|
55
|
-
height: 0,
|
|
56
|
-
paddingTop: 0,
|
|
57
|
-
paddingBottom: 0,
|
|
58
|
-
borderBottomWidth: 0,
|
|
59
|
-
opacity: 0,
|
|
60
|
-
overflow: 'hidden',
|
|
61
|
-
});
|
|
62
|
-
const ANIMATED_PROPS = 'height,paddingTop,paddingBottom,borderBottomWidth,opacity,overflow';
|
|
63
|
-
|
|
64
|
-
const reducedMotion = () =>
|
|
65
|
-
typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* @param {unknown} option
|
|
69
|
-
* @returns {unknown} What a getter option returns, or the option itself when it is the value.
|
|
70
|
-
*/
|
|
71
|
-
function current(option) {
|
|
72
|
-
if (typeof option !== 'function') return option;
|
|
73
|
-
// Every function option of this preset is a getter that takes no arguments.
|
|
74
|
-
const getter = /** @type {() => unknown} */ (option);
|
|
75
|
-
return getter();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Calls `target[method]()` when it has one: Lenis and ScrollTrigger are optional and versions differ.
|
|
80
|
-
*
|
|
81
|
-
* @param {unknown} target
|
|
82
|
-
* @param {string} method
|
|
83
|
-
*/
|
|
84
|
-
function callIfPresent(target, method) {
|
|
85
|
-
if (typeof target !== 'object' || target === null) return;
|
|
86
|
-
/** @type {unknown} */
|
|
87
|
-
const fn = Reflect.get(target, method);
|
|
88
|
-
// A method of `target`, called on it.
|
|
89
|
-
if (typeof fn === 'function') /** @type {(this: unknown) => unknown} */ (fn).call(target);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @param {unknown} value
|
|
94
|
-
* @returns {value is Gsap}
|
|
95
|
-
*/
|
|
96
|
-
const isGsap = (value) =>
|
|
97
|
-
typeof value === 'object' &&
|
|
98
|
-
value !== null &&
|
|
99
|
-
typeof Reflect.get(value, 'fromTo') === 'function' &&
|
|
100
|
-
typeof Reflect.get(value, 'to') === 'function';
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Hooks for sites with page transitions, smooth scroll and GSAP: spread into `Tether.init()`.
|
|
104
|
-
*
|
|
105
|
-
* @param {BarbaPresetOptions} [options]
|
|
106
|
-
* @returns {BarbaPresetConfig}
|
|
107
|
-
*/
|
|
108
|
-
export function barbaPreset(options = {}) {
|
|
109
|
-
const {
|
|
110
|
-
gsap = () => /** @type {unknown} */ (Reflect.get(window, 'gsap')),
|
|
111
|
-
lenis = null,
|
|
112
|
-
scrollTrigger = () => /** @type {unknown} */ (Reflect.get(window, 'ScrollTrigger')),
|
|
113
|
-
lockScroll = null,
|
|
114
|
-
unlockScroll = null,
|
|
115
|
-
onResize = null,
|
|
116
|
-
onValueChange = null,
|
|
117
|
-
animateLines = true,
|
|
118
|
-
lineDuration = LINE_DURATION_SECONDS,
|
|
119
|
-
} = options;
|
|
120
|
-
|
|
121
|
-
/** Whether this preset holds the scroll lock, so a modal's own lock stays intact. */
|
|
122
|
-
let locked = false;
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* @param {HTMLElement} node
|
|
126
|
-
* @returns {Gsap | null} GSAP, when this line should animate.
|
|
127
|
-
*/
|
|
128
|
-
const animator = (node) => {
|
|
129
|
-
if (!animateLines || reducedMotion() || node.offsetHeight <= 0) return null;
|
|
130
|
-
const animation = current(gsap);
|
|
131
|
-
return isGsap(animation) ? animation : null;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
autoMount: false,
|
|
136
|
-
|
|
137
|
-
onLayoutChange() {
|
|
138
|
-
callIfPresent(current(lenis), 'resize');
|
|
139
|
-
callIfPresent(current(scrollTrigger), 'refresh');
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
onCartOpen(drawers) {
|
|
143
|
-
const drawer = drawers[0];
|
|
144
|
-
if (locked || typeof lockScroll !== 'function' || !drawer) return;
|
|
145
|
-
lockScroll(drawer);
|
|
146
|
-
locked = true;
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
onCartClose() {
|
|
150
|
-
if (locked && typeof unlockScroll === 'function') unlockScroll();
|
|
151
|
-
locked = false;
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
onLineEnter(node) {
|
|
155
|
-
const animation = animator(node);
|
|
156
|
-
if (!animation) return;
|
|
157
|
-
const style = getComputedStyle(node);
|
|
158
|
-
animation.fromTo(node, collapsed(), {
|
|
159
|
-
height: node.offsetHeight,
|
|
160
|
-
paddingTop: style.paddingTop,
|
|
161
|
-
paddingBottom: style.paddingBottom,
|
|
162
|
-
borderBottomWidth: style.borderBottomWidth,
|
|
163
|
-
opacity: 1,
|
|
164
|
-
duration: lineDuration,
|
|
165
|
-
clearProps: ANIMATED_PROPS,
|
|
166
|
-
onComplete: () => onResize?.(),
|
|
167
|
-
});
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
onLineLeave(node, done) {
|
|
171
|
-
const animation = animator(node);
|
|
172
|
-
if (!animation) {
|
|
173
|
-
done();
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
animation.to(node, {
|
|
177
|
-
...collapsed(),
|
|
178
|
-
duration: lineDuration,
|
|
179
|
-
onComplete: () => {
|
|
180
|
-
done();
|
|
181
|
-
onResize?.();
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
onValueChange: typeof onValueChange === 'function' ? onValueChange : null,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default barbaPreset;
|
package/src/product-events.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A product scope's clicks and changes: option and plan buttons, purchase toggles, quantity steppers, the selects and
|
|
3
|
-
* add to cart. One delegated listener of each kind on the root, so cards a list re-renders need no new listeners.
|
|
4
|
-
*/
|
|
5
|
-
import { reportError } from './config.js';
|
|
6
|
-
import { sel, get, emit, setText, busy } from './dom.js';
|
|
7
|
-
import { addButtonLabel } from './variant-render.js';
|
|
8
|
-
/** @import { ProductScope } from './product.js' */
|
|
9
|
-
|
|
10
|
-
const productSelector = () => sel('product');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @param {Element} el
|
|
14
|
-
* @returns {el is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement}
|
|
15
|
-
*/
|
|
16
|
-
const isField = (el) => el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {EventTarget | null} target
|
|
20
|
-
* @returns {target is Element}
|
|
21
|
-
*/
|
|
22
|
-
const isElement = (target) => target instanceof window.Element;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param {ProductScope} scope
|
|
26
|
-
* @param {Element} target
|
|
27
|
-
* @param {string} selector
|
|
28
|
-
* @returns {HTMLElement | null} The nearest `selector` around `target` that belongs to the scope, not to a nested one.
|
|
29
|
-
* Typed as an HTML element, as `qsa` types its matches.
|
|
30
|
-
*/
|
|
31
|
-
function closestOwn(scope, target, selector) {
|
|
32
|
-
const el = /** @type {HTMLElement | null} */ (target.closest(selector));
|
|
33
|
-
return el?.closest(productSelector()) === scope.root ? el : null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Adds to the cart with `aria-busy` on the button and `tether-adding-text` as its label meanwhile.
|
|
38
|
-
*
|
|
39
|
-
* @param {ProductScope} scope
|
|
40
|
-
* @param {HTMLElement} button
|
|
41
|
-
*/
|
|
42
|
-
function onAddClick(scope, button) {
|
|
43
|
-
const { label, original } = addButtonLabel(button);
|
|
44
|
-
const addingText = get(button, 'adding-text');
|
|
45
|
-
void busy(button, async () => {
|
|
46
|
-
if (addingText) setText(label, addingText);
|
|
47
|
-
try {
|
|
48
|
-
await scope.addToCart();
|
|
49
|
-
} catch (error) {
|
|
50
|
-
reportError(error, 'add to cart failed');
|
|
51
|
-
emit('error', { error, context: 'add-to-cart' }, scope.root);
|
|
52
|
-
} finally {
|
|
53
|
-
if (addingText) setText(label, original);
|
|
54
|
-
}
|
|
55
|
-
}).then(() => {
|
|
56
|
-
// The mode-dependent labels (tether-subscribe-text, tether-soldout-text) wait for aria-busy to clear.
|
|
57
|
-
scope.renderVariant();
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* @param {ProductScope} scope
|
|
63
|
-
* @param {Event} event
|
|
64
|
-
* @param {Element} target
|
|
65
|
-
*/
|
|
66
|
-
function onClick(scope, event, target) {
|
|
67
|
-
const optionValue = closestOwn(scope, target, sel('option-value'));
|
|
68
|
-
const optionList = optionValue?.closest(sel('option-list'));
|
|
69
|
-
if (optionValue && optionList) {
|
|
70
|
-
event.preventDefault();
|
|
71
|
-
scope.select(scope.optName(get(optionList, 'option-list')), get(optionValue, 'option-value') ?? '');
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const plan = closestOwn(scope, target, sel('plan-value'));
|
|
75
|
-
if (plan) {
|
|
76
|
-
event.preventDefault();
|
|
77
|
-
scope.selectPlan(get(plan, 'plan-value'));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const purchase = closestOwn(scope, target, `${sel('purchase')}:not(select)`);
|
|
81
|
-
if (purchase) {
|
|
82
|
-
// A radio or checkbox input keeps its own click, so it shows checked.
|
|
83
|
-
if (purchase.tagName !== 'INPUT') event.preventDefault();
|
|
84
|
-
scope.setPurchase(get(purchase, 'purchase'));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const plus = closestOwn(scope, target, sel('qty-plus'));
|
|
88
|
-
const minus = plus ? null : closestOwn(scope, target, sel('qty-minus'));
|
|
89
|
-
if (plus || minus) {
|
|
90
|
-
event.preventDefault();
|
|
91
|
-
scope.setQuantity(scope.quantity + (plus ? 1 : -1));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const add = closestOwn(scope, target, sel('add-to-cart'));
|
|
95
|
-
if (add) {
|
|
96
|
-
event.preventDefault();
|
|
97
|
-
onAddClick(scope, add);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* @param {ProductScope} scope
|
|
103
|
-
* @param {Element} target A form field in the scope.
|
|
104
|
-
*/
|
|
105
|
-
function onChange(scope, target) {
|
|
106
|
-
const value = isField(target) ? target.value : '';
|
|
107
|
-
if (target.matches(`select${sel('option')}`)) {
|
|
108
|
-
scope.select(scope.optName(get(target, 'option')), value);
|
|
109
|
-
} else if (target.matches(`select${sel('variant-select')}`)) {
|
|
110
|
-
scope.selectVariant(value);
|
|
111
|
-
} else if (target.matches(`select${sel('plan')}`)) {
|
|
112
|
-
if (value) scope.selectPlan(value);
|
|
113
|
-
else scope.setPurchase('one-time');
|
|
114
|
-
} else if (target.matches(`select${sel('purchase')}`)) {
|
|
115
|
-
scope.setPurchase(value);
|
|
116
|
-
} else if (target.matches(sel('qty'))) {
|
|
117
|
-
scope.setQuantity(value);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** @param {ProductScope} scope */
|
|
122
|
-
export function bindProductEvents(scope) {
|
|
123
|
-
scope.root.addEventListener('click', (event) => {
|
|
124
|
-
if (isElement(event.target)) onClick(scope, event, event.target);
|
|
125
|
-
});
|
|
126
|
-
scope.root.addEventListener('change', (event) => {
|
|
127
|
-
const { target } = event;
|
|
128
|
-
if (isElement(target) && target.closest(productSelector()) === scope.root) onChange(scope, target);
|
|
129
|
-
});
|
|
130
|
-
}
|