@occluze/tether 0.7.0 → 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.
Files changed (132) hide show
  1. package/README.md +21 -21
  2. package/dist/esm/chunks/{account-UIPWJQ3Y.js → account-CXMA5RPZ.js} +1 -1
  3. package/dist/esm/chunks/{account-pages-6ROA6Y4V.js → account-pages-IZO7FEOZ.js} +1 -1
  4. package/dist/esm/chunks/{analytics-2V632IRV.js → analytics-PG5FPRDN.js} +1 -1
  5. package/dist/esm/chunks/{chunk-4GCXM764.js → chunk-4HD4J5JC.js} +1 -1
  6. package/dist/esm/chunks/{chunk-BFTZR4OB.js → chunk-6TFMLT4A.js} +2 -2
  7. package/dist/esm/chunks/{chunk-T44MUL4Y.js → chunk-BBVUVPMO.js} +1 -1
  8. package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
  9. package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
  10. package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
  11. package/dist/esm/chunks/{chunk-5Z5O3767.js → chunk-N3V45JPS.js} +1 -1
  12. package/dist/esm/chunks/{chunk-YLUULATY.js → chunk-RPMBCAGS.js} +1 -1
  13. package/dist/esm/chunks/{chunk-MPDWM2NN.js → chunk-VFNHIGCS.js} +1 -1
  14. package/dist/esm/chunks/{chunk-6OZJCAGZ.js → chunk-WT7RGGJU.js} +1 -1
  15. package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
  16. package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
  17. package/dist/esm/chunks/{diagnose-3FEKRIEK.js → diagnose-XCEBQKBO.js} +1 -1
  18. package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
  19. package/dist/esm/chunks/{markets-ui-FD6NI2OG.js → markets-ui-M4IQ62CT.js} +1 -1
  20. package/dist/esm/chunks/search-A5DS2IOS.js +1 -0
  21. package/dist/esm/index.js +1 -1
  22. package/dist/esm/server.js +2 -2
  23. package/dist/tether.account-pages.js +13 -3
  24. package/dist/tether.account-pages.min.js +1 -1
  25. package/dist/tether.account.js +13 -3
  26. package/dist/tether.account.min.js +1 -1
  27. package/dist/tether.analytics.js +18 -3
  28. package/dist/tether.analytics.min.js +1 -1
  29. package/dist/tether.diagnose.js +18 -3
  30. package/dist/tether.diagnose.min.js +2 -2
  31. package/dist/tether.extras.js +19 -6
  32. package/dist/tether.extras.min.js +1 -1
  33. package/dist/tether.js +162 -112
  34. package/dist/tether.markets.js +18 -3
  35. package/dist/tether.markets.min.js +1 -1
  36. package/dist/tether.min.js +3 -3
  37. package/dist/tether.min.js.map +4 -4
  38. package/dist/tether.search.js +18 -3
  39. package/dist/tether.search.min.js +1 -1
  40. package/dist/types/server/src/shared/cart-id.d.ts +2 -0
  41. package/dist/types/src/cart/mutations.d.ts +6 -0
  42. package/dist/types/src/cart/store.d.ts +5 -2
  43. package/dist/types/src/cart.d.ts +2 -2
  44. package/dist/types/src/client.d.ts +53 -19
  45. package/dist/types/src/config.d.ts +6 -1
  46. package/dist/types/src/index.d.ts +2 -2
  47. package/dist/types/src/product-shape.d.ts +3 -1
  48. package/package.json +1 -2
  49. package/dist/esm/chunks/chunk-56FCLJJD.js +0 -1
  50. package/dist/esm/chunks/chunk-A5SYCVQM.js +0 -1
  51. package/dist/esm/chunks/chunk-BA4LQJJQ.js +0 -1
  52. package/dist/esm/chunks/chunk-C5JYQCWH.js +0 -1
  53. package/dist/esm/chunks/extras-6POFBNOP.js +0 -1
  54. package/dist/esm/chunks/search-UJ73YBQ5.js +0 -1
  55. package/src/account/addresses-view.ts +0 -58
  56. package/src/account/answers.ts +0 -79
  57. package/src/account/markup.ts +0 -36
  58. package/src/account/operations.ts +0 -145
  59. package/src/account/order-lines.ts +0 -75
  60. package/src/account/order-list.ts +0 -107
  61. package/src/account/order-page.ts +0 -137
  62. package/src/account/page-answers.ts +0 -317
  63. package/src/account/page-events.ts +0 -253
  64. package/src/account/server-client.ts +0 -183
  65. package/src/account/subscriptions-view.ts +0 -115
  66. package/src/account-api.ts +0 -98
  67. package/src/account-marks.ts +0 -35
  68. package/src/account-pages.ts +0 -81
  69. package/src/account.ts +0 -284
  70. package/src/amounts.ts +0 -64
  71. package/src/analytics-shopify.ts +0 -246
  72. package/src/analytics.ts +0 -315
  73. package/src/api-version.ts +0 -79
  74. package/src/astro.ts +0 -272
  75. package/src/cart/cart-shape.ts +0 -25
  76. package/src/cart/checkout.ts +0 -101
  77. package/src/cart/drawer.ts +0 -125
  78. package/src/cart/mutations.ts +0 -237
  79. package/src/cart/render.ts +0 -266
  80. package/src/cart/store.ts +0 -279
  81. package/src/cart.ts +0 -345
  82. package/src/client.ts +0 -391
  83. package/src/collection.ts +0 -175
  84. package/src/config-sections.ts +0 -284
  85. package/src/config.ts +0 -480
  86. package/src/controls/cart-controls.ts +0 -123
  87. package/src/controls/discount.ts +0 -75
  88. package/src/controls/extras.ts +0 -54
  89. package/src/controls/navigation.ts +0 -77
  90. package/src/diagnose-markup.ts +0 -298
  91. package/src/diagnose.ts +0 -294
  92. package/src/dom.ts +0 -268
  93. package/src/extras.ts +0 -136
  94. package/src/fragments.ts +0 -261
  95. package/src/globals.d.ts +0 -15
  96. package/src/images.ts +0 -91
  97. package/src/index.ts +0 -415
  98. package/src/initial.ts +0 -101
  99. package/src/lazy-iife.ts +0 -197
  100. package/src/lazy.ts +0 -42
  101. package/src/loader.ts +0 -363
  102. package/src/market-codes.ts +0 -6
  103. package/src/markets-ui.ts +0 -227
  104. package/src/markets.ts +0 -246
  105. package/src/markup.ts +0 -127
  106. package/src/mounting.ts +0 -264
  107. package/src/page-market.ts +0 -110
  108. package/src/part-integrity.ts +0 -7
  109. package/src/parts/account-pages.ts +0 -7
  110. package/src/parts/account.ts +0 -7
  111. package/src/parts/analytics.ts +0 -7
  112. package/src/parts/diagnose.ts +0 -7
  113. package/src/parts/extras.ts +0 -7
  114. package/src/parts/markets.ts +0 -7
  115. package/src/parts/search.ts +0 -7
  116. package/src/presets/barba.ts +0 -194
  117. package/src/product-events.ts +0 -115
  118. package/src/product-render.ts +0 -218
  119. package/src/product-shape.ts +0 -49
  120. package/src/product-state.ts +0 -318
  121. package/src/product.ts +0 -308
  122. package/src/repeaters.ts +0 -161
  123. package/src/search.ts +0 -305
  124. package/src/selling-plans.ts +0 -145
  125. package/src/server.ts +0 -211
  126. package/src/shopify-values.ts +0 -91
  127. package/src/status.ts +0 -106
  128. package/src/storage.ts +0 -34
  129. package/src/store-html.ts +0 -359
  130. package/src/storefront/catalog.ts +0 -196
  131. package/src/storefront/json-ld.ts +0 -144
  132. package/src/variant-render.ts +0 -139
