@decocms/apps 0.23.3 → 0.25.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/LICENSE +21 -0
- package/commerce/components/Image.tsx +129 -143
- package/commerce/components/JsonLd.tsx +192 -201
- package/commerce/components/Picture.tsx +65 -75
- package/commerce/sdk/analytics.ts +15 -15
- package/commerce/sdk/formatPrice.ts +13 -16
- package/commerce/sdk/url.ts +7 -7
- package/commerce/sdk/useOffer.ts +46 -57
- package/commerce/sdk/useVariantPossibilities.ts +25 -25
- package/commerce/types/commerce.ts +868 -875
- package/commerce/utils/canonical.ts +5 -8
- package/commerce/utils/constants.ts +5 -6
- package/commerce/utils/filters.ts +4 -4
- package/commerce/utils/productToAnalyticsItem.ts +52 -56
- package/commerce/utils/stateByZip.ts +42 -42
- package/package.json +24 -4
- package/shopify/actions/cart/addItems.ts +24 -25
- package/shopify/actions/cart/updateCoupons.ts +19 -20
- package/shopify/actions/cart/updateItems.ts +19 -20
- package/shopify/actions/user/signIn.ts +25 -30
- package/shopify/actions/user/signUp.ts +19 -24
- package/shopify/client.ts +24 -24
- package/shopify/index.ts +20 -18
- package/shopify/init.ts +18 -21
- package/shopify/loaders/ProductDetailsPage.ts +16 -20
- package/shopify/loaders/ProductList.ts +66 -69
- package/shopify/loaders/ProductListingPage.ts +150 -158
- package/shopify/loaders/RelatedProducts.ts +24 -27
- package/shopify/loaders/cart.ts +53 -52
- package/shopify/loaders/shop.ts +22 -27
- package/shopify/loaders/user.ts +27 -32
- package/shopify/utils/admin/admin.ts +33 -34
- package/shopify/utils/admin/queries.ts +2 -2
- package/shopify/utils/cart.ts +18 -14
- package/shopify/utils/cookies.ts +62 -65
- package/shopify/utils/enums.ts +424 -424
- package/shopify/utils/graphql.ts +44 -55
- package/shopify/utils/storefront/queries.ts +24 -29
- package/shopify/utils/storefront/storefront.graphql.gen.ts +55 -55
- package/shopify/utils/transform.ts +370 -376
- package/shopify/utils/types.ts +118 -118
- package/shopify/utils/user.ts +11 -11
- package/shopify/utils/utils.ts +135 -140
- package/vtex/actions/address.ts +86 -86
- package/vtex/actions/auth.ts +14 -27
- package/vtex/actions/checkout.ts +36 -49
- package/vtex/actions/masterData.ts +10 -27
- package/vtex/actions/misc.ts +101 -111
- package/vtex/actions/newsletter.ts +48 -52
- package/vtex/actions/orders.ts +13 -16
- package/vtex/actions/profile.ts +55 -55
- package/vtex/actions/session.ts +36 -35
- package/vtex/actions/trigger.ts +25 -25
- package/vtex/actions/wishlist.ts +51 -53
- package/vtex/client.ts +14 -42
- package/vtex/hooks/index.ts +4 -4
- package/vtex/hooks/useAutocomplete.ts +42 -48
- package/vtex/hooks/useCart.ts +153 -165
- package/vtex/hooks/useUser.ts +40 -40
- package/vtex/hooks/useWishlist.ts +70 -70
- package/vtex/inline-loaders/productDetailsPage.ts +1 -3
- package/vtex/inline-loaders/productList.ts +121 -127
- package/vtex/inline-loaders/productListShelf.ts +159 -0
- package/vtex/inline-loaders/productListingPage.ts +10 -34
- package/vtex/inline-loaders/relatedProducts.ts +1 -3
- package/vtex/inline-loaders/suggestions.ts +36 -39
- package/vtex/inline-loaders/workflowProducts.ts +45 -49
- package/vtex/invoke.ts +159 -194
- package/vtex/loaders/address.ts +49 -54
- package/vtex/loaders/brands.ts +19 -26
- package/vtex/loaders/cart.ts +24 -21
- package/vtex/loaders/catalog.ts +51 -53
- package/vtex/loaders/collections.ts +25 -27
- package/vtex/loaders/legacy.ts +487 -534
- package/vtex/loaders/logistics.ts +33 -37
- package/vtex/loaders/navbar.ts +5 -8
- package/vtex/loaders/orders.ts +28 -39
- package/vtex/loaders/pageType.ts +41 -35
- package/vtex/loaders/payment.ts +27 -37
- package/vtex/loaders/profile.ts +38 -38
- package/vtex/loaders/promotion.ts +5 -8
- package/vtex/loaders/search.ts +56 -59
- package/vtex/loaders/session.ts +22 -30
- package/vtex/loaders/user.ts +39 -41
- package/vtex/loaders/wishlist.ts +35 -35
- package/vtex/loaders/wishlistProducts.ts +3 -15
- package/vtex/loaders/workflow.ts +220 -227
- package/vtex/middleware.ts +116 -119
- package/vtex/types.ts +201 -201
- package/vtex/utils/batch.ts +13 -16
- package/vtex/utils/cookies.ts +76 -80
- package/vtex/utils/enrichment.ts +62 -42
- package/vtex/utils/fetchCache.ts +1 -4
- package/vtex/utils/index.ts +6 -6
- package/vtex/utils/intelligentSearch.ts +48 -57
- package/vtex/utils/legacy.ts +108 -124
- package/vtex/utils/pickAndOmit.ts +15 -20
- package/vtex/utils/proxy.ts +136 -146
- package/vtex/utils/resourceRange.ts +3 -3
- package/vtex/utils/segment.ts +100 -111
- package/vtex/utils/similars.ts +1 -2
- package/vtex/utils/sitemap.ts +91 -91
- package/vtex/utils/slugCache.ts +2 -6
- package/vtex/utils/slugify.ts +9 -9
- package/vtex/utils/transform.ts +1178 -1105
- package/vtex/utils/types.ts +1381 -1381
- package/vtex/utils/vtexId.ts +44 -47
- package/.github/workflows/release.yml +0 -34
- package/.releaserc.json +0 -28
- package/knip.json +0 -19
- package/tsconfig.json +0 -11
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import type {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
AggregateOffer,
|
|
25
|
+
AggregateRating,
|
|
26
|
+
BreadcrumbList,
|
|
27
|
+
ListItem,
|
|
28
|
+
Offer,
|
|
29
|
+
Product,
|
|
30
|
+
ProductListingPage,
|
|
31
|
+
UnitPriceSpecification,
|
|
32
32
|
} from "../types/commerce";
|
|
33
33
|
|
|
34
34
|
// -------------------------------------------------------------------------
|
|
@@ -36,12 +36,9 @@ import type {
|
|
|
36
36
|
// -------------------------------------------------------------------------
|
|
37
37
|
|
|
38
38
|
function JsonLdScript({ data }: { data: unknown }) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
39
|
+
return (
|
|
40
|
+
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />
|
|
41
|
+
);
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
// -------------------------------------------------------------------------
|
|
@@ -49,103 +46,96 @@ function JsonLdScript({ data }: { data: unknown }) {
|
|
|
49
46
|
// -------------------------------------------------------------------------
|
|
50
47
|
|
|
51
48
|
export interface ProductJsonLdProps {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
product: Product;
|
|
50
|
+
/** Override the canonical URL. Defaults to product.url. */
|
|
51
|
+
url?: string;
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
function getBestOffer(offers: Offer[] | AggregateOffer | undefined): {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
price?: number;
|
|
56
|
+
priceCurrency?: string;
|
|
57
|
+
availability?: string;
|
|
58
|
+
seller?: string;
|
|
59
|
+
priceValidUntil?: string;
|
|
63
60
|
} {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
61
|
+
if (!offers) return {};
|
|
62
|
+
|
|
63
|
+
if ("@type" in offers && offers["@type"] === "AggregateOffer") {
|
|
64
|
+
const agg = offers as AggregateOffer;
|
|
65
|
+
return {
|
|
66
|
+
price: agg.lowPrice,
|
|
67
|
+
priceCurrency: agg.priceCurrency,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(offers) && offers.length > 0) {
|
|
72
|
+
const best = offers.reduce((a, b) => {
|
|
73
|
+
const ap = a.price ?? Infinity;
|
|
74
|
+
const bp = b.price ?? Infinity;
|
|
75
|
+
return ap <= bp ? a : b;
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
price: best.price,
|
|
79
|
+
priceCurrency: best.priceCurrency,
|
|
80
|
+
availability: best.availability,
|
|
81
|
+
seller: best.seller,
|
|
82
|
+
priceValidUntil: best.priceValidUntil,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {};
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
p.priceType === "https://schema.org/SRP",
|
|
100
|
-
);
|
|
101
|
-
return list?.price;
|
|
89
|
+
function _getListPrice(priceSpec: UnitPriceSpecification[] | undefined): number | undefined {
|
|
90
|
+
if (!priceSpec) return undefined;
|
|
91
|
+
const list = priceSpec.find(
|
|
92
|
+
(p) =>
|
|
93
|
+
p.priceType === "https://schema.org/ListPrice" || p.priceType === "https://schema.org/SRP",
|
|
94
|
+
);
|
|
95
|
+
return list?.price;
|
|
102
96
|
}
|
|
103
97
|
|
|
104
98
|
export function ProductJsonLd({ product, url }: ProductJsonLdProps) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return <JsonLdScript data={data} />;
|
|
99
|
+
const offer = getBestOffer(product.offers as Offer[] | AggregateOffer | undefined);
|
|
100
|
+
const images = product.image?.map((img) => img.url).filter(Boolean) ?? [];
|
|
101
|
+
const rating = product.aggregateRating as AggregateRating | undefined;
|
|
102
|
+
|
|
103
|
+
const data: Record<string, unknown> = {
|
|
104
|
+
"@context": "https://schema.org",
|
|
105
|
+
"@type": "Product",
|
|
106
|
+
name: product.name,
|
|
107
|
+
description: product.description,
|
|
108
|
+
image: images.length === 1 ? images[0] : images,
|
|
109
|
+
url: url ?? product.url,
|
|
110
|
+
sku: product.sku,
|
|
111
|
+
productID: product.productID,
|
|
112
|
+
brand: product.brand ? { "@type": "Brand", name: product.brand.name } : undefined,
|
|
113
|
+
gtin: product.gtin,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (offer.price != null) {
|
|
117
|
+
data.offers = {
|
|
118
|
+
"@type": "Offer",
|
|
119
|
+
price: offer.price,
|
|
120
|
+
priceCurrency: offer.priceCurrency ?? "BRL",
|
|
121
|
+
availability: offer.availability ?? "https://schema.org/InStock",
|
|
122
|
+
seller: offer.seller ? { "@type": "Organization", name: offer.seller } : undefined,
|
|
123
|
+
priceValidUntil: offer.priceValidUntil,
|
|
124
|
+
url: url ?? product.url,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (rating?.ratingValue) {
|
|
129
|
+
data.aggregateRating = {
|
|
130
|
+
"@type": "AggregateRating",
|
|
131
|
+
ratingValue: rating.ratingValue,
|
|
132
|
+
reviewCount: rating.reviewCount ?? rating.ratingCount ?? 0,
|
|
133
|
+
bestRating: rating.bestRating ?? 5,
|
|
134
|
+
worstRating: rating.worstRating ?? 1,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return <JsonLdScript data={data} />;
|
|
149
139
|
}
|
|
150
140
|
|
|
151
141
|
// -------------------------------------------------------------------------
|
|
@@ -153,45 +143,46 @@ export function ProductJsonLd({ product, url }: ProductJsonLdProps) {
|
|
|
153
143
|
// -------------------------------------------------------------------------
|
|
154
144
|
|
|
155
145
|
export interface PLPJsonLdProps {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
page: ProductListingPage;
|
|
147
|
+
/** Override the canonical URL. */
|
|
148
|
+
url?: string;
|
|
159
149
|
}
|
|
160
150
|
|
|
161
151
|
export function PLPJsonLd({ page, url }: PLPJsonLdProps) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
152
|
+
const items = (page.products ?? []).map((product, index) => {
|
|
153
|
+
const offer = getBestOffer(product.offers as Offer[] | AggregateOffer | undefined);
|
|
154
|
+
return {
|
|
155
|
+
"@type": "ListItem" as const,
|
|
156
|
+
position: index + 1,
|
|
157
|
+
item: {
|
|
158
|
+
"@type": "Product" as const,
|
|
159
|
+
name: product.name,
|
|
160
|
+
url: product.url,
|
|
161
|
+
image: product.image?.[0]?.url,
|
|
162
|
+
offers:
|
|
163
|
+
offer.price != null
|
|
164
|
+
? {
|
|
165
|
+
"@type": "Offer" as const,
|
|
166
|
+
price: offer.price,
|
|
167
|
+
priceCurrency: offer.priceCurrency ?? "BRL",
|
|
168
|
+
availability: offer.availability ?? "https://schema.org/InStock",
|
|
169
|
+
}
|
|
170
|
+
: undefined,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const data = {
|
|
176
|
+
"@context": "https://schema.org",
|
|
177
|
+
"@type": "ItemList",
|
|
178
|
+
url: url ?? page.seo?.canonical,
|
|
179
|
+
name: page.seo?.title,
|
|
180
|
+
description: page.seo?.description,
|
|
181
|
+
numberOfItems: page.products?.length ?? 0,
|
|
182
|
+
itemListElement: items,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return <JsonLdScript data={data} />;
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
// -------------------------------------------------------------------------
|
|
@@ -199,28 +190,28 @@ export function PLPJsonLd({ page, url }: PLPJsonLdProps) {
|
|
|
199
190
|
// -------------------------------------------------------------------------
|
|
200
191
|
|
|
201
192
|
export interface BreadcrumbJsonLdProps {
|
|
202
|
-
|
|
193
|
+
breadcrumb: BreadcrumbList;
|
|
203
194
|
}
|
|
204
195
|
|
|
205
196
|
export function BreadcrumbJsonLd({ breadcrumb }: BreadcrumbJsonLdProps) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
197
|
+
const items = (breadcrumb.itemListElement ?? []).map((item, index) => {
|
|
198
|
+
const listItem = item as ListItem;
|
|
199
|
+
return {
|
|
200
|
+
"@type": "ListItem" as const,
|
|
201
|
+
position: listItem.position ?? index + 1,
|
|
202
|
+
name: listItem.name,
|
|
203
|
+
item: listItem.item ?? listItem.url,
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const data = {
|
|
208
|
+
"@context": "https://schema.org",
|
|
209
|
+
"@type": "BreadcrumbList",
|
|
210
|
+
itemListElement: items,
|
|
211
|
+
numberOfItems: items.length,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return <JsonLdScript data={data} />;
|
|
224
215
|
}
|
|
225
216
|
|
|
226
217
|
// -------------------------------------------------------------------------
|
|
@@ -228,13 +219,13 @@ export function BreadcrumbJsonLd({ breadcrumb }: BreadcrumbJsonLdProps) {
|
|
|
228
219
|
// -------------------------------------------------------------------------
|
|
229
220
|
|
|
230
221
|
export interface SeoMetaProps {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
222
|
+
title?: string;
|
|
223
|
+
description?: string;
|
|
224
|
+
canonical?: string;
|
|
225
|
+
image?: string;
|
|
226
|
+
noIndex?: boolean;
|
|
227
|
+
type?: "website" | "article" | "product";
|
|
228
|
+
siteName?: string;
|
|
238
229
|
}
|
|
239
230
|
|
|
240
231
|
/**
|
|
@@ -245,41 +236,41 @@ export interface SeoMetaProps {
|
|
|
245
236
|
* by React's built-in behavior with TanStack Start).
|
|
246
237
|
*/
|
|
247
238
|
export function seoMetaTags(props: SeoMetaProps): Array<Record<string, string>> {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
239
|
+
const tags: Array<Record<string, string>> = [];
|
|
240
|
+
|
|
241
|
+
if (props.title) {
|
|
242
|
+
tags.push({ title: props.title });
|
|
243
|
+
tags.push({ property: "og:title", content: props.title });
|
|
244
|
+
tags.push({ name: "twitter:title", content: props.title });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (props.description) {
|
|
248
|
+
tags.push({ name: "description", content: props.description });
|
|
249
|
+
tags.push({ property: "og:description", content: props.description });
|
|
250
|
+
tags.push({ name: "twitter:description", content: props.description });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (props.canonical) {
|
|
254
|
+
tags.push({ property: "og:url", content: props.canonical });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (props.image) {
|
|
258
|
+
tags.push({ property: "og:image", content: props.image });
|
|
259
|
+
tags.push({ name: "twitter:image", content: props.image });
|
|
260
|
+
tags.push({ name: "twitter:card", content: "summary_large_image" });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (props.type) {
|
|
264
|
+
tags.push({ property: "og:type", content: props.type });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (props.siteName) {
|
|
268
|
+
tags.push({ property: "og:site_name", content: props.siteName });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (props.noIndex) {
|
|
272
|
+
tags.push({ name: "robots", content: "noindex, nofollow" });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return tags;
|
|
285
276
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
type ComponentPropsWithoutRef,
|
|
3
|
+
createContext,
|
|
4
|
+
forwardRef,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
8
|
} from "react";
|
|
9
|
-
import { getOptimizedMediaUrl, getSrcSet
|
|
9
|
+
import { type FitOptions, getOptimizedMediaUrl, getSrcSet } from "./Image";
|
|
10
10
|
|
|
11
11
|
// -------------------------------------------------------------------------
|
|
12
12
|
// Preload context — flows from <Picture preload> to child <Source> elements
|
|
@@ -15,7 +15,7 @@ import { getOptimizedMediaUrl, getSrcSet, type FitOptions } from "./Image";
|
|
|
15
15
|
// -------------------------------------------------------------------------
|
|
16
16
|
|
|
17
17
|
interface PreloadContextValue {
|
|
18
|
-
|
|
18
|
+
preload: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const PreloadContext = createContext<PreloadContextValue>({ preload: false });
|
|
@@ -25,59 +25,48 @@ const PreloadContext = createContext<PreloadContextValue>({ preload: false });
|
|
|
25
25
|
// preload link injection when inside a <Picture preload>.
|
|
26
26
|
// -------------------------------------------------------------------------
|
|
27
27
|
|
|
28
|
-
export type SourceProps = Omit<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
fetchPriority?: "high" | "low" | "auto";
|
|
39
|
-
/** @description Object-fit */
|
|
40
|
-
fit?: FitOptions;
|
|
28
|
+
export type SourceProps = Omit<ComponentPropsWithoutRef<"source">, "width" | "height"> & {
|
|
29
|
+
src: string;
|
|
30
|
+
/** @description Improves Web Vitals (CLS|LCP) */
|
|
31
|
+
width: number;
|
|
32
|
+
/** @description Improves Web Vitals (CLS|LCP) */
|
|
33
|
+
height?: number;
|
|
34
|
+
/** @description Improves Web Vitals (LCP). Use high for LCP image. */
|
|
35
|
+
fetchPriority?: "high" | "low" | "auto";
|
|
36
|
+
/** @description Object-fit */
|
|
37
|
+
fit?: FitOptions;
|
|
41
38
|
};
|
|
42
39
|
|
|
43
|
-
export const Source = forwardRef<HTMLSourceElement, SourceProps>(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const { preload } = useContext(PreloadContext);
|
|
40
|
+
export const Source = forwardRef<HTMLSourceElement, SourceProps>(function Source(
|
|
41
|
+
{ src, width, height, fetchPriority, fit = "cover", ...rest },
|
|
42
|
+
ref,
|
|
43
|
+
) {
|
|
44
|
+
const { preload } = useContext(PreloadContext);
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
const optimizedSrc = getOptimizedMediaUrl({
|
|
47
|
+
originalSrc: src,
|
|
48
|
+
width,
|
|
49
|
+
height,
|
|
50
|
+
fit,
|
|
51
|
+
});
|
|
52
|
+
const srcSet = rest.srcSet ?? getSrcSet(src, width, height, fit);
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
height={height}
|
|
75
|
-
ref={ref}
|
|
76
|
-
/>
|
|
77
|
-
</>
|
|
78
|
-
);
|
|
79
|
-
},
|
|
80
|
-
);
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
{preload && (
|
|
57
|
+
<link
|
|
58
|
+
as="image"
|
|
59
|
+
rel="preload"
|
|
60
|
+
href={optimizedSrc}
|
|
61
|
+
imageSrcSet={srcSet}
|
|
62
|
+
fetchPriority={fetchPriority ?? "high"}
|
|
63
|
+
media={rest.media}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
<source {...rest} srcSet={srcSet ?? optimizedSrc} width={width} height={height} ref={ref} />
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
});
|
|
81
70
|
|
|
82
71
|
// -------------------------------------------------------------------------
|
|
83
72
|
// Picture — composable wrapper that provides preload context to children.
|
|
@@ -91,24 +80,25 @@ export const Source = forwardRef<HTMLSourceElement, SourceProps>(
|
|
|
91
80
|
// -------------------------------------------------------------------------
|
|
92
81
|
|
|
93
82
|
export type PictureProps = ComponentPropsWithoutRef<"picture"> & {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
83
|
+
children: ReactNode;
|
|
84
|
+
/**
|
|
85
|
+
* @description When true, child <Source> and <Image> elements inject
|
|
86
|
+
* `<link rel="preload">` tags for their respective media queries.
|
|
87
|
+
*/
|
|
88
|
+
preload?: boolean;
|
|
100
89
|
};
|
|
101
90
|
|
|
102
|
-
export const Picture = forwardRef<HTMLPictureElement, PictureProps>(
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
export const Picture = forwardRef<HTMLPictureElement, PictureProps>(function Picture(
|
|
92
|
+
{ children, preload = false, ...props },
|
|
93
|
+
ref,
|
|
94
|
+
) {
|
|
95
|
+
const value = useMemo(() => ({ preload }), [preload]);
|
|
105
96
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
);
|
|
97
|
+
return (
|
|
98
|
+
<PreloadContext.Provider value={value}>
|
|
99
|
+
<picture {...props} ref={ref}>
|
|
100
|
+
{children}
|
|
101
|
+
</picture>
|
|
102
|
+
</PreloadContext.Provider>
|
|
103
|
+
);
|
|
104
|
+
});
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import type { AnalyticsEvent } from "../types/commerce";
|
|
2
2
|
|
|
3
3
|
declare global {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
interface Window {
|
|
5
|
+
DECO: { events: { dispatch: (event: any) => void } };
|
|
6
|
+
DECO_SITES_STD: {
|
|
7
|
+
sendAnalyticsEvent: (args: AnalyticsEvent) => void;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export const sendEvent = <E extends AnalyticsEvent>(event: E) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
if (typeof globalThis.window?.DECO?.events?.dispatch === "function") {
|
|
14
|
+
globalThis.window.DECO.events.dispatch(event);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
if (typeof globalThis.window?.DECO_SITES_STD?.sendAnalyticsEvent === "function") {
|
|
19
|
+
globalThis.window.DECO_SITES_STD.sendAnalyticsEvent(event);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
console.info("[analytics] No event dispatcher found. Event not sent:", event.name);
|
|
24
24
|
};
|