@occluze/tether 0.6.8 → 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 -2
- package/dist/presets/barba.esm.js +19 -23
- package/dist/tether.account-pages.js +320 -365
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +242 -491
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +66 -88
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +36 -33
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +28 -36
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +1270 -894
- package/dist/tether.markets.js +73 -90
- 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 +44 -68
- 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 +24 -23
- 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.js → order-lines.ts} +20 -41
- 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.js → page-events.ts} +55 -90
- package/src/account/server-client.ts +183 -0
- package/src/account/{subscriptions-view.js → subscriptions-view.ts} +25 -68
- package/src/account-api.ts +98 -0
- package/src/account-marks.ts +35 -0
- package/src/{account-pages.js → account-pages.ts} +29 -37
- package/src/account.ts +284 -0
- package/src/amounts.ts +64 -0
- package/src/{analytics-shopify.js → analytics-shopify.ts} +112 -107
- package/src/analytics.ts +315 -0
- package/src/{api-version.js → api-version.ts} +25 -31
- package/src/{astro.js → astro.ts} +110 -104
- package/src/cart/cart-shape.ts +25 -0
- package/src/cart/checkout.ts +101 -0
- package/src/cart/{drawer.js → drawer.ts} +18 -31
- package/src/cart/mutations.ts +237 -0
- package/src/cart/{render.js → render.ts} +68 -96
- package/src/cart/store.ts +279 -0
- package/src/{cart.js → cart.ts} +82 -128
- package/src/client.ts +391 -0
- package/src/{collection.js → collection.ts} +30 -38
- package/src/config-sections.ts +284 -0
- package/src/config.ts +480 -0
- package/src/controls/{cart-controls.js → cart-controls.ts} +22 -54
- package/src/controls/{discount.js → discount.ts} +13 -34
- package/src/controls/{extras.js → extras.ts} +15 -24
- package/src/controls/{navigation.js → navigation.ts} +24 -30
- package/src/{diagnose-markup.js → diagnose-markup.ts} +37 -72
- package/src/{diagnose.js → diagnose.ts} +95 -96
- package/src/dom.ts +268 -0
- package/src/{extras.js → extras.ts} +43 -52
- package/src/fragments.ts +261 -0
- package/src/globals.d.ts +15 -0
- package/src/{images.js → images.ts} +30 -43
- 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.js → loader.ts} +149 -123
- package/src/market-codes.ts +6 -0
- package/src/{markets-ui.js → markets-ui.ts} +91 -68
- package/src/markets.ts +246 -0
- package/src/markup.ts +127 -0
- package/src/{mounting.js → mounting.ts} +88 -101
- package/src/{page-market.js → page-market.ts} +32 -38
- 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.js → product-events.ts} +33 -48
- package/src/{product-render.js → product-render.ts} +38 -77
- package/src/product-shape.ts +49 -0
- package/src/product-state.ts +318 -0
- package/src/{product.js → product.ts} +90 -126
- package/src/{repeaters.js → repeaters.ts} +40 -52
- package/src/{search.js → search.ts} +85 -103
- package/src/selling-plans.ts +145 -0
- package/src/server.ts +211 -0
- package/src/{shopify-values.js → shopify-values.ts} +24 -22
- package/src/{status.js → status.ts} +34 -54
- 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.js → json-ld.ts} +45 -68
- package/src/{variant-render.js → variant-render.ts} +24 -50
- 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-list.js +0 -91
- package/src/account/order-page.js +0 -192
- 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-api.js +0 -118
- package/src/account-links.js +0 -35
- package/src/account.js +0 -342
- package/src/amounts.js +0 -36
- package/src/analytics.js +0 -352
- package/src/cart/checkout.js +0 -97
- package/src/cart/mutations.js +0 -234
- package/src/cart/store.js +0 -326
- package/src/client.js +0 -267
- package/src/config.js +0 -349
- package/src/dom.js +0 -287
- package/src/fragments.js +0 -237
- 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/market-codes.js +0 -35
- package/src/markets.js +0 -334
- package/src/markup.js +0 -64
- 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-state.js +0 -318
- package/src/selling-plans.js +0 -154
- package/src/server.js +0 -215
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +0 -199
- 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/config.ts
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime configuration. `cfg` is one shared object that every browser module imports; `configure()` replaces its
|
|
3
|
+
* contents in place, so those imports see the new values, and `setCountry()` changes its market keys the same way.
|
|
4
|
+
* Every option is validated here, once, where it arrives: values that end up in query text, URLs, attribute names or
|
|
5
|
+
* storage keys never reach the code that uses them unchecked.
|
|
6
|
+
*/
|
|
7
|
+
import { STOREFRONT_API_VERSION } from '../server/src/shared/api-versions.ts';
|
|
8
|
+
import { isQuarterlyVersion } from './api-version.ts';
|
|
9
|
+
import {
|
|
10
|
+
accountOption,
|
|
11
|
+
analyticsOption,
|
|
12
|
+
domainOption,
|
|
13
|
+
embedOriginsOption,
|
|
14
|
+
marketsOption,
|
|
15
|
+
metafieldsOption,
|
|
16
|
+
partsUrlOption,
|
|
17
|
+
} from './config-sections.ts';
|
|
18
|
+
|
|
19
|
+
export { MARKET_SOURCES } from './config-sections.ts';
|
|
20
|
+
import { toCountry, toLanguage } from './market-codes.ts';
|
|
21
|
+
import type { ApiVersion } from '../server/src/shared/api-versions.ts';
|
|
22
|
+
import type { ShopDomain } from './config-sections.ts';
|
|
23
|
+
import type { Money } from './shopify-values.ts';
|
|
24
|
+
import type { CountryCode, LanguageCode } from './market-codes.ts';
|
|
25
|
+
|
|
26
|
+
/** Where the visitor's market comes from (`markets.ts`). */
|
|
27
|
+
export interface MarketsConfig {
|
|
28
|
+
/** True: every source; a list: only those. */
|
|
29
|
+
detect?: boolean | ('saved' | 'server' | 'webflow')[];
|
|
30
|
+
/** Webflow locale (subdirectory or html lang) to market. */
|
|
31
|
+
locales?: Record<string, { country?: string; language?: string; locale?: string }>;
|
|
32
|
+
/** The cookie a server sets with the visitor's country. */
|
|
33
|
+
cookie?: string;
|
|
34
|
+
/** Remember the visitor's choice (default true). */
|
|
35
|
+
remember?: boolean;
|
|
36
|
+
/** With `locales`: ignore an html lang that isn't in the map. */
|
|
37
|
+
strict?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The optional analytics part. */
|
|
41
|
+
export interface AnalyticsConfig {
|
|
42
|
+
/** GA4/GTM events, into `window.dataLayer` or the named array. */
|
|
43
|
+
dataLayer?: boolean | string;
|
|
44
|
+
/** Shopify's own analytics. `proxy`: the site serves the Storefront API on its own domain. */
|
|
45
|
+
shopify?: boolean | { consent?: 'no-banner' | 'default-banner' | 'custom-banner'; proxy?: boolean };
|
|
46
|
+
onEvent?: (name: string, data: Record<string, unknown>) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The optional customer accounts part, which runs through the site's Worker. */
|
|
50
|
+
export interface AccountConfig {
|
|
51
|
+
/** The Worker's account routes, a URL or a path on this site. */
|
|
52
|
+
server: string;
|
|
53
|
+
/** The site's own order page, with `{id}` for the numeric order id. */
|
|
54
|
+
orderUrl?: string;
|
|
55
|
+
/** Shopify's subscription management page. */
|
|
56
|
+
subscriptionsUrl?: string;
|
|
57
|
+
/** A separate page for payment methods (default `subscriptionsUrl`). */
|
|
58
|
+
paymentUrl?: string;
|
|
59
|
+
/** Where Shopify sends the visitor after logout (default the home page). */
|
|
60
|
+
logoutUrl?: string;
|
|
61
|
+
/** The language of Shopify's login pages. */
|
|
62
|
+
locale?: string;
|
|
63
|
+
/** Replaces `location.assign` for the login redirect (popups, tests). */
|
|
64
|
+
navigate?: (url: string) => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface MetafieldIdentifier {
|
|
68
|
+
namespace: string;
|
|
69
|
+
key: string;
|
|
70
|
+
}
|
|
71
|
+
/** `'namespace.key'` or the identifier itself. */
|
|
72
|
+
export type MetafieldRef = string | MetafieldIdentifier;
|
|
73
|
+
/** Shopify's note on a cart change. */
|
|
74
|
+
export interface CartWarning {
|
|
75
|
+
code: string;
|
|
76
|
+
message: string;
|
|
77
|
+
target?: string;
|
|
78
|
+
}
|
|
79
|
+
export type CheckoutTarget = '_self' | '_blank';
|
|
80
|
+
type PurchaseKind = 'one-time' | 'subscription';
|
|
81
|
+
|
|
82
|
+
/** Every option, with its default filled in. */
|
|
83
|
+
export interface TetherConfig {
|
|
84
|
+
/** The store's domain, without protocol or path; `''` until one is set. */
|
|
85
|
+
domain: ShopDomain | '';
|
|
86
|
+
/** The public Storefront API access token. */
|
|
87
|
+
token: string;
|
|
88
|
+
/** A dated Shopify API version (or `unstable`, with a warning). */
|
|
89
|
+
apiVersion: ApiVersion | 'unstable';
|
|
90
|
+
/** The market's country, for `@inContext(country:)`. */
|
|
91
|
+
country: CountryCode | undefined;
|
|
92
|
+
/** The market's language, for `@inContext(language:)`. */
|
|
93
|
+
language: LanguageCode | undefined;
|
|
94
|
+
/** An `Intl` locale for money and dates. */
|
|
95
|
+
locale: string | undefined;
|
|
96
|
+
markets: MarketsConfig | null;
|
|
97
|
+
/** The attribute prefix: `tether` reads `tether-product`, `tether-bind`. */
|
|
98
|
+
prefix: string;
|
|
99
|
+
/** A folder (`/product`) or a template with `{handle}` / `{id}`. */
|
|
100
|
+
productPath: string;
|
|
101
|
+
collectionPath: string;
|
|
102
|
+
/** The product metafields to fetch. */
|
|
103
|
+
metafields: MetafieldRef[];
|
|
104
|
+
/** Origins (`https://video.example`) a description's iframes may load from, besides the built-in video players. */
|
|
105
|
+
embedOrigins: string[];
|
|
106
|
+
imagesFirst: number;
|
|
107
|
+
variantsFirst: number;
|
|
108
|
+
sellingPlanGroupsFirst: number;
|
|
109
|
+
sellingPlansFirst: number;
|
|
110
|
+
collectionFirst: number;
|
|
111
|
+
/** Retries of a request that failed on the network, a 429/5xx or Shopify's throttling. */
|
|
112
|
+
retries: number;
|
|
113
|
+
/** Refresh server-rendered products once in the background. */
|
|
114
|
+
revalidate: boolean;
|
|
115
|
+
/** The localStorage key of the cart id (default `<prefix>:cart:<domain>`). */
|
|
116
|
+
storageKey: string;
|
|
117
|
+
cartDrawerOpenClass: string;
|
|
118
|
+
openCartOnAdd: boolean;
|
|
119
|
+
/** Move focus into the open cart, keep Tab inside it, restore it on close. */
|
|
120
|
+
cartFocus: boolean;
|
|
121
|
+
checkoutTarget: CheckoutTarget;
|
|
122
|
+
defaultPurchase: PurchaseKind;
|
|
123
|
+
/** `?variant=` follows the visitor's choice on the page's main product. */
|
|
124
|
+
variantUrl: boolean;
|
|
125
|
+
/** Seconds a Shopify answer may be cached; 0 leaves it to Shopify, which can be hours. */
|
|
126
|
+
maxAge: number;
|
|
127
|
+
/** `tether-show="low-stock"` at this many or fewer (needs inventory access). */
|
|
128
|
+
lowStock: number;
|
|
129
|
+
/** Milliseconds the search part waits after the last keystroke. */
|
|
130
|
+
searchDelay: number;
|
|
131
|
+
/** Mount the document on DOMContentLoaded. */
|
|
132
|
+
autoMount: boolean;
|
|
133
|
+
/** Mount elements added later (CMS load more, filters). */
|
|
134
|
+
observe: boolean;
|
|
135
|
+
/** Mount on Astro's `astro:page-load` and close the cart on navigation. */
|
|
136
|
+
astro: boolean;
|
|
137
|
+
/** The folder of the optional parts, for the `<script>` build. */
|
|
138
|
+
partsUrl: string | null;
|
|
139
|
+
analytics: AnalyticsConfig | null;
|
|
140
|
+
account: AccountConfig | null;
|
|
141
|
+
/** The site's text for a cart warning; nothing keeps Shopify's. */
|
|
142
|
+
warningText: ((warning: CartWarning) => string | null | undefined) | null;
|
|
143
|
+
formatMoney: ((money: Money, locale: string | undefined) => string) | null;
|
|
144
|
+
/** Default: `ScrollTrigger.refresh()` when present. */
|
|
145
|
+
onLayoutChange: ((reason: string) => void) | null;
|
|
146
|
+
onCartOpen: ((drawers: HTMLElement[]) => void) | null;
|
|
147
|
+
onCartClose: ((drawers: HTMLElement[]) => void) | null;
|
|
148
|
+
onLineEnter: ((node: HTMLElement) => void) | null;
|
|
149
|
+
/** Animates a line out, then calls `done()`. */
|
|
150
|
+
onLineLeave: ((node: HTMLElement, done: () => void) => void) | null;
|
|
151
|
+
onValueChange: ((element: HTMLElement, previousText: string) => void) | null;
|
|
152
|
+
/** Every failure that reaches the visitor. */
|
|
153
|
+
onError: ((error: unknown, context: string) => void) | null;
|
|
154
|
+
debug: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The options a site writes as plain text, which `resolveConfig` checks into their typed form. */
|
|
158
|
+
type ParsedFromText = 'domain' | 'apiVersion' | 'country' | 'language' | 'storageKey';
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* What a site passes: any option, the market as the codes people write (`'de'`, `'pt-BR'`), and `null` for the
|
|
162
|
+
* default storage key.
|
|
163
|
+
*/
|
|
164
|
+
export type TetherOptions = {
|
|
165
|
+
[Key in Exclude<keyof TetherConfig, ParsedFromText>]?: TetherConfig[Key];
|
|
166
|
+
} & Partial<Record<Exclude<ParsedFromText, 'storageKey'>, string | undefined>> & {
|
|
167
|
+
storageKey?: string | null | undefined;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const DEFAULTS: Readonly<TetherConfig> = {
|
|
171
|
+
domain: '',
|
|
172
|
+
token: '',
|
|
173
|
+
apiVersion: STOREFRONT_API_VERSION,
|
|
174
|
+
country: undefined,
|
|
175
|
+
language: undefined,
|
|
176
|
+
locale: undefined,
|
|
177
|
+
markets: null,
|
|
178
|
+
prefix: 'tether',
|
|
179
|
+
productPath: '/product',
|
|
180
|
+
collectionPath: '/collection',
|
|
181
|
+
metafields: [],
|
|
182
|
+
embedOrigins: [],
|
|
183
|
+
imagesFirst: 25,
|
|
184
|
+
variantsFirst: 100,
|
|
185
|
+
sellingPlanGroupsFirst: 10,
|
|
186
|
+
sellingPlansFirst: 25,
|
|
187
|
+
collectionFirst: 24,
|
|
188
|
+
retries: 2,
|
|
189
|
+
revalidate: true,
|
|
190
|
+
storageKey: '',
|
|
191
|
+
cartDrawerOpenClass: 'is-open',
|
|
192
|
+
openCartOnAdd: true,
|
|
193
|
+
cartFocus: true,
|
|
194
|
+
checkoutTarget: '_self',
|
|
195
|
+
defaultPurchase: 'one-time',
|
|
196
|
+
variantUrl: false,
|
|
197
|
+
maxAge: 60,
|
|
198
|
+
lowStock: 5,
|
|
199
|
+
searchDelay: 150,
|
|
200
|
+
autoMount: true,
|
|
201
|
+
observe: false,
|
|
202
|
+
astro: true,
|
|
203
|
+
partsUrl: null,
|
|
204
|
+
analytics: null,
|
|
205
|
+
account: null,
|
|
206
|
+
warningText: null,
|
|
207
|
+
formatMoney: null,
|
|
208
|
+
onLayoutChange: null,
|
|
209
|
+
onCartOpen: null,
|
|
210
|
+
onCartClose: null,
|
|
211
|
+
onLineEnter: null,
|
|
212
|
+
onLineLeave: null,
|
|
213
|
+
onValueChange: null,
|
|
214
|
+
onError: null,
|
|
215
|
+
debug: false,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const cfg: TetherConfig = { ...DEFAULTS };
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @returns A view of `target` and the objects in it that reads the live values and refuses every change: a write
|
|
222
|
+
* throws in strict-mode code (as on a frozen object) and warns in any code.
|
|
223
|
+
*/
|
|
224
|
+
function readOnlyView<T extends object>(target: T): T {
|
|
225
|
+
const refuse = (key: string | symbol) => {
|
|
226
|
+
warn(`Tether.config is read-only (${String(key)}): pass options to Tether.init() instead`);
|
|
227
|
+
return false;
|
|
228
|
+
};
|
|
229
|
+
return new Proxy(target, {
|
|
230
|
+
get(object, key) {
|
|
231
|
+
const value: unknown = Reflect.get(object, key);
|
|
232
|
+
// The config's objects are its own copies (`resolveConfig`), never frozen, so a view of each is allowed.
|
|
233
|
+
return typeof value === 'object' && value !== null ? readOnlyView(value) : value;
|
|
234
|
+
},
|
|
235
|
+
set: (_object, key) => refuse(key),
|
|
236
|
+
deleteProperty: (_object, key) => refuse(key),
|
|
237
|
+
defineProperty: (_object, key) => refuse(key),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* `cfg` as `Tether.config` shows it. A value written there would skip the validation `configure()` does, so the view
|
|
243
|
+
* is read-only; `Tether.init(options)` is how a site changes the config.
|
|
244
|
+
*/
|
|
245
|
+
export const configView = readOnlyView(cfg);
|
|
246
|
+
|
|
247
|
+
/** The prefix becomes part of attribute names, CSS selectors and event names, so it is kept to those characters. */
|
|
248
|
+
const PREFIX = /^[a-z][a-z0-9-]*$/;
|
|
249
|
+
|
|
250
|
+
type CountOption =
|
|
251
|
+
| 'imagesFirst'
|
|
252
|
+
| 'variantsFirst'
|
|
253
|
+
| 'sellingPlanGroupsFirst'
|
|
254
|
+
| 'sellingPlansFirst'
|
|
255
|
+
| 'collectionFirst'
|
|
256
|
+
| 'retries'
|
|
257
|
+
| 'maxAge'
|
|
258
|
+
| 'lowStock'
|
|
259
|
+
| 'searchDelay';
|
|
260
|
+
type HookOption =
|
|
261
|
+
| 'warningText'
|
|
262
|
+
| 'formatMoney'
|
|
263
|
+
| 'onLayoutChange'
|
|
264
|
+
| 'onCartOpen'
|
|
265
|
+
| 'onCartClose'
|
|
266
|
+
| 'onLineEnter'
|
|
267
|
+
| 'onLineLeave'
|
|
268
|
+
| 'onValueChange'
|
|
269
|
+
| 'onError';
|
|
270
|
+
type SwitchOption =
|
|
271
|
+
'revalidate' | 'openCartOnAdd' | 'cartFocus' | 'variantUrl' | 'autoMount' | 'observe' | 'astro' | 'debug';
|
|
272
|
+
type TextOption = 'token' | 'productPath' | 'collectionPath' | 'cartDrawerOpenClass';
|
|
273
|
+
|
|
274
|
+
const SWITCH_OPTIONS: readonly SwitchOption[] = [
|
|
275
|
+
'revalidate',
|
|
276
|
+
'openCartOnAdd',
|
|
277
|
+
'cartFocus',
|
|
278
|
+
'variantUrl',
|
|
279
|
+
'autoMount',
|
|
280
|
+
'observe',
|
|
281
|
+
'astro',
|
|
282
|
+
'debug',
|
|
283
|
+
];
|
|
284
|
+
const TEXT_OPTIONS: readonly TextOption[] = ['token', 'productPath', 'collectionPath', 'cartDrawerOpenClass'];
|
|
285
|
+
|
|
286
|
+
/** Options that are written into queries as list sizes or used as counts. */
|
|
287
|
+
const COUNT_OPTIONS: readonly CountOption[] = [
|
|
288
|
+
'imagesFirst',
|
|
289
|
+
'variantsFirst',
|
|
290
|
+
'sellingPlanGroupsFirst',
|
|
291
|
+
'sellingPlansFirst',
|
|
292
|
+
'collectionFirst',
|
|
293
|
+
'retries',
|
|
294
|
+
'maxAge',
|
|
295
|
+
'lowStock',
|
|
296
|
+
'searchDelay',
|
|
297
|
+
];
|
|
298
|
+
const HOOK_OPTIONS: readonly HookOption[] = [
|
|
299
|
+
'warningText',
|
|
300
|
+
'formatMoney',
|
|
301
|
+
'onLayoutChange',
|
|
302
|
+
'onCartOpen',
|
|
303
|
+
'onCartClose',
|
|
304
|
+
'onLineEnter',
|
|
305
|
+
'onLineLeave',
|
|
306
|
+
'onValueChange',
|
|
307
|
+
'onError',
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
/** @returns `fallback`, with a warning unless `value` was left out. */
|
|
311
|
+
function invalidOption<T>(name: string, value: unknown, fallback: T): T {
|
|
312
|
+
if (value !== undefined) warn(`invalid ${name} ${JSON.stringify(value)}: using ${JSON.stringify(fallback)}`);
|
|
313
|
+
return fallback;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Warns about an option without a default to fall back to, which is left out (`config-sections.ts` too). */
|
|
317
|
+
function dropped(name: string, value: unknown): void {
|
|
318
|
+
warn(`invalid ${name} ${JSON.stringify(value)}: ignored`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Warns about each key that is no option, so a misspelt one (`openCartOnAd`) is seen; none of them reaches `cfg`.
|
|
323
|
+
*/
|
|
324
|
+
function warnUnknownKeys(options: Readonly<Record<string, unknown>>): void {
|
|
325
|
+
for (const key of Object.keys(options)) {
|
|
326
|
+
if (!(key in DEFAULTS)) warn(`unknown option ${JSON.stringify(key)}: ignored`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** @returns A site's hook, `null` for anything that isn't a function. */
|
|
331
|
+
function hookOption(value: unknown): ((...args: never[]) => unknown) | null {
|
|
332
|
+
return typeof value === 'function' ? (value as (...args: never[]) => unknown) : null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function hookOptions(options: Readonly<Record<string, unknown>>): Pick<TetherConfig, HookOption> {
|
|
336
|
+
const hooks = Object.fromEntries(HOOK_OPTIONS.map((name) => [name, hookOption(options[name])]));
|
|
337
|
+
// A function from the site can only be checked for being callable; how it is called is the documented signature
|
|
338
|
+
// of its option.
|
|
339
|
+
return hooks as Pick<TetherConfig, HookOption>;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** @returns `value` when it is a non-negative integer, else the default. */
|
|
343
|
+
function countOption(name: CountOption, value: unknown): number {
|
|
344
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) return value;
|
|
345
|
+
return invalidOption(name, value, DEFAULTS[name]);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function switchOption(name: SwitchOption, value: unknown): boolean {
|
|
349
|
+
return typeof value === 'boolean' ? value : invalidOption(name, value, DEFAULTS[name]);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function textOption(name: TextOption, value: unknown): string {
|
|
353
|
+
return typeof value === 'string' ? value : invalidOption(name, value, DEFAULTS[name]);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** @returns An `Intl` locale as written, `undefined` for one `Intl` doesn't accept. */
|
|
357
|
+
function localeOption(value: unknown): string | undefined {
|
|
358
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
359
|
+
try {
|
|
360
|
+
if (typeof value === 'string' && Intl.getCanonicalLocales(value).length) return value;
|
|
361
|
+
} catch {
|
|
362
|
+
// RangeError: not a locale (falls through to the warning).
|
|
363
|
+
}
|
|
364
|
+
dropped('locale', value);
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** @param choices The first one is the default. */
|
|
369
|
+
function choiceOption<Choice extends string>(
|
|
370
|
+
name: string,
|
|
371
|
+
value: unknown,
|
|
372
|
+
choices: readonly [Choice, ...Choice[]],
|
|
373
|
+
): Choice {
|
|
374
|
+
return choices.find((choice) => choice === value) ?? invalidOption(name, value, choices[0]);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @returns A quarterly version, or `unstable` with a warning; the default for anything else, which would be written
|
|
379
|
+
* into the endpoint's path.
|
|
380
|
+
*/
|
|
381
|
+
function apiVersionOption(value: unknown): ApiVersion | 'unstable' {
|
|
382
|
+
if (value === 'unstable') {
|
|
383
|
+
warn('apiVersion "unstable" changes without notice. Pin a dated version (e.g. 2026-07).');
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
return isQuarterlyVersion(value) ? value : invalidOption('apiVersion', value, DEFAULTS.apiVersion);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function codeOption<Code>(name: string, value: unknown, parse: (value: unknown) => Code | null): Code | undefined {
|
|
390
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
391
|
+
const code = parse(value);
|
|
392
|
+
if (code !== null) return code;
|
|
393
|
+
dropped(name, value);
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The configuration `options` describe: defaults filled in, every value validated (an invalid one warns and falls
|
|
399
|
+
* back to its default), hooks that aren't functions dropped so callers only check for null.
|
|
400
|
+
*
|
|
401
|
+
* @param options What the site set, or any object (the server helpers take their options from code and `.env`).
|
|
402
|
+
*/
|
|
403
|
+
export function resolveConfig(options: TetherOptions | Readonly<Record<string, unknown>>): TetherConfig {
|
|
404
|
+
warnUnknownKeys(options);
|
|
405
|
+
const domain = domainOption(options.domain, dropped);
|
|
406
|
+
const prefix =
|
|
407
|
+
typeof options.prefix === 'string' && PREFIX.test(options.prefix)
|
|
408
|
+
? options.prefix
|
|
409
|
+
: invalidOption('prefix', options.prefix, DEFAULTS.prefix);
|
|
410
|
+
const resolved: TetherConfig = {
|
|
411
|
+
...DEFAULTS,
|
|
412
|
+
...hookOptions(options),
|
|
413
|
+
domain,
|
|
414
|
+
apiVersion: apiVersionOption(options.apiVersion),
|
|
415
|
+
country: codeOption('country', options.country, toCountry),
|
|
416
|
+
locale: localeOption(options.locale),
|
|
417
|
+
partsUrl: partsUrlOption(options.partsUrl, dropped),
|
|
418
|
+
language: codeOption('language', options.language, toLanguage),
|
|
419
|
+
markets: marketsOption(options.markets, dropped),
|
|
420
|
+
analytics: analyticsOption(options.analytics, dropped),
|
|
421
|
+
account: accountOption(options.account, dropped, warn),
|
|
422
|
+
prefix,
|
|
423
|
+
storageKey:
|
|
424
|
+
typeof options.storageKey === 'string' && options.storageKey ? options.storageKey : `${prefix}:cart:${domain}`,
|
|
425
|
+
metafields: metafieldsOption(options.metafields ?? [], dropped),
|
|
426
|
+
embedOrigins: embedOriginsOption(options.embedOrigins ?? [], dropped),
|
|
427
|
+
checkoutTarget: choiceOption('checkoutTarget', options.checkoutTarget, ['_self', '_blank']),
|
|
428
|
+
defaultPurchase: choiceOption('defaultPurchase', options.defaultPurchase, ['one-time', 'subscription']),
|
|
429
|
+
};
|
|
430
|
+
for (const name of COUNT_OPTIONS) resolved[name] = countOption(name, options[name]);
|
|
431
|
+
for (const name of SWITCH_OPTIONS) resolved[name] = switchOption(name, options[name]);
|
|
432
|
+
for (const name of TEXT_OPTIONS) resolved[name] = textOption(name, options[name]);
|
|
433
|
+
return resolved;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* @param value A `markets` option from code other than the config (`Tether.detectMarket(markets)`).
|
|
438
|
+
* @returns It validated as the config's own is, with the same warnings.
|
|
439
|
+
*/
|
|
440
|
+
export const parseMarkets = (value: unknown): MarketsConfig | null => marketsOption(value, dropped);
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Replaces the shared configuration with `options` over the defaults (see `resolveConfig`).
|
|
444
|
+
*
|
|
445
|
+
* @returns The shared `cfg`.
|
|
446
|
+
*/
|
|
447
|
+
export function configure(options: TetherOptions | Readonly<Record<string, unknown>> = {}): TetherConfig {
|
|
448
|
+
const resolved = resolveConfig(options);
|
|
449
|
+
for (const key of Object.keys(cfg)) {
|
|
450
|
+
if (!(key in resolved)) Reflect.deleteProperty(cfg, key);
|
|
451
|
+
}
|
|
452
|
+
return Object.assign(cfg, resolved);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** @returns True once a domain and a token are set. */
|
|
456
|
+
export const isConfigured = (): boolean => Boolean(cfg.domain && cfg.token);
|
|
457
|
+
|
|
458
|
+
export function log(...args: unknown[]): void {
|
|
459
|
+
if (cfg.debug) console.log(`[${cfg.prefix}]`, ...args);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function warn(...args: unknown[]): void {
|
|
463
|
+
console.warn(`[${cfg.prefix}]`, ...args);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Logs a failure that reaches the visitor and hands it to `onError`. A throwing `onError` is logged, never rethrown:
|
|
468
|
+
* the site's hook must not break the render that reported the failure.
|
|
469
|
+
*
|
|
470
|
+
* @param context What failed, e.g. `product coffee-box`.
|
|
471
|
+
*/
|
|
472
|
+
export function reportError(error: unknown, context: string): void {
|
|
473
|
+
warn(context, error instanceof Error && error.message ? error.message : error);
|
|
474
|
+
if (!cfg.onError) return;
|
|
475
|
+
try {
|
|
476
|
+
cfg.onError(error, context);
|
|
477
|
+
} catch (hookError) {
|
|
478
|
+
warn('onError threw', hookError);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
@@ -3,46 +3,30 @@
|
|
|
3
3
|
* clear and checkout buttons, a line's +, - and remove buttons, its quantity input and plan select, the cart note, and
|
|
4
4
|
* the keyboard of the open drawer (Escape closes it, Tab stays inside it).
|
|
5
5
|
*/
|
|
6
|
-
import { cfg, reportError } from '../config.
|
|
7
|
-
import { Cart, trapFocus } from '../cart.
|
|
8
|
-
import { sel, busy } from '../dom.
|
|
6
|
+
import { cfg, reportError } from '../config.ts';
|
|
7
|
+
import { Cart, trapFocus } from '../cart.ts';
|
|
8
|
+
import { sel, busy } from '../dom.ts';
|
|
9
9
|
|
|
10
|
-
/** @returns
|
|
11
|
-
const lineKeyAttribute = () => `data-${cfg.prefix}-key`;
|
|
10
|
+
/** @returns The attribute a rendered cart line carries its line id in. */
|
|
11
|
+
const lineKeyAttribute = (): string => `data-${cfg.prefix}-key`;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
* @param {EventTarget | null} target
|
|
15
|
-
* @returns {target is Element}
|
|
16
|
-
*/
|
|
17
|
-
const isElement = (target) => target instanceof window.Element;
|
|
13
|
+
const isElement = (target: EventTarget | null): target is Element => target instanceof window.Element;
|
|
18
14
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
* @returns {{ line: Element, id: string } | null} The rendered cart line `target` is in, with its line id.
|
|
22
|
-
*/
|
|
23
|
-
function lineOf(target) {
|
|
15
|
+
/** @returns The rendered cart line `target` is in, with its line id. */
|
|
16
|
+
function lineOf(target: Element): { line: Element; id: string } | null {
|
|
24
17
|
const line = target.closest(`[${lineKeyAttribute()}]`);
|
|
25
18
|
const id = line?.getAttribute(lineKeyAttribute());
|
|
26
19
|
return line && id ? { line, id } : null;
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* @param {string} context
|
|
32
|
-
*/
|
|
33
|
-
function reportFailure(change, context) {
|
|
34
|
-
change.catch((/** @type {unknown} */ error) => {
|
|
22
|
+
function reportFailure(change: Promise<unknown>, context: string): void {
|
|
23
|
+
change.catch((error: unknown) => {
|
|
35
24
|
reportError(error, context);
|
|
36
25
|
});
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
* @param {MouseEvent} event
|
|
43
|
-
* @param {Element} target
|
|
44
|
-
*/
|
|
45
|
-
function onLineClick(event, target) {
|
|
28
|
+
/** A line's remove, + and - buttons. */
|
|
29
|
+
function onLineClick(event: MouseEvent, target: Element): void {
|
|
46
30
|
const found = lineOf(target);
|
|
47
31
|
if (!found?.line.closest(sel('cart-lines'))) return;
|
|
48
32
|
const { line, id } = found;
|
|
@@ -58,18 +42,14 @@ function onLineClick(event, target) {
|
|
|
58
42
|
reportFailure(Cart.changeQuantity(id, delta), 'cart line');
|
|
59
43
|
}
|
|
60
44
|
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
* @returns {1 | -1 | 0} The quantity change of a + or - button, 0 for anything else.
|
|
64
|
-
*/
|
|
65
|
-
function lineDelta(target) {
|
|
45
|
+
/** @returns The quantity change of a + or - button, 0 for anything else. */
|
|
46
|
+
function lineDelta(target: Element): 1 | -1 | 0 {
|
|
66
47
|
if (target.closest(sel('line-plus'))) return 1;
|
|
67
48
|
if (target.closest(sel('line-minus'))) return -1;
|
|
68
49
|
return 0;
|
|
69
50
|
}
|
|
70
51
|
|
|
71
|
-
|
|
72
|
-
function onClick(event) {
|
|
52
|
+
function onClick(event: MouseEvent): void {
|
|
73
53
|
const { target } = event;
|
|
74
54
|
if (!isElement(target)) return;
|
|
75
55
|
if (target.closest(sel('cart-open'))) {
|
|
@@ -86,7 +66,7 @@ function onClick(event) {
|
|
|
86
66
|
if (clear) {
|
|
87
67
|
event.preventDefault();
|
|
88
68
|
void busy(clear, () =>
|
|
89
|
-
Cart.clear().catch((
|
|
69
|
+
Cart.clear().catch((error: unknown) => {
|
|
90
70
|
reportError(error, 'cart clear');
|
|
91
71
|
}),
|
|
92
72
|
);
|
|
@@ -101,21 +81,14 @@ function onClick(event) {
|
|
|
101
81
|
onLineClick(event, target);
|
|
102
82
|
}
|
|
103
83
|
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
*
|
|
107
|
-
* @param {Element} line
|
|
108
|
-
* @param {Promise<unknown>} change
|
|
109
|
-
* @param {string} context
|
|
110
|
-
*/
|
|
111
|
-
function showLineBusy(line, change, context) {
|
|
84
|
+
/** The line shows `is-busy` and `aria-busy` until Shopify has answered; the next render removes them. */
|
|
85
|
+
function showLineBusy(line: Element, change: Promise<unknown>, context: string): void {
|
|
112
86
|
line.classList.add('is-busy');
|
|
113
87
|
line.setAttribute('aria-busy', 'true');
|
|
114
88
|
reportFailure(change, context);
|
|
115
89
|
}
|
|
116
90
|
|
|
117
|
-
|
|
118
|
-
function onChange(event) {
|
|
91
|
+
function onChange(event: Event): void {
|
|
119
92
|
const { target } = event;
|
|
120
93
|
if (!isElement(target)) return;
|
|
121
94
|
const found = lineOf(target);
|
|
@@ -128,17 +101,12 @@ function onChange(event) {
|
|
|
128
101
|
}
|
|
129
102
|
}
|
|
130
103
|
|
|
131
|
-
|
|
132
|
-
* @param {Element} target
|
|
133
|
-
* @returns {target is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement}
|
|
134
|
-
*/
|
|
135
|
-
const hasValue = (target) =>
|
|
104
|
+
const hasValue = (target: Element): target is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>
|
|
136
105
|
target instanceof window.HTMLInputElement ||
|
|
137
106
|
target instanceof window.HTMLTextAreaElement ||
|
|
138
107
|
target instanceof window.HTMLSelectElement;
|
|
139
108
|
|
|
140
|
-
|
|
141
|
-
function onKeydown(event) {
|
|
109
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
142
110
|
if (!Cart.isOpen()) return;
|
|
143
111
|
if (event.key === 'Escape') {
|
|
144
112
|
Cart.close();
|
|
@@ -148,7 +116,7 @@ function onKeydown(event) {
|
|
|
148
116
|
}
|
|
149
117
|
|
|
150
118
|
/** Binds the document-wide listeners, once per page load of the script. */
|
|
151
|
-
export function bindCartControls() {
|
|
119
|
+
export function bindCartControls(): void {
|
|
152
120
|
document.addEventListener('click', onClick);
|
|
153
121
|
document.addEventListener('change', onChange);
|
|
154
122
|
document.addEventListener('keydown', onKeydown);
|