@@ -1,144 +0,0 @@
1
- /**
2
- * schema.org data for search engines (Google's product rich results): a `Product`, or a `ProductGroup` with one
3
- * `Product` per variant, each with its price, currency, stock, sku, image and `?variant=` link. Products that are only
4
- * sold on a subscription are priced at their first plan.
5
- */
6
- import { allPlans, allocationFor, planPrice } from '../selling-plans.ts';
7
- import { productUrl } from '../shopify-values.ts';
8
- import type { Product, ProductVariant } from '../fragments.ts';
9
- import type { Money } from '../shopify-values.ts';
10
-
11
- export interface JsonLdOptions {
12
- /** The product page's absolute URL; default from the storefront's `siteUrl`. */
13
- url?: string;
14
- /** Default the product's vendor. */
15
- brand?: string;
16
- }
17
-
18
- /** Where the product pages are. */
19
- interface JsonLdSite {
20
- productPath: string;
21
- siteUrl: string | undefined;
22
- }
23
-
24
- /** The option names schema.org has a property for: a `ProductGroup` varies by them. */
25
- const VARIES_BY: Readonly<Record<string, string>> = {
26
- size: 'https://schema.org/size',
27
- color: 'https://schema.org/color',
28
- colour: 'https://schema.org/color',
29
- material: 'https://schema.org/material',
30
- };
31
-
32
- /**
33
- * Text for a `<script>` tag: `<` is escaped so product text can never close the tag, and U+2028 / U+2029 so older
34
- * JavaScript parsers read it.
35
- */
36
- export const scriptJson = (value: unknown): string =>
37
- JSON.stringify(value)
38
- .replace(/</g, '\\u003c')
39
- .replace(/\u2028/g, '\\u2028')
40
- .replace(/\u2029/g, '\\u2029');
41
-
42
- /** @returns `value` without its `undefined` fields, nested ones too. */
43
- function withoutUndefined(value: Record<string, unknown>): Record<string, unknown> {
44
- const copy: unknown = JSON.parse(JSON.stringify(value));
45
- // The JSON of an object is an object.
46
- return copy as Record<string, unknown>;
47
- }
48
-
49
- const numericId = (gid: string) => gid.split('/').pop() ?? gid;
50
-
51
- /** @returns The schema.org availability. */
52
- function availability(variant: ProductVariant): string {
53
- if (!variant.availableForSale) return 'https://schema.org/OutOfStock';
54
- return variant.currentlyNotInStock ? 'https://schema.org/BackOrder' : 'https://schema.org/InStock';
55
- }
56
-
57
- /** @returns The variant's price, or its first plan's for a product sold only on a subscription. */
58
- function offeredPrice(product: Product, variant: ProductVariant): Money {
59
- if (!product.requiresSellingPlan) return variant.price;
60
- const plan = allPlans(product).find((candidate) => allocationFor(variant, candidate.id));
61
- return (plan && planPrice(variant, plan.id)) ?? variant.price;
62
- }
63
-
64
- function offer(product: Product, variant: ProductVariant, pageUrl: string | undefined) {
65
- const price = offeredPrice(product, variant);
66
- const separator = pageUrl?.includes('?') ? '&' : '?';
67
- return {
68
- '@type': 'Offer',
69
- price: price.amount,
70
- priceCurrency: price.currencyCode,
71
- availability: availability(variant),
72
- url: pageUrl ? `${pageUrl}${separator}variant=${numericId(variant.id)}` : undefined,
73
- };
74
- }
75
-
76
- function variantProduct(
77
- product: Product,
78
- variant: ProductVariant,
79
- pageUrl: string | undefined,
80
- images: readonly string[],
81
- ) {
82
- // schema.org property name → the variant's value.
83
- const properties = variant.selectedOptions.flatMap((option): [string, string][] => {
84
- const property = VARIES_BY[option.name.toLowerCase()];
85
- return property ? [[property.split('/').pop() ?? property, option.value]] : [];
86
- });
87
- return {
88
- '@type': 'Product',
89
- name: `${product.title} - ${variant.title}`,
90
- sku: variant.sku || undefined,
91
- image: variant.image?.url || images[0],
92
- ...Object.fromEntries(properties),
93
- offers: offer(product, variant, pageUrl),
94
- };
95
- }
96
-
97
- function pageUrlOf(product: Product, { url }: JsonLdOptions, site: JsonLdSite): string | undefined {
98
- if (url) return url;
99
- const path = productUrl(site.productPath, product.handle, product.id);
100
- if (!site.siteUrl || !path) return undefined;
101
- return new URL(path, site.siteUrl).toString();
102
- }
103
-
104
- /** @returns The schema.org object, `null` without a product. */
105
- export function jsonLdData(
106
- product: Product | null | undefined,
107
- options: JsonLdOptions,
108
- site: JsonLdSite,
109
- ): Record<string, unknown> | null {
110
- if (!product) return null;
111
- const pageUrl = pageUrlOf(product, options, site);
112
- const images = product.images.nodes.map((image) => image.url).filter(Boolean);
113
- const brandName = options.brand || product.vendor;
114
- const brand = brandName ? { '@type': 'Brand', name: brandName } : undefined;
115
- const description = product.description.trim() || undefined;
116
- const variants = product.variants.nodes;
117
- if (variants.length <= 1) {
118
- const [variant] = variants;
119
- return withoutUndefined({
120
- '@context': 'https://schema.org',
121
- '@type': 'Product',
122
- name: product.title,
123
- description,
124
- url: pageUrl,
125
- image: images,
126
- sku: variant?.sku || undefined,
127
- brand,
128
- offers: variant ? offer(product, variant, pageUrl) : undefined,
129
- });
130
- }
131
- const variesBy = product.options.map((option) => VARIES_BY[option.name.toLowerCase()]).filter(Boolean);
132
- return withoutUndefined({
133
- '@context': 'https://schema.org',
134
- '@type': 'ProductGroup',
135
- name: product.title,
136
- description,
137
- url: pageUrl,
138
- productGroupID: numericId(product.id),
139
- image: images,
140
- brand,
141
- variesBy: [...new Set(variesBy)],
142
- hasVariant: variants.map((variant) => variantProduct(product, variant, pageUrl, images)),
143
- });
144
- }
@@ -1,139 +0,0 @@
1
- /**
2
- * The DOM writes of a product scope that follow the visitor's choice, on every change: `product-state.ts`'s view as
3
- * text, selected and unavailable states, `tether-show` conditions and the add-to-cart buttons.
4
- */
5
- import { attr, sel, get, qsa, formatMoney, formatPercent, setText, setImage, setSelected, show } from './dom.ts';
6
- import { STATUS_KEYS, ownedBy } from './status.ts';
7
- import { allocationFor } from './selling-plans.ts';
8
- import { isValueAvailable, planBindTexts, variantBindTexts } from './product-state.ts';
9
- import { isSelect, selectsIn } from './product-render.ts';
10
- import type { Formatters, ProductView } from './product-state.ts';
11
- import type { RenderScope } from './product-render.ts';
12
-
13
- const FORMAT: Formatters = { money: formatMoney, percent: formatPercent };
14
-
15
- const isInput = (el: Element): el is HTMLInputElement => el.tagName === 'INPUT';
16
-
17
- /**
18
- * The element of an add-to-cart button that holds its label, with the label as written kept in
19
- * `data-tether-label`, so mode-dependent texts (`tether-subscribe-text`, `tether-soldout-text`) can be undone.
20
- */
21
- export function addButtonLabel(button: HTMLElement): { label: HTMLElement; original: string } {
22
- const label = qsa(button, sel('add-label'))[0] ?? button;
23
- const original = label.dataset['tetherLabel'] ?? (isInput(label) ? label.value : label.textContent);
24
- label.dataset['tetherLabel'] = original;
25
- return { label, original };
26
- }
27
-
28
- function renderOptionStates(scope: RenderScope, view: ProductView): void {
29
- const { product } = scope;
30
- const selectedValue = (authored: string | null) => view.selected[scope.optName(authored)] ?? '';
31
- scope.$(sel('option-selected')).forEach((el) => {
32
- setText(el, selectedValue(get(el, 'option-selected')));
33
- });
34
- scope.$(sel('option-list')).forEach((container) => {
35
- const name = scope.optName(get(container, 'option-list'));
36
- qsa(container, sel('option-value')).forEach((node) => {
37
- const value = get(node, 'option-value') ?? '';
38
- setSelected(node, view.selected[name] === value);
39
- const unavailable = !isValueAvailable(product, view.selected, name, value);
40
- node.classList.toggle('is-unavailable', unavailable);
41
- node.setAttribute('data-unavailable', unavailable ? 'true' : 'false');
42
- });
43
- });
44
- selectsIn(scope, sel('option')).forEach((select) => {
45
- select.value = selectedValue(get(select, 'option'));
46
- });
47
- selectsIn(scope, sel('variant-select')).forEach((select) => {
48
- select.value = view.variant.id;
49
- });
50
- }
51
-
52
- /**
53
- * The purchase toggles and the plan list and select.
54
- */
55
- function renderPurchaseStates(scope: RenderScope, view: ProductView): void {
56
- const { variant, purchase, line } = view;
57
- scope.$(sel('purchase')).forEach((el) => {
58
- if (isSelect(el)) {
59
- el.value = purchase.kind;
60
- return;
61
- }
62
- const mode = get(el, 'purchase');
63
- setSelected(el, mode === purchase.kind);
64
- const unavailable = mode === 'subscription' ? !view.subscriptionAvailable : scope.product.requiresSellingPlan;
65
- el.classList.toggle('is-unavailable', unavailable);
66
- });
67
- scope.$(sel('plan-value')).forEach((node) => {
68
- const planId = get(node, 'plan-value') ?? '';
69
- setSelected(node, line.kind === 'subscription' && planId === line.planId);
70
- node.classList.toggle('is-unavailable', !allocationFor(variant, planId));
71
- const plan = scope.plans.find((candidate) => candidate.id === planId);
72
- const texts = planBindTexts(variant, planId, plan, FORMAT);
73
- qsa(node, sel('plan-bind')).forEach((el) => {
74
- const text = texts.get(get(el, 'plan-bind') ?? '');
75
- if (text) setText(el, text());
76
- });
77
- });
78
- selectsIn(scope, sel('plan')).forEach((select) => {
79
- select.value = line.kind === 'subscription' ? line.planId : '';
80
- });
81
- }
82
-
83
- /**
84
- * The `tether-show` conditions, with the scope's own status: a product scope that renders is ready.
85
- */
86
- function renderConditions(scope: RenderScope, view: ProductView): void {
87
- const conditions = new Map<string, boolean>(Object.entries(view.show));
88
- STATUS_KEYS.forEach((status) => conditions.set(status, status === 'ready'));
89
- scope.$(sel('show')).forEach((el) => {
90
- const key = get(el, 'show') ?? '';
91
- const visible = conditions.get(key);
92
- if (visible === undefined) return;
93
- // State blocks of a list nested in this product belong to that list.
94
- if (STATUS_KEYS.some((status) => status === key) && !ownedBy(el, scope.root)) return;
95
- show(el, visible);
96
- });
97
- }
98
-
99
- function renderAddButtons(scope: RenderScope, view: ProductView): void {
100
- const disabled = !view.variant.availableForSale;
101
- scope.$(sel('add-to-cart')).forEach((button) => {
102
- button.classList.toggle('is-disabled', disabled);
103
- button.setAttribute('aria-disabled', String(disabled));
104
- // A <button> or <input> is disabled; a link or div carries only aria-disabled.
105
- if ('disabled' in button) button.disabled = disabled;
106
- const soldOutText = get(button, 'soldout-text');
107
- const subscribeText = get(button, 'subscribe-text');
108
- if (!soldOutText && !subscribeText) return;
109
- const { label, original } = addButtonLabel(button);
110
- let text = original;
111
- if (view.line.kind === 'subscription' && subscribeText) text = subscribeText;
112
- if (disabled && soldOutText) text = soldOutText;
113
- if (button.getAttribute('aria-busy') !== 'true') setText(label, text);
114
- });
115
- }
116
-
117
- /**
118
- * Writes what follows the variant and purchase.
119
- */
120
- export function writeVariant(scope: RenderScope, view: ProductView): void {
121
- const { variant } = view;
122
- const { product } = scope;
123
- const texts = variantBindTexts(view, FORMAT);
124
- scope.$(sel('bind')).forEach((el) => {
125
- const key = get(el, 'bind') ?? '';
126
- if (key === 'variant-image') {
127
- setImage(el, variant.image?.url || product.featuredImage?.url, variant.image?.altText || product.title);
128
- return;
129
- }
130
- const text = texts.get(key);
131
- if (text) setText(el, text());
132
- });
133
- renderOptionStates(scope, view);
134
- renderPurchaseStates(scope, view);
135
- renderConditions(scope, view);
136
- scope.root.classList.toggle('is-subscription', view.line.kind === 'subscription');
137
- scope.root.setAttribute(attr('purchase-mode'), view.line.kind);
138
- renderAddButtons(scope, view);
139
- }