@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
|
@@ -3,43 +3,25 @@
|
|
|
3
3
|
* (`tether-discount-remove` inside `tether-discount-code`), delegated from the document. A code Shopify refuses marks
|
|
4
4
|
* the form `is-invalid` (and the input `aria-invalid`) until the visitor types again.
|
|
5
5
|
*/
|
|
6
|
-
import { reportError } from '../config.
|
|
7
|
-
import { TetherError } from '../client.
|
|
8
|
-
import { Cart } from '../cart.
|
|
9
|
-
import { attr, sel, busy, emit } from '../dom.
|
|
6
|
+
import { reportError } from '../config.ts';
|
|
7
|
+
import { TetherError } from '../client.ts';
|
|
8
|
+
import { Cart } from '../cart.ts';
|
|
9
|
+
import { attr, sel, busy, emit } from '../dom.ts';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
* @param {EventTarget | null} target
|
|
13
|
-
* @returns {target is Element}
|
|
14
|
-
*/
|
|
15
|
-
const isElement = (target) => target instanceof window.Element;
|
|
11
|
+
const isElement = (target: EventTarget | null): target is Element => target instanceof window.Element;
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
* @param {Element} form
|
|
19
|
-
* @returns {HTMLInputElement | null}
|
|
20
|
-
*/
|
|
21
|
-
function codeInputOf(form) {
|
|
13
|
+
function codeInputOf(form: Element): HTMLInputElement | null {
|
|
22
14
|
const input = form.querySelector(sel('discount-input')) ?? form.querySelector('input');
|
|
23
15
|
return input instanceof window.HTMLInputElement ? input : null;
|
|
24
16
|
}
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
* @param {Element} form
|
|
28
|
-
* @param {HTMLInputElement | null} input
|
|
29
|
-
* @param {boolean} invalid
|
|
30
|
-
*/
|
|
31
|
-
function markInvalid(form, input, invalid) {
|
|
18
|
+
function markInvalid(form: Element, input: HTMLInputElement | null, invalid: boolean): void {
|
|
32
19
|
form.classList.toggle('is-invalid', invalid);
|
|
33
20
|
if (invalid) input?.setAttribute('aria-invalid', 'true');
|
|
34
21
|
else input?.removeAttribute('aria-invalid');
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
* @param {Element} form
|
|
39
|
-
* @param {HTMLInputElement | null} input
|
|
40
|
-
* @param {string} code
|
|
41
|
-
*/
|
|
42
|
-
async function applyCode(form, input, code) {
|
|
24
|
+
async function applyCode(form: Element, input: HTMLInputElement | null, code: string): Promise<void> {
|
|
43
25
|
try {
|
|
44
26
|
await Cart.applyDiscount(code);
|
|
45
27
|
markInvalid(form, input, false);
|
|
@@ -52,8 +34,7 @@ async function applyCode(form, input, code) {
|
|
|
52
34
|
}
|
|
53
35
|
}
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
function onSubmit(event) {
|
|
37
|
+
function onSubmit(event: SubmitEvent): void {
|
|
57
38
|
const { target } = event;
|
|
58
39
|
const form = isElement(target) ? target.closest(sel('discount-form')) : null;
|
|
59
40
|
if (!form) return;
|
|
@@ -64,8 +45,7 @@ function onSubmit(event) {
|
|
|
64
45
|
void busy(form, () => applyCode(form, input, code));
|
|
65
46
|
}
|
|
66
47
|
|
|
67
|
-
|
|
68
|
-
function onInput(event) {
|
|
48
|
+
function onInput(event: Event): void {
|
|
69
49
|
const { target } = event;
|
|
70
50
|
if (!isElement(target)) return;
|
|
71
51
|
const form = target.closest(sel('discount-form'));
|
|
@@ -74,21 +54,20 @@ function onInput(event) {
|
|
|
74
54
|
target.removeAttribute('aria-invalid');
|
|
75
55
|
}
|
|
76
56
|
|
|
77
|
-
|
|
78
|
-
function onClick(event) {
|
|
57
|
+
function onClick(event: MouseEvent): void {
|
|
79
58
|
const { target } = event;
|
|
80
59
|
const remove = isElement(target) ? target.closest(sel('discount-remove')) : null;
|
|
81
60
|
if (!remove) return;
|
|
82
61
|
event.preventDefault();
|
|
83
62
|
const code = remove.closest(sel('discount-code'))?.getAttribute(attr('discount-code'));
|
|
84
63
|
if (!code) return;
|
|
85
|
-
Cart.removeDiscount(code).catch((
|
|
64
|
+
Cart.removeDiscount(code).catch((error: unknown) => {
|
|
86
65
|
reportError(error, 'discount');
|
|
87
66
|
});
|
|
88
67
|
}
|
|
89
68
|
|
|
90
69
|
/** Binds the document-wide listeners, once per page load of the script. */
|
|
91
|
-
export function bindDiscountControls() {
|
|
70
|
+
export function bindDiscountControls(): void {
|
|
92
71
|
// Capture: the submit is handled before a router (Astro's ClientRouter) turns it into a navigation.
|
|
93
72
|
document.addEventListener('submit', onSubmit, true);
|
|
94
73
|
document.addEventListener('input', onInput);
|
|
@@ -3,28 +3,20 @@
|
|
|
3
3
|
* group add buttons, the cart or market changing in another tab, and shareable variant links. A part that can't load
|
|
4
4
|
* (a self-hosted site without `tether.extras.min.js`) is reported, so the missing file shows.
|
|
5
5
|
*/
|
|
6
|
-
import { cfg, reportError } from '../config.
|
|
7
|
-
import { attr, sel, busy } from '../dom.
|
|
8
|
-
import { loadPart } from '../lazy.
|
|
9
|
-
|
|
6
|
+
import { cfg, reportError } from '../config.ts';
|
|
7
|
+
import { attr, sel, busy, isOwnEvent } from '../dom.ts';
|
|
8
|
+
import { loadPart } from '../lazy.ts';
|
|
9
|
+
import type { ShopifyCart } from '../cart/store.ts';
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/** `[tether-group]` with `[tether-group-add]`: adds every product scope inside the group in one request. */
|
|
12
|
+
export const addGroup = (button: Element): Promise<ShopifyCart | null> =>
|
|
13
|
+
loadPart('extras').then((part) => part.addGroup(button));
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
* `[tether-group]` with `[tether-group-add]`: adds every product scope inside the group in one request.
|
|
15
|
-
*
|
|
16
|
-
* @param {Element} button
|
|
17
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
18
|
-
*/
|
|
19
|
-
export const addGroup = (button) => loadPart('extras').then((part) => part.addGroup(button));
|
|
20
|
-
|
|
21
|
-
/** @param {unknown} error */
|
|
22
|
-
const reportExtrasError = (error) => {
|
|
15
|
+
const reportExtrasError = (error: unknown) => {
|
|
23
16
|
reportError(error, 'extras');
|
|
24
17
|
};
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
function onClick(event) {
|
|
19
|
+
function onClick(event: MouseEvent): void {
|
|
28
20
|
const { target } = event;
|
|
29
21
|
const button = target instanceof window.Element ? target.closest(sel('group-add')) : null;
|
|
30
22
|
if (!button) return;
|
|
@@ -32,8 +24,7 @@ function onClick(event) {
|
|
|
32
24
|
busy(button, () => addGroup(button)).catch(reportExtrasError);
|
|
33
25
|
}
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
function onStorage(event) {
|
|
27
|
+
function onStorage(event: StorageEvent): void {
|
|
37
28
|
if (!cfg.domain || !event.key?.startsWith(`${cfg.prefix}:`)) return;
|
|
38
29
|
loadPart('extras')
|
|
39
30
|
.then((part) => {
|
|
@@ -42,8 +33,9 @@ function onStorage(event) {
|
|
|
42
33
|
.catch(reportExtrasError);
|
|
43
34
|
}
|
|
44
35
|
|
|
45
|
-
/** @param
|
|
46
|
-
function onVariantChanged(event) {
|
|
36
|
+
/** @param event `tether:variant-changed`, which a product scope emits; one another script dispatches is ignored. */
|
|
37
|
+
function onVariantChanged(event: Event): void {
|
|
38
|
+
if (!isOwnEvent(event, 'variant-changed')) return;
|
|
47
39
|
const { target } = event;
|
|
48
40
|
if (!(target instanceof window.Element)) return;
|
|
49
41
|
if (!cfg.variantUrl && !target.hasAttribute(attr('variant-url'))) return;
|
|
@@ -55,9 +47,8 @@ function onVariantChanged(event) {
|
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
/** Binds the document-wide listeners, once per page load of the script. */
|
|
58
|
-
export function bindExtrasControls() {
|
|
50
|
+
export function bindExtrasControls(): void {
|
|
59
51
|
document.addEventListener('click', onClick);
|
|
60
52
|
window.addEventListener('storage', onStorage);
|
|
61
|
-
|
|
62
|
-
document.addEventListener(`${cfg.prefix}:variant-changed`, /** @type {EventListener} */ (onVariantChanged));
|
|
53
|
+
document.addEventListener(`${cfg.prefix}:variant-changed`, onVariantChanged);
|
|
63
54
|
}
|
|
@@ -3,31 +3,29 @@
|
|
|
3
3
|
* `astro:before-swap`) mounts each new page in its market and closes the cart while leaving; a country select
|
|
4
4
|
* switches the market; back online, what failed to load loads again.
|
|
5
5
|
*/
|
|
6
|
-
import { cfg, reportError } from '../config.
|
|
7
|
-
import { attr, sel } from '../dom.
|
|
8
|
-
import { liveLists, retryFailed } from '../mounting.
|
|
9
|
-
import { marketForArrival, setCountry } from '../page-market.
|
|
6
|
+
import { cfg, reportError } from '../config.ts';
|
|
7
|
+
import { attr, sel } from '../dom.ts';
|
|
8
|
+
import { liveLists, retryFailed } from '../mounting.ts';
|
|
9
|
+
import { marketForArrival, setCountry } from '../page-market.ts';
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
/** What the entry module does for a page. */
|
|
12
|
+
interface PageHooks {
|
|
13
|
+
/** Settles once `init()` has applied the visitor's market. */
|
|
14
|
+
ready: () => Promise<unknown>;
|
|
15
|
+
mount: (root: Document) => unknown;
|
|
16
|
+
unmount: () => void;
|
|
17
|
+
}
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* @type {HTMLElement | null} */
|
|
22
|
-
let mountedBody = null;
|
|
19
|
+
/** The body the last full-document mount ran on: Astro's first `astro:page-load` comes after that mount. */
|
|
20
|
+
let mountedBody: HTMLElement | null = null;
|
|
23
21
|
|
|
24
22
|
/** Records a mount of the whole document, so the `astro:page-load` of the same page sends no second round of requests. */
|
|
25
|
-
export function rememberDocumentMount() {
|
|
23
|
+
export function rememberDocumentMount(): void {
|
|
26
24
|
mountedBody = document.body;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
/**
|
|
30
|
-
async function onPageLoad(hooks) {
|
|
27
|
+
/** Mounts the page Astro swapped in. It rejects when `init()` failed or the mount threw; the listener reports that. */
|
|
28
|
+
async function onPageLoad(hooks: PageHooks): Promise<void> {
|
|
31
29
|
await hooks.ready();
|
|
32
30
|
const target = marketForArrival();
|
|
33
31
|
if (!target) {
|
|
@@ -42,10 +40,11 @@ async function onPageLoad(hooks) {
|
|
|
42
40
|
hooks.mount(document);
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
function bindAstroRouter(hooks) {
|
|
43
|
+
function bindAstroRouter(hooks: PageHooks): void {
|
|
47
44
|
document.addEventListener('astro:page-load', () => {
|
|
48
|
-
|
|
45
|
+
onPageLoad(hooks).catch((error: unknown) => {
|
|
46
|
+
reportError(error, 'page mount');
|
|
47
|
+
});
|
|
49
48
|
});
|
|
50
49
|
document.addEventListener('astro:before-swap', () => {
|
|
51
50
|
mountedBody = null;
|
|
@@ -53,12 +52,11 @@ function bindAstroRouter(hooks) {
|
|
|
53
52
|
});
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
function onChange(event) {
|
|
55
|
+
function onChange(event: Event): void {
|
|
58
56
|
const { target } = event;
|
|
59
57
|
if (!(target instanceof window.HTMLSelectElement) || !target.matches(sel('country-select'))) return;
|
|
60
58
|
if (!target.value) return;
|
|
61
|
-
setCountry(target.value).catch((
|
|
59
|
+
setCountry(target.value).catch((error: unknown) => {
|
|
62
60
|
reportError(error, 'country');
|
|
63
61
|
});
|
|
64
62
|
}
|
|
@@ -71,12 +69,8 @@ function onOnline() {
|
|
|
71
69
|
});
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
* @param {PageHooks} hooks
|
|
78
|
-
*/
|
|
79
|
-
export function bindNavigation(hooks) {
|
|
72
|
+
/** Binds the document-wide listeners, once per page load of the script. */
|
|
73
|
+
export function bindNavigation(hooks: PageHooks): void {
|
|
80
74
|
if (cfg.astro) bindAstroRouter(hooks);
|
|
81
75
|
document.addEventListener('change', onChange);
|
|
82
76
|
window.addEventListener('online', onOnline);
|
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
* The markup half of `Tether.diagnose()`: problems that show without the network. It knows every value and attribute
|
|
3
3
|
* name Tether reads, so a typo gets a "did you mean".
|
|
4
4
|
*/
|
|
5
|
-
import { cfg } from './config.
|
|
6
|
-
import { attr, sel, get, qsa, hasTemplate } from './dom.
|
|
7
|
-
import { resolveProductKey, boundary } from './status.
|
|
5
|
+
import { cfg } from './config.ts';
|
|
6
|
+
import { attr, sel, get, qsa, hasTemplate } from './dom.ts';
|
|
7
|
+
import { resolveProductKey, boundary } from './status.ts';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
17
|
-
|
|
9
|
+
type ProblemLevel = 'error' | 'warn';
|
|
10
|
+
|
|
11
|
+
export interface Problem {
|
|
12
|
+
level: ProblemLevel;
|
|
13
|
+
message: string;
|
|
14
|
+
/** The element, described for people: `<div#id.class>`. */
|
|
15
|
+
element?: string;
|
|
16
|
+
/** The element itself, for the console. */
|
|
17
|
+
el?: Element;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type AddProblem = (level: ProblemLevel, message: string, el?: Element) => void;
|
|
18
21
|
|
|
19
22
|
/** Every `tether-show` value. */
|
|
20
23
|
const SHOW = new Set([
|
|
@@ -139,14 +142,9 @@ const IMAGE_BIND = /^image:\d+$/;
|
|
|
139
142
|
/** The class names of an element that its description shows. */
|
|
140
143
|
const DESCRIBED_CLASSES = 2;
|
|
141
144
|
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
* @returns {number} The Levenshtein distance: the fewest single-character edits from one to the other.
|
|
146
|
-
*/
|
|
147
|
-
function editDistance(from, to) {
|
|
148
|
-
/** @type {number[]} */
|
|
149
|
-
let previous = Array.from({ length: to.length + 1 }, (_, index) => index);
|
|
145
|
+
/** The Levenshtein distance: the fewest single-character edits from one string to the other. */
|
|
146
|
+
function editDistance(from: string, to: string): number {
|
|
147
|
+
let previous: number[] = Array.from({ length: to.length + 1 }, (_, index) => index);
|
|
150
148
|
for (let i = 1; i <= from.length; i++) {
|
|
151
149
|
const row = [i];
|
|
152
150
|
for (let j = 1; j <= to.length; j++) {
|
|
@@ -159,14 +157,13 @@ function editDistance(from, to) {
|
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
/**
|
|
162
|
-
* @param
|
|
163
|
-
* @param
|
|
164
|
-
* @param
|
|
165
|
-
* @returns
|
|
160
|
+
* @param value What the markup says.
|
|
161
|
+
* @param options What Tether reads.
|
|
162
|
+
* @param prefix Put before the suggestion, e.g. `tether-`.
|
|
163
|
+
* @returns ` Did you mean "…"?` for an option close enough to `value`, else `''`.
|
|
166
164
|
*/
|
|
167
|
-
export function didYouMean(value, options
|
|
168
|
-
|
|
169
|
-
let best = null;
|
|
165
|
+
export function didYouMean(value: string, options: Iterable<string>, prefix = ''): string {
|
|
166
|
+
let best: string | null = null;
|
|
170
167
|
let bestDistance = Infinity;
|
|
171
168
|
for (const option of options) {
|
|
172
169
|
const distance = editDistance(value, option);
|
|
@@ -179,11 +176,8 @@ export function didYouMean(value, options, prefix = '') {
|
|
|
179
176
|
return best !== null && bestDistance <= closeEnough ? ` Did you mean "${prefix}${best}"?` : '';
|
|
180
177
|
}
|
|
181
178
|
|
|
182
|
-
/**
|
|
183
|
-
|
|
184
|
-
* @returns {string} `<div#id.first.second>`.
|
|
185
|
-
*/
|
|
186
|
-
function describe(el) {
|
|
179
|
+
/** `el` described for people: `<div#id.first.second>`. */
|
|
180
|
+
function describe(el: Element): string {
|
|
187
181
|
const id = el.id ? `#${el.id}` : '';
|
|
188
182
|
const classes = el.className && typeof el.className === 'string' ? el.className.trim().split(/\s+/) : [];
|
|
189
183
|
const classText = classes.length ? `.${classes.slice(0, DESCRIBED_CLASSES).join('.')}` : '';
|
|
@@ -192,11 +186,7 @@ function describe(el) {
|
|
|
192
186
|
|
|
193
187
|
const listSelector = () => `${sel('collection')},${sel('products')},${sel('recommendations')}`;
|
|
194
188
|
|
|
195
|
-
|
|
196
|
-
* @param {ParentNode} root
|
|
197
|
-
* @param {AddProblem} add
|
|
198
|
-
*/
|
|
199
|
-
function checkProductKeys(root, add) {
|
|
189
|
+
function checkProductKeys(root: ParentNode, add: AddProblem): void {
|
|
200
190
|
qsa(root, sel('product')).forEach((el) => {
|
|
201
191
|
// A list card gets its handle when the list renders.
|
|
202
192
|
if (el.closest(listSelector()) && el.closest(sel('list'))) return;
|
|
@@ -213,11 +203,7 @@ function checkProductKeys(root, add) {
|
|
|
213
203
|
});
|
|
214
204
|
}
|
|
215
205
|
|
|
216
|
-
|
|
217
|
-
* @param {ParentNode} root
|
|
218
|
-
* @param {AddProblem} add
|
|
219
|
-
*/
|
|
220
|
-
function checkPlacement(root, add) {
|
|
206
|
+
function checkPlacement(root: ParentNode, add: AddProblem): void {
|
|
221
207
|
for (const name of PRODUCT_ONLY) {
|
|
222
208
|
qsa(root, sel(name)).forEach((el) => {
|
|
223
209
|
if (el.closest(sel('product'))) return;
|
|
@@ -242,11 +228,7 @@ function checkPlacement(root, add) {
|
|
|
242
228
|
});
|
|
243
229
|
}
|
|
244
230
|
|
|
245
|
-
|
|
246
|
-
* @param {ParentNode} root
|
|
247
|
-
* @param {AddProblem} add
|
|
248
|
-
*/
|
|
249
|
-
function checkValues(root, add) {
|
|
231
|
+
function checkValues(root: ParentNode, add: AddProblem): void {
|
|
250
232
|
qsa(root, sel('show')).forEach((el) => {
|
|
251
233
|
const value = get(el, 'show') ?? '';
|
|
252
234
|
if (!SHOW.has(value)) add('warn', `Unknown ${attr('show')}="${value}".${didYouMean(value, SHOW)}`, el);
|
|
@@ -258,11 +240,7 @@ function checkValues(root, add) {
|
|
|
258
240
|
});
|
|
259
241
|
}
|
|
260
242
|
|
|
261
|
-
|
|
262
|
-
* @param {ParentNode} root
|
|
263
|
-
* @param {AddProblem} add
|
|
264
|
-
*/
|
|
265
|
-
function checkMetafieldBinds(root, add) {
|
|
243
|
+
function checkMetafieldBinds(root: ParentNode, add: AddProblem): void {
|
|
266
244
|
qsa(root, sel('bind')).forEach((el) => {
|
|
267
245
|
const value = get(el, 'bind') || '';
|
|
268
246
|
if (!value.startsWith(METAFIELD_BIND)) return;
|
|
@@ -278,14 +256,10 @@ function checkMetafieldBinds(root, add) {
|
|
|
278
256
|
|
|
279
257
|
/**
|
|
280
258
|
* Typos in attribute names (tether-bnd, tether-add-to-card) do nothing, silently. Each unknown name is reported once.
|
|
281
|
-
*
|
|
282
|
-
* @param {ParentNode} root
|
|
283
|
-
* @param {AddProblem} add
|
|
284
259
|
*/
|
|
285
|
-
function checkAttributeNames(root, add) {
|
|
260
|
+
function checkAttributeNames(root: ParentNode, add: AddProblem): void {
|
|
286
261
|
const prefix = `${cfg.prefix}-`;
|
|
287
|
-
|
|
288
|
-
const reported = new Set();
|
|
262
|
+
const reported = new Set<string>();
|
|
289
263
|
qsa(root, '*').forEach((el) => {
|
|
290
264
|
for (const { name: fullName } of el.attributes) {
|
|
291
265
|
if (!fullName.startsWith(prefix)) continue;
|
|
@@ -297,11 +271,7 @@ function checkAttributeNames(root, add) {
|
|
|
297
271
|
});
|
|
298
272
|
}
|
|
299
273
|
|
|
300
|
-
|
|
301
|
-
* @param {ParentNode} root
|
|
302
|
-
* @param {AddProblem} add
|
|
303
|
-
*/
|
|
304
|
-
function checkCartAndScopes(root, add) {
|
|
274
|
+
function checkCartAndScopes(root: ParentNode, add: AddProblem): void {
|
|
305
275
|
const drawers = qsa(root, sel('cart-drawer')).length;
|
|
306
276
|
if (qsa(root, sel('cart-open')).length && !drawers) {
|
|
307
277
|
add('warn', `${attr('cart-open')} exists but there is no ${attr('cart-drawer')} on this page.`);
|
|
@@ -312,15 +282,10 @@ function checkCartAndScopes(root, add) {
|
|
|
312
282
|
});
|
|
313
283
|
}
|
|
314
284
|
|
|
315
|
-
/**
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
export function checkMarkup(root = document) {
|
|
320
|
-
/** @type {Problem[]} */
|
|
321
|
-
const problems = [];
|
|
322
|
-
/** @type {AddProblem} */
|
|
323
|
-
const add = (level, message, el) => {
|
|
285
|
+
/** The markup problems in `root`, in page order per check. */
|
|
286
|
+
export function checkMarkup(root: ParentNode = document): Problem[] {
|
|
287
|
+
const problems: Problem[] = [];
|
|
288
|
+
const add: AddProblem = (level, message, el) => {
|
|
324
289
|
problems.push(el ? { level, message, element: describe(el), el } : { level, message });
|
|
325
290
|
};
|
|
326
291
|
checkProductKeys(root, add);
|