@01.software/sdk 0.27.0 → 0.29.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 +125 -1
- package/dist/analytics/react.cjs.map +1 -1
- package/dist/analytics/react.js.map +1 -1
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/{const-C0GlmeJ_.d.cts → const-DAjQYNuM.d.ts} +4 -4
- package/dist/{const-D-xucnw4.d.ts → const-Dsixdi6z.d.cts} +4 -4
- package/dist/index.cjs +626 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -8
- package/dist/index.d.ts +19 -8
- package/dist/index.js +626 -13
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-BPvUmPAq.d.cts → payload-types-Ci-ZA7aM.d.cts} +153 -73
- package/dist/{payload-types-BPvUmPAq.d.ts → payload-types-Ci-ZA7aM.d.ts} +153 -73
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js.map +1 -1
- package/dist/{server-n3xK4Nks.d.cts → server-C0C8dtms.d.cts} +331 -12
- package/dist/{server-_zvihptw.d.ts → server-Cv0Q4dPQ.d.ts} +331 -12
- package/dist/server.cjs +68 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +68 -4
- package/dist/server.js.map +1 -1
- package/dist/{types-BLdthWiW.d.ts → types-BWq_WlbB.d.ts} +1 -1
- package/dist/{types-DzWNu9pw.d.cts → types-zKjATmDK.d.cts} +1 -1
- package/dist/ui/canvas/server.cjs.map +1 -1
- package/dist/ui/canvas/server.js.map +1 -1
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.d.cts +3 -3
- package/dist/webhook.d.ts +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -47,6 +47,7 @@ __export(src_exports, {
|
|
|
47
47
|
OrderApi: () => OrderApi,
|
|
48
48
|
PermissionError: () => PermissionError,
|
|
49
49
|
ProductApi: () => ProductApi,
|
|
50
|
+
ProductSelectionCodecError: () => ProductSelectionCodecError,
|
|
50
51
|
QueryHooks: () => QueryHooks,
|
|
51
52
|
RateLimitError: () => RateLimitError,
|
|
52
53
|
ReadOnlyCollectionClient: () => ReadOnlyCollectionClient,
|
|
@@ -66,6 +67,7 @@ __export(src_exports, {
|
|
|
66
67
|
buildProductListingGroupsByOption: () => buildProductListingGroupsByOption,
|
|
67
68
|
buildProductListingProjection: () => buildProductListingProjection,
|
|
68
69
|
buildProductOptionMatrix: () => buildProductOptionMatrix,
|
|
70
|
+
buildProductOptionMatrixFromDetail: () => buildProductOptionMatrixFromDetail,
|
|
69
71
|
collectionKeys: () => collectionKeys,
|
|
70
72
|
createAnalytics: () => createAnalytics,
|
|
71
73
|
createAuthError: () => createAuthError,
|
|
@@ -74,6 +76,7 @@ __export(src_exports, {
|
|
|
74
76
|
createCustomerAuthWebhookHandler: () => createCustomerAuthWebhookHandler,
|
|
75
77
|
createNotFoundError: () => createNotFoundError,
|
|
76
78
|
createPermissionError: () => createPermissionError,
|
|
79
|
+
createProductSelectionCodec: () => createProductSelectionCodec,
|
|
77
80
|
createRateLimitError: () => createRateLimitError,
|
|
78
81
|
createServerClient: () => createServerClient,
|
|
79
82
|
createTypedWebhookHandler: () => createTypedWebhookHandler,
|
|
@@ -110,10 +113,14 @@ __export(src_exports, {
|
|
|
110
113
|
isUsageLimitError: () => isUsageLimitError,
|
|
111
114
|
isValidWebhookEvent: () => isValidWebhookEvent,
|
|
112
115
|
isValidationError: () => isValidationError,
|
|
116
|
+
normalizeProductSelection: () => normalizeProductSelection,
|
|
113
117
|
normalizeSelectedValueIds: () => normalizeSelectedValueIds,
|
|
118
|
+
parseProductSelection: () => parseProductSelection,
|
|
114
119
|
productKeys: () => productKeys,
|
|
120
|
+
resolveProductSelection: () => resolveProductSelection,
|
|
115
121
|
resolveRelation: () => resolveRelation,
|
|
116
|
-
resolveVariantForSelection: () => resolveVariantForSelection
|
|
122
|
+
resolveVariantForSelection: () => resolveVariantForSelection,
|
|
123
|
+
stringifyProductSelection: () => stringifyProductSelection
|
|
117
124
|
});
|
|
118
125
|
module.exports = __toCommonJS(src_exports);
|
|
119
126
|
|
|
@@ -1260,6 +1267,7 @@ var INTERNAL_COLLECTIONS = [
|
|
|
1260
1267
|
"analytics-event-schemas",
|
|
1261
1268
|
"subscriptions",
|
|
1262
1269
|
"billing-history",
|
|
1270
|
+
"inventory-reservations",
|
|
1263
1271
|
"order-status-logs",
|
|
1264
1272
|
"api-keys",
|
|
1265
1273
|
"personal-access-tokens",
|
|
@@ -1300,8 +1308,8 @@ var COLLECTIONS = [
|
|
|
1300
1308
|
"carts",
|
|
1301
1309
|
"cart-items",
|
|
1302
1310
|
"discounts",
|
|
1303
|
-
"promotions",
|
|
1304
1311
|
"shipping-policies",
|
|
1312
|
+
"shipping-zones",
|
|
1305
1313
|
"documents",
|
|
1306
1314
|
"document-categories",
|
|
1307
1315
|
"document-types",
|
|
@@ -1970,7 +1978,15 @@ var CommerceClient = class {
|
|
|
1970
1978
|
};
|
|
1971
1979
|
this.product = {
|
|
1972
1980
|
stockCheck: (params) => execute("/api/products/stock-check", params),
|
|
1973
|
-
listingGroups: (params) => execute("/api/products/listing-groups", params)
|
|
1981
|
+
listingGroups: (params) => execute("/api/products/listing-groups", params),
|
|
1982
|
+
detail: async (params) => {
|
|
1983
|
+
try {
|
|
1984
|
+
return await execute("/api/products/detail", params);
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
if (err instanceof NotFoundError) return null;
|
|
1987
|
+
throw err;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1974
1990
|
};
|
|
1975
1991
|
this.cart = {
|
|
1976
1992
|
get: cartApi.getCart.bind(cartApi),
|
|
@@ -2013,6 +2029,29 @@ var ProductApi = class extends BaseApi {
|
|
|
2013
2029
|
params
|
|
2014
2030
|
);
|
|
2015
2031
|
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Fetch full product detail by slug or id.
|
|
2034
|
+
* Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
|
|
2035
|
+
* `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
|
|
2036
|
+
* inspect `client.lastRequestId` against backend logs.
|
|
2037
|
+
*/
|
|
2038
|
+
async detail(params) {
|
|
2039
|
+
try {
|
|
2040
|
+
return await this.request("/api/products/detail", params);
|
|
2041
|
+
} catch (err) {
|
|
2042
|
+
if (err instanceof NotFoundError) return null;
|
|
2043
|
+
throw err;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Atomically create or update a product together with its options,
|
|
2048
|
+
* option-values, and variants in a single transaction. Mirrors Shopify's
|
|
2049
|
+
* `productSet` shape and is the canonical write path for the MCP
|
|
2050
|
+
* `product-upsert` tool.
|
|
2051
|
+
*/
|
|
2052
|
+
upsert(params) {
|
|
2053
|
+
return this.request("/api/products/upsert", params);
|
|
2054
|
+
}
|
|
2016
2055
|
};
|
|
2017
2056
|
|
|
2018
2057
|
// src/core/api/discount-api.ts
|
|
@@ -2105,7 +2144,9 @@ var ServerCommerceClient = class {
|
|
|
2105
2144
|
const orderApi = new OrderApi(serverOptions);
|
|
2106
2145
|
this.product = {
|
|
2107
2146
|
stockCheck: productApi.stockCheck.bind(productApi),
|
|
2108
|
-
listingGroups: productApi.listingGroups.bind(productApi)
|
|
2147
|
+
listingGroups: productApi.listingGroups.bind(productApi),
|
|
2148
|
+
detail: productApi.detail.bind(productApi),
|
|
2149
|
+
upsert: productApi.upsert.bind(productApi)
|
|
2109
2150
|
};
|
|
2110
2151
|
this.cart = {
|
|
2111
2152
|
get: cartApi.getCart.bind(cartApi),
|
|
@@ -2192,10 +2233,24 @@ var customerKeys = {
|
|
|
2192
2233
|
};
|
|
2193
2234
|
var productKeys = {
|
|
2194
2235
|
listingGroups: (options) => ["products", "listing-groups", "list", options],
|
|
2195
|
-
listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options]
|
|
2236
|
+
listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
|
|
2237
|
+
detail: (params) => ["products", "detail", params],
|
|
2238
|
+
detailAll: () => ["products", "detail"]
|
|
2196
2239
|
};
|
|
2197
2240
|
|
|
2198
2241
|
// src/core/query/collection-hooks.ts
|
|
2242
|
+
var PRODUCT_DETAIL_INVALIDATING_COLLECTIONS = /* @__PURE__ */ new Set([
|
|
2243
|
+
"products",
|
|
2244
|
+
"product-variants",
|
|
2245
|
+
"product-options",
|
|
2246
|
+
"product-option-values",
|
|
2247
|
+
"product-categories",
|
|
2248
|
+
"product-tags",
|
|
2249
|
+
"product-collections",
|
|
2250
|
+
"brands",
|
|
2251
|
+
"brand-logos",
|
|
2252
|
+
"images"
|
|
2253
|
+
]);
|
|
2199
2254
|
var DEFAULT_PAGE_SIZE = 20;
|
|
2200
2255
|
var CollectionHooks = class {
|
|
2201
2256
|
constructor(queryClient, collectionClient) {
|
|
@@ -2353,6 +2408,9 @@ var CollectionHooks = class {
|
|
|
2353
2408
|
this.queryClient.invalidateQueries({
|
|
2354
2409
|
queryKey: collectionKeys(collection).all
|
|
2355
2410
|
});
|
|
2411
|
+
if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
|
|
2412
|
+
this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
|
|
2413
|
+
}
|
|
2356
2414
|
options?.onSuccess?.(data);
|
|
2357
2415
|
},
|
|
2358
2416
|
onError: options?.onError,
|
|
@@ -2373,6 +2431,9 @@ var CollectionHooks = class {
|
|
|
2373
2431
|
this.queryClient.invalidateQueries({
|
|
2374
2432
|
queryKey: collectionKeys(collection).all
|
|
2375
2433
|
});
|
|
2434
|
+
if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
|
|
2435
|
+
this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
|
|
2436
|
+
}
|
|
2376
2437
|
options?.onSuccess?.(data);
|
|
2377
2438
|
},
|
|
2378
2439
|
onError: options?.onError,
|
|
@@ -2389,6 +2450,9 @@ var CollectionHooks = class {
|
|
|
2389
2450
|
this.queryClient.invalidateQueries({
|
|
2390
2451
|
queryKey: collectionKeys(collection).all
|
|
2391
2452
|
});
|
|
2453
|
+
if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
|
|
2454
|
+
this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
|
|
2455
|
+
}
|
|
2392
2456
|
options?.onSuccess?.(data);
|
|
2393
2457
|
},
|
|
2394
2458
|
onError: options?.onError,
|
|
@@ -2541,7 +2605,7 @@ var CustomerHooks = class {
|
|
|
2541
2605
|
|
|
2542
2606
|
// src/core/query/query-hooks.ts
|
|
2543
2607
|
var QueryHooks = class extends CollectionHooks {
|
|
2544
|
-
constructor(queryClient, collectionClient, customerAuth) {
|
|
2608
|
+
constructor(queryClient, collectionClient, customerAuth, commerceClient) {
|
|
2545
2609
|
super(queryClient, collectionClient);
|
|
2546
2610
|
// --- Customer hooks delegation ---
|
|
2547
2611
|
this.useCustomerMe = (...args) => this._customer.useCustomerMe(...args);
|
|
@@ -2558,6 +2622,7 @@ var QueryHooks = class extends CollectionHooks {
|
|
|
2558
2622
|
this.getCustomerData = () => this._customer.getCustomerData();
|
|
2559
2623
|
this.setCustomerData = (data) => this._customer.setCustomerData(data);
|
|
2560
2624
|
this._customer = new CustomerHooks(queryClient, customerAuth);
|
|
2625
|
+
this._commerce = commerceClient;
|
|
2561
2626
|
}
|
|
2562
2627
|
useProductListingGroupsQuery(params, options) {
|
|
2563
2628
|
const queryOptions = params.options;
|
|
@@ -2651,6 +2716,21 @@ var QueryHooks = class extends CollectionHooks {
|
|
|
2651
2716
|
staleTime: options?.staleTime
|
|
2652
2717
|
});
|
|
2653
2718
|
}
|
|
2719
|
+
useProductDetail(params, options) {
|
|
2720
|
+
const discriminator = "slug" in params ? params.slug : params.id;
|
|
2721
|
+
const enabled = options?.enabled !== false && Boolean(discriminator);
|
|
2722
|
+
return (0, import_react_query4.useQuery)({
|
|
2723
|
+
queryKey: productKeys.detail(params),
|
|
2724
|
+
queryFn: () => this._commerce.product.detail(params),
|
|
2725
|
+
enabled
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
useProductDetailBySlug(slug, options) {
|
|
2729
|
+
return this.useProductDetail({ slug }, options);
|
|
2730
|
+
}
|
|
2731
|
+
useProductDetailById(id, options) {
|
|
2732
|
+
return this.useProductDetail({ id }, options);
|
|
2733
|
+
}
|
|
2654
2734
|
};
|
|
2655
2735
|
|
|
2656
2736
|
// src/core/client/client.ts
|
|
@@ -2713,7 +2793,8 @@ var Client = class {
|
|
|
2713
2793
|
this.query = new QueryHooks(
|
|
2714
2794
|
this.queryClient,
|
|
2715
2795
|
collectionClient,
|
|
2716
|
-
this.customer.auth
|
|
2796
|
+
this.customer.auth,
|
|
2797
|
+
this.commerce
|
|
2717
2798
|
);
|
|
2718
2799
|
}
|
|
2719
2800
|
getState() {
|
|
@@ -2775,7 +2856,7 @@ var ServerClient = class {
|
|
|
2775
2856
|
onRequestId
|
|
2776
2857
|
);
|
|
2777
2858
|
this.queryClient = getQueryClient();
|
|
2778
|
-
this.query = new QueryHooks(this.queryClient, this.collections);
|
|
2859
|
+
this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
|
|
2779
2860
|
}
|
|
2780
2861
|
getState() {
|
|
2781
2862
|
return { ...this.state };
|
|
@@ -3052,6 +3133,13 @@ function createTypedWebhookHandler(collection, handler) {
|
|
|
3052
3133
|
}
|
|
3053
3134
|
|
|
3054
3135
|
// src/utils/ecommerce.ts
|
|
3136
|
+
var ProductSelectionCodecError = class extends Error {
|
|
3137
|
+
constructor(message) {
|
|
3138
|
+
super(message);
|
|
3139
|
+
this.code = "ambiguous_product_selection_query";
|
|
3140
|
+
this.name = "ProductSelectionCodecError";
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3055
3143
|
function getRelationID(value) {
|
|
3056
3144
|
if (typeof value === "string") return value;
|
|
3057
3145
|
if (typeof value === "number") return String(value);
|
|
@@ -3098,10 +3186,11 @@ function getFirstAvailableVariantPrimaryImage(variants) {
|
|
|
3098
3186
|
}
|
|
3099
3187
|
return null;
|
|
3100
3188
|
}
|
|
3101
|
-
function normalizeOptionValue(value, fallbackOptionId) {
|
|
3189
|
+
function normalizeOptionValue(value, fallbackOptionId, fallbackOptionSlug) {
|
|
3102
3190
|
return {
|
|
3103
3191
|
id: String(value.id),
|
|
3104
3192
|
optionId: getRelationID(value.option) ?? fallbackOptionId,
|
|
3193
|
+
optionSlug: fallbackOptionSlug,
|
|
3105
3194
|
label: value.value || value.slug || String(value.id),
|
|
3106
3195
|
slug: value.slug ?? null,
|
|
3107
3196
|
swatchColor: value.swatchColor ?? null,
|
|
@@ -3110,20 +3199,26 @@ function normalizeOptionValue(value, fallbackOptionId) {
|
|
|
3110
3199
|
order: value._order ?? value["_product-option-values_values_order"] ?? ""
|
|
3111
3200
|
};
|
|
3112
3201
|
}
|
|
3113
|
-
function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
|
|
3202
|
+
function normalizeVariantOptionValues(variant, optionById, valueToOptionId, optionIds) {
|
|
3114
3203
|
const optionValueByOptionId = /* @__PURE__ */ new Map();
|
|
3204
|
+
const optionValueByOptionSlug = /* @__PURE__ */ new Map();
|
|
3115
3205
|
for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
|
|
3116
3206
|
const valueId = getRelationID(rawValue);
|
|
3117
3207
|
if (!valueId) continue;
|
|
3118
3208
|
const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
|
|
3119
3209
|
if (!optionId || optionValueByOptionId.has(optionId)) continue;
|
|
3120
3210
|
optionValueByOptionId.set(optionId, valueId);
|
|
3211
|
+
const optionSlug = optionById.get(optionId)?.slug;
|
|
3212
|
+
if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
|
|
3213
|
+
optionValueByOptionSlug.set(optionSlug, valueId);
|
|
3214
|
+
}
|
|
3121
3215
|
}
|
|
3122
3216
|
const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
|
|
3123
3217
|
return {
|
|
3124
3218
|
id: String(variant.id),
|
|
3125
3219
|
optionValueIds,
|
|
3126
3220
|
optionValueByOptionId,
|
|
3221
|
+
optionValueByOptionSlug,
|
|
3127
3222
|
source: variant
|
|
3128
3223
|
};
|
|
3129
3224
|
}
|
|
@@ -3133,17 +3228,20 @@ function buildProductOptionMatrix({
|
|
|
3133
3228
|
}) {
|
|
3134
3229
|
const normalizedOptions = options.map((option) => {
|
|
3135
3230
|
const valuesById = /* @__PURE__ */ new Map();
|
|
3231
|
+
const optionSlug = option.slug ?? String(option.id);
|
|
3136
3232
|
for (const rawValue of option.values?.docs ?? []) {
|
|
3137
3233
|
if (!isProductOptionValueDoc(rawValue)) continue;
|
|
3138
3234
|
const normalizedValue = normalizeOptionValue(
|
|
3139
3235
|
rawValue,
|
|
3140
|
-
String(option.id)
|
|
3236
|
+
String(option.id),
|
|
3237
|
+
optionSlug
|
|
3141
3238
|
);
|
|
3142
3239
|
valuesById.set(normalizedValue.id, normalizedValue);
|
|
3143
3240
|
}
|
|
3144
3241
|
return {
|
|
3145
3242
|
id: String(option.id),
|
|
3146
3243
|
title: option.title ?? String(option.id),
|
|
3244
|
+
slug: optionSlug,
|
|
3147
3245
|
order: option._order ?? option["_product-options_options_order"] ?? "",
|
|
3148
3246
|
values: Array.from(valuesById.values()).sort(
|
|
3149
3247
|
(left, right) => compareOrder(left.order, right.order)
|
|
@@ -3153,24 +3251,113 @@ function buildProductOptionMatrix({
|
|
|
3153
3251
|
const optionById = new Map(
|
|
3154
3252
|
normalizedOptions.map((option) => [option.id, option])
|
|
3155
3253
|
);
|
|
3254
|
+
const optionBySlug = new Map(
|
|
3255
|
+
normalizedOptions.map((option) => [option.slug, option])
|
|
3256
|
+
);
|
|
3156
3257
|
const valueById = /* @__PURE__ */ new Map();
|
|
3157
3258
|
const valueToOptionId = /* @__PURE__ */ new Map();
|
|
3259
|
+
const valueToOptionSlug = /* @__PURE__ */ new Map();
|
|
3158
3260
|
for (const option of normalizedOptions) {
|
|
3159
3261
|
for (const value of option.values) {
|
|
3160
3262
|
valueById.set(value.id, value);
|
|
3161
3263
|
valueToOptionId.set(value.id, option.id);
|
|
3264
|
+
valueToOptionSlug.set(value.id, option.slug);
|
|
3162
3265
|
}
|
|
3163
3266
|
}
|
|
3164
3267
|
const optionIds = normalizedOptions.map((option) => option.id);
|
|
3268
|
+
const optionSlugs = normalizedOptions.map((option) => option.slug);
|
|
3165
3269
|
const normalizedVariants = variants.map(
|
|
3166
|
-
(variant) => normalizeVariantOptionValues(
|
|
3270
|
+
(variant) => normalizeVariantOptionValues(
|
|
3271
|
+
variant,
|
|
3272
|
+
optionById,
|
|
3273
|
+
valueToOptionId,
|
|
3274
|
+
optionIds
|
|
3275
|
+
)
|
|
3276
|
+
);
|
|
3277
|
+
return {
|
|
3278
|
+
options: normalizedOptions,
|
|
3279
|
+
optionIds,
|
|
3280
|
+
optionSlugs,
|
|
3281
|
+
optionById,
|
|
3282
|
+
optionBySlug,
|
|
3283
|
+
valueById,
|
|
3284
|
+
valueToOptionId,
|
|
3285
|
+
valueToOptionSlug,
|
|
3286
|
+
variants: normalizedVariants
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
function matrixOrder(index) {
|
|
3290
|
+
return String(index).padStart(6, "0");
|
|
3291
|
+
}
|
|
3292
|
+
function buildProductOptionMatrixFromDetail(detail) {
|
|
3293
|
+
const normalizedOptions = detail.options.map((option, optionIndex) => ({
|
|
3294
|
+
id: String(option.id),
|
|
3295
|
+
title: option.title || String(option.id),
|
|
3296
|
+
slug: option.slug,
|
|
3297
|
+
order: matrixOrder(optionIndex),
|
|
3298
|
+
values: option.values.map((value, valueIndex) => ({
|
|
3299
|
+
id: String(value.id),
|
|
3300
|
+
optionId: String(option.id),
|
|
3301
|
+
optionSlug: option.slug,
|
|
3302
|
+
label: value.value || value.slug || String(value.id),
|
|
3303
|
+
slug: value.slug,
|
|
3304
|
+
swatchColor: value.swatchColor ?? null,
|
|
3305
|
+
thumbnail: value.thumbnail ?? null,
|
|
3306
|
+
images: value.images ?? null,
|
|
3307
|
+
order: matrixOrder(valueIndex)
|
|
3308
|
+
}))
|
|
3309
|
+
}));
|
|
3310
|
+
const optionById = new Map(
|
|
3311
|
+
normalizedOptions.map((option) => [option.id, option])
|
|
3312
|
+
);
|
|
3313
|
+
const optionBySlug = new Map(
|
|
3314
|
+
normalizedOptions.map((option) => [option.slug, option])
|
|
3167
3315
|
);
|
|
3316
|
+
const valueById = /* @__PURE__ */ new Map();
|
|
3317
|
+
const valueToOptionId = /* @__PURE__ */ new Map();
|
|
3318
|
+
const valueToOptionSlug = /* @__PURE__ */ new Map();
|
|
3319
|
+
for (const option of normalizedOptions) {
|
|
3320
|
+
for (const value of option.values) {
|
|
3321
|
+
valueById.set(value.id, value);
|
|
3322
|
+
valueToOptionId.set(value.id, option.id);
|
|
3323
|
+
valueToOptionSlug.set(value.id, option.slug);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
const optionIds = normalizedOptions.map((option) => option.id);
|
|
3327
|
+
const optionSlugs = normalizedOptions.map((option) => option.slug);
|
|
3328
|
+
const normalizedVariants = detail.variants.map((variant) => {
|
|
3329
|
+
const optionValueByOptionId = /* @__PURE__ */ new Map();
|
|
3330
|
+
const optionValueByOptionSlug = /* @__PURE__ */ new Map();
|
|
3331
|
+
for (const rawValue of variant.optionValues) {
|
|
3332
|
+
const optionId = String(rawValue.optionId);
|
|
3333
|
+
const valueId = String(rawValue.valueId);
|
|
3334
|
+
const optionSlug = rawValue.optionSlug;
|
|
3335
|
+
if (!optionById.has(optionId)) continue;
|
|
3336
|
+
if (valueToOptionId.get(valueId) !== optionId) continue;
|
|
3337
|
+
if (optionValueByOptionId.has(optionId)) continue;
|
|
3338
|
+
optionValueByOptionId.set(optionId, valueId);
|
|
3339
|
+
if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
|
|
3340
|
+
optionValueByOptionSlug.set(optionSlug, valueId);
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
|
|
3344
|
+
return {
|
|
3345
|
+
id: String(variant.id),
|
|
3346
|
+
optionValueIds,
|
|
3347
|
+
optionValueByOptionId,
|
|
3348
|
+
optionValueByOptionSlug,
|
|
3349
|
+
source: variant
|
|
3350
|
+
};
|
|
3351
|
+
});
|
|
3168
3352
|
return {
|
|
3169
3353
|
options: normalizedOptions,
|
|
3170
3354
|
optionIds,
|
|
3355
|
+
optionSlugs,
|
|
3171
3356
|
optionById,
|
|
3357
|
+
optionBySlug,
|
|
3172
3358
|
valueById,
|
|
3173
3359
|
valueToOptionId,
|
|
3360
|
+
valueToOptionSlug,
|
|
3174
3361
|
variants: normalizedVariants
|
|
3175
3362
|
};
|
|
3176
3363
|
}
|
|
@@ -3203,7 +3390,7 @@ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
|
|
|
3203
3390
|
)
|
|
3204
3391
|
);
|
|
3205
3392
|
const availableValueIds = new Set(
|
|
3206
|
-
matchingVariants.map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
|
|
3393
|
+
matchingVariants.filter((variant) => variant.source.isActive !== false).map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
|
|
3207
3394
|
);
|
|
3208
3395
|
return option.values.filter((value) => availableValueIds.has(value.id));
|
|
3209
3396
|
}
|
|
@@ -3218,6 +3405,432 @@ function resolveVariantForSelection(matrix, selectedValueIds) {
|
|
|
3218
3405
|
)
|
|
3219
3406
|
);
|
|
3220
3407
|
}
|
|
3408
|
+
function getVariantSelection(matrix, variantId) {
|
|
3409
|
+
if (variantId == null) return void 0;
|
|
3410
|
+
const id = String(variantId);
|
|
3411
|
+
return matrix.variants.find((variant) => variant.id === id);
|
|
3412
|
+
}
|
|
3413
|
+
function hasExplicitSelection(selection) {
|
|
3414
|
+
return Boolean(
|
|
3415
|
+
selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
|
|
3416
|
+
);
|
|
3417
|
+
}
|
|
3418
|
+
function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
|
|
3419
|
+
if (valueId == null) return;
|
|
3420
|
+
const normalizedValueId = String(valueId);
|
|
3421
|
+
if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return;
|
|
3422
|
+
selectedByOptionId.set(optionId, normalizedValueId);
|
|
3423
|
+
}
|
|
3424
|
+
function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
|
|
3425
|
+
if (!valueSlug) return;
|
|
3426
|
+
const option = matrix.optionById.get(optionId);
|
|
3427
|
+
if (!option) return;
|
|
3428
|
+
const value = option.values.find((candidate) => candidate.slug === valueSlug);
|
|
3429
|
+
if (!value) return;
|
|
3430
|
+
selectedByOptionId.set(optionId, value.id);
|
|
3431
|
+
}
|
|
3432
|
+
function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
|
|
3433
|
+
if (!valueSlug) return;
|
|
3434
|
+
const option = matrix.optionBySlug.get(optionSlug);
|
|
3435
|
+
if (!option) return;
|
|
3436
|
+
const value = option.values.find((candidate) => candidate.slug === valueSlug);
|
|
3437
|
+
if (!value) return;
|
|
3438
|
+
selectedByOptionId.set(option.id, value.id);
|
|
3439
|
+
}
|
|
3440
|
+
function requireValueSlug(value) {
|
|
3441
|
+
if (value.slug) return value.slug;
|
|
3442
|
+
throw new ProductSelectionCodecError(
|
|
3443
|
+
`Option value "${value.id}" does not have a slug and cannot be used in product selection URLs.`
|
|
3444
|
+
);
|
|
3445
|
+
}
|
|
3446
|
+
function requireOptionSlug(option) {
|
|
3447
|
+
if (option.slug) return option.slug;
|
|
3448
|
+
throw new ProductSelectionCodecError(
|
|
3449
|
+
`Option "${option.id}" does not have a slug and cannot be used in product selection URLs.`
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
function toSearchParams(search) {
|
|
3453
|
+
if (!search) return new URLSearchParams();
|
|
3454
|
+
if (search instanceof URLSearchParams) return new URLSearchParams(search);
|
|
3455
|
+
if (search instanceof URL) return new URLSearchParams(search.searchParams);
|
|
3456
|
+
const trimmed = search.trim();
|
|
3457
|
+
if (!trimmed) return new URLSearchParams();
|
|
3458
|
+
try {
|
|
3459
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
|
|
3460
|
+
return new URL(trimmed).searchParams;
|
|
3461
|
+
}
|
|
3462
|
+
} catch {
|
|
3463
|
+
return new URLSearchParams();
|
|
3464
|
+
}
|
|
3465
|
+
return new URLSearchParams(
|
|
3466
|
+
trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3469
|
+
function slugLike(value) {
|
|
3470
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3471
|
+
}
|
|
3472
|
+
function assertNoAmbiguousSelectionParams(matrix, params) {
|
|
3473
|
+
const knownSelectionKeys = /* @__PURE__ */ new Set();
|
|
3474
|
+
for (const option of matrix.options) {
|
|
3475
|
+
knownSelectionKeys.add(slugLike(option.slug));
|
|
3476
|
+
knownSelectionKeys.add(slugLike(option.title));
|
|
3477
|
+
for (const value of option.values) {
|
|
3478
|
+
if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
for (const [key, value] of params.entries()) {
|
|
3482
|
+
if (key.startsWith("opt.")) {
|
|
3483
|
+
const optionToken = key.slice(4);
|
|
3484
|
+
if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
|
|
3485
|
+
throw new ProductSelectionCodecError(
|
|
3486
|
+
`Unknown product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
|
|
3487
|
+
);
|
|
3488
|
+
}
|
|
3489
|
+
if (!value) {
|
|
3490
|
+
throw new ProductSelectionCodecError(
|
|
3491
|
+
`Product selection query parameter "${key}" requires a value slug.`
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
continue;
|
|
3495
|
+
}
|
|
3496
|
+
const keyToken = slugLike(key);
|
|
3497
|
+
if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
|
|
3498
|
+
throw new ProductSelectionCodecError(
|
|
3499
|
+
`Ambiguous product selection query parameter "${key}". Use opt.<optionSlug>=<valueSlug>.`
|
|
3500
|
+
);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
function emitLegacyOptionIdParam(options, event) {
|
|
3505
|
+
try {
|
|
3506
|
+
options?.onLegacyOptionIdParam?.(event);
|
|
3507
|
+
} catch {
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
function assignSearchSelection(matrix, selectedByOptionId, search, options) {
|
|
3511
|
+
if (!search) return;
|
|
3512
|
+
const params = toSearchParams(search);
|
|
3513
|
+
assertNoAmbiguousSelectionParams(matrix, params);
|
|
3514
|
+
for (const [key, valueSlug] of params.entries()) {
|
|
3515
|
+
if (!key.startsWith("opt.")) continue;
|
|
3516
|
+
const optionToken = key.slice(4);
|
|
3517
|
+
const optionBySlug = matrix.optionBySlug.get(optionToken);
|
|
3518
|
+
if (optionBySlug) {
|
|
3519
|
+
assignSelectedValueSlugByOptionSlug(
|
|
3520
|
+
matrix,
|
|
3521
|
+
selectedByOptionId,
|
|
3522
|
+
optionBySlug.slug,
|
|
3523
|
+
valueSlug
|
|
3524
|
+
);
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
const legacyOption = matrix.optionById.get(optionToken);
|
|
3528
|
+
if (!legacyOption) continue;
|
|
3529
|
+
const before = selectedByOptionId.get(legacyOption.id);
|
|
3530
|
+
assignSelectedValueSlugByOptionId(
|
|
3531
|
+
matrix,
|
|
3532
|
+
selectedByOptionId,
|
|
3533
|
+
legacyOption.id,
|
|
3534
|
+
valueSlug
|
|
3535
|
+
);
|
|
3536
|
+
if (selectedByOptionId.get(legacyOption.id) !== before) {
|
|
3537
|
+
emitLegacyOptionIdParam(options, {
|
|
3538
|
+
optionId: legacyOption.id,
|
|
3539
|
+
optionSlug: legacyOption.slug,
|
|
3540
|
+
valueSlug,
|
|
3541
|
+
searchParam: key
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
function normalizeProductSelection(detail, selection = {}, options) {
|
|
3547
|
+
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
3548
|
+
const selectedByOptionId = /* @__PURE__ */ new Map();
|
|
3549
|
+
const variantSelection = getVariantSelection(matrix, selection.variantId);
|
|
3550
|
+
const variantId = variantSelection?.id ?? null;
|
|
3551
|
+
if (variantSelection) {
|
|
3552
|
+
for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
|
|
3553
|
+
selectedByOptionId.set(optionId, valueId);
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
for (const rawValueId of selection.valueIds ?? []) {
|
|
3557
|
+
const valueId = getRelationID(rawValueId);
|
|
3558
|
+
if (!valueId) continue;
|
|
3559
|
+
const optionId = matrix.valueToOptionId.get(valueId);
|
|
3560
|
+
if (!optionId) continue;
|
|
3561
|
+
selectedByOptionId.set(optionId, valueId);
|
|
3562
|
+
}
|
|
3563
|
+
assignSearchSelection(matrix, selectedByOptionId, selection.search, options);
|
|
3564
|
+
for (const [rawOptionId, rawSelection] of Object.entries(
|
|
3565
|
+
selection.byOptionId ?? {}
|
|
3566
|
+
)) {
|
|
3567
|
+
const optionId = String(rawOptionId);
|
|
3568
|
+
if (!matrix.optionById.has(optionId)) continue;
|
|
3569
|
+
if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
|
|
3570
|
+
assignSelectedValue(
|
|
3571
|
+
matrix,
|
|
3572
|
+
selectedByOptionId,
|
|
3573
|
+
optionId,
|
|
3574
|
+
rawSelection.valueId
|
|
3575
|
+
);
|
|
3576
|
+
continue;
|
|
3577
|
+
}
|
|
3578
|
+
if (typeof rawSelection === "string" || typeof rawSelection === "number") {
|
|
3579
|
+
assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3582
|
+
if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
|
|
3583
|
+
assignSelectedValueSlugByOptionId(
|
|
3584
|
+
matrix,
|
|
3585
|
+
selectedByOptionId,
|
|
3586
|
+
optionId,
|
|
3587
|
+
rawSelection.valueSlug
|
|
3588
|
+
);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
for (const [rawOptionSlug, rawSelection] of Object.entries(
|
|
3592
|
+
selection.byOptionSlug ?? {}
|
|
3593
|
+
)) {
|
|
3594
|
+
const optionSlug = String(rawOptionSlug);
|
|
3595
|
+
if (!matrix.optionBySlug.has(optionSlug)) continue;
|
|
3596
|
+
if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
|
|
3597
|
+
const option = matrix.optionBySlug.get(optionSlug);
|
|
3598
|
+
if (option) {
|
|
3599
|
+
assignSelectedValue(
|
|
3600
|
+
matrix,
|
|
3601
|
+
selectedByOptionId,
|
|
3602
|
+
option.id,
|
|
3603
|
+
rawSelection.valueId
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
continue;
|
|
3607
|
+
}
|
|
3608
|
+
if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
|
|
3609
|
+
assignSelectedValueSlugByOptionSlug(
|
|
3610
|
+
matrix,
|
|
3611
|
+
selectedByOptionId,
|
|
3612
|
+
optionSlug,
|
|
3613
|
+
rawSelection.valueSlug
|
|
3614
|
+
);
|
|
3615
|
+
continue;
|
|
3616
|
+
}
|
|
3617
|
+
if (typeof rawSelection === "string" || typeof rawSelection === "number") {
|
|
3618
|
+
assignSelectedValueSlugByOptionSlug(
|
|
3619
|
+
matrix,
|
|
3620
|
+
selectedByOptionId,
|
|
3621
|
+
optionSlug,
|
|
3622
|
+
String(rawSelection)
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
const byOptionId = Object.fromEntries(
|
|
3627
|
+
matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
|
|
3628
|
+
);
|
|
3629
|
+
const byOptionSlug = Object.fromEntries(
|
|
3630
|
+
matrix.options.map((option) => {
|
|
3631
|
+
const valueId = selectedByOptionId.get(option.id);
|
|
3632
|
+
const value = valueId ? matrix.valueById.get(valueId) : void 0;
|
|
3633
|
+
return [option.slug, value?.slug ?? void 0];
|
|
3634
|
+
}).filter((entry) => Boolean(entry[1]))
|
|
3635
|
+
);
|
|
3636
|
+
return {
|
|
3637
|
+
byOptionSlug,
|
|
3638
|
+
byOptionId,
|
|
3639
|
+
valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
|
|
3640
|
+
variantId
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
function parseProductSelection(detail, search, options) {
|
|
3644
|
+
return normalizeProductSelection(detail, { search }, options);
|
|
3645
|
+
}
|
|
3646
|
+
function stringifyProductSelection(detail, selection = {}, options) {
|
|
3647
|
+
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
3648
|
+
const normalized = normalizeProductSelection(detail, selection, options);
|
|
3649
|
+
const params = new URLSearchParams();
|
|
3650
|
+
for (const optionId of matrix.optionIds) {
|
|
3651
|
+
const valueId = normalized.byOptionId[optionId];
|
|
3652
|
+
if (!valueId) continue;
|
|
3653
|
+
const option = matrix.optionById.get(optionId);
|
|
3654
|
+
const value = matrix.valueById.get(valueId);
|
|
3655
|
+
if (!option || !value) continue;
|
|
3656
|
+
params.append(`opt.${requireOptionSlug(option)}`, requireValueSlug(value));
|
|
3657
|
+
}
|
|
3658
|
+
return params.toString();
|
|
3659
|
+
}
|
|
3660
|
+
function createProductSelectionCodec(detail, options) {
|
|
3661
|
+
return {
|
|
3662
|
+
parse: (search) => parseProductSelection(detail, search, options),
|
|
3663
|
+
stringify: (selection = {}) => stringifyProductSelection(detail, selection, options)
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
function selectedEntries(selection) {
|
|
3667
|
+
return Object.entries(selection.byOptionId);
|
|
3668
|
+
}
|
|
3669
|
+
function getMatchingVariantEntries(matrix, selection) {
|
|
3670
|
+
const entries = selectedEntries(selection);
|
|
3671
|
+
if (entries.length === 0) return matrix.variants;
|
|
3672
|
+
return matrix.variants.filter(
|
|
3673
|
+
(variant) => entries.every(
|
|
3674
|
+
([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
|
|
3675
|
+
)
|
|
3676
|
+
);
|
|
3677
|
+
}
|
|
3678
|
+
function activeVariantEntries(variants) {
|
|
3679
|
+
return variants.filter((variant) => variant.source.isActive !== false);
|
|
3680
|
+
}
|
|
3681
|
+
function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
|
|
3682
|
+
if (matrix.optionIds.length === 0) {
|
|
3683
|
+
return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
|
|
3684
|
+
}
|
|
3685
|
+
const allOptionsSelected = matrix.optionIds.every(
|
|
3686
|
+
(optionId) => Boolean(selection.byOptionId[optionId])
|
|
3687
|
+
);
|
|
3688
|
+
if (!allOptionsSelected) return null;
|
|
3689
|
+
return matchingVariants.find(
|
|
3690
|
+
(variant) => matrix.optionIds.every(
|
|
3691
|
+
(optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
|
|
3692
|
+
)
|
|
3693
|
+
) ?? null;
|
|
3694
|
+
}
|
|
3695
|
+
function buildSelectionPrice(variants) {
|
|
3696
|
+
const { min, max } = getMinMax(variants.map((variant) => variant.price));
|
|
3697
|
+
const { min: compareAtMin, max: compareAtMax } = getMinMax(
|
|
3698
|
+
variants.map((variant) => variant.compareAtPrice)
|
|
3699
|
+
);
|
|
3700
|
+
return {
|
|
3701
|
+
min,
|
|
3702
|
+
max,
|
|
3703
|
+
compareAtMin,
|
|
3704
|
+
compareAtMax,
|
|
3705
|
+
isRange: min !== null && max !== null ? min !== max : false
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
function firstMedia(value) {
|
|
3709
|
+
if (value == null) return null;
|
|
3710
|
+
if (Array.isArray(value)) return firstMedia(value[0]);
|
|
3711
|
+
return value;
|
|
3712
|
+
}
|
|
3713
|
+
function isPresentMedia(value) {
|
|
3714
|
+
return value != null;
|
|
3715
|
+
}
|
|
3716
|
+
function mediaArray(values) {
|
|
3717
|
+
if (!Array.isArray(values)) return [];
|
|
3718
|
+
return values.filter(isPresentMedia);
|
|
3719
|
+
}
|
|
3720
|
+
function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
|
|
3721
|
+
const selectedValueImages = selectedValues.flatMap(
|
|
3722
|
+
(value) => mediaArray(value.images)
|
|
3723
|
+
);
|
|
3724
|
+
const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
|
|
3725
|
+
const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
|
|
3726
|
+
const matchingVariantPrimary = matchingVariants.map(
|
|
3727
|
+
(variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
|
|
3728
|
+
).find((value) => value != null) ?? null;
|
|
3729
|
+
const detailImages = mediaArray(detail.images);
|
|
3730
|
+
const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? firstMedia(detail.listing.primaryImage) ?? matchingVariantPrimary ?? firstMedia(detailImages);
|
|
3731
|
+
const images = mediaArray(selectedVariant?.images).length > 0 ? mediaArray(selectedVariant?.images) : selectedValueImages.length > 0 ? selectedValueImages : detailImages;
|
|
3732
|
+
return {
|
|
3733
|
+
primaryImage,
|
|
3734
|
+
images
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
function buildSelectionStock(selectedVariant, matchingVariants) {
|
|
3738
|
+
if (selectedVariant) {
|
|
3739
|
+
const availableStock = selectedVariant.isUnlimited ? null : Math.max(0, selectedVariant.stock - selectedVariant.reservedStock);
|
|
3740
|
+
const isActive = selectedVariant.isActive !== false;
|
|
3741
|
+
return {
|
|
3742
|
+
availableForSale: isActive && (selectedVariant.isUnlimited || (availableStock ?? 0) > 0),
|
|
3743
|
+
isUnlimited: selectedVariant.isUnlimited,
|
|
3744
|
+
stock: selectedVariant.stock,
|
|
3745
|
+
reservedStock: selectedVariant.reservedStock,
|
|
3746
|
+
availableStock
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3749
|
+
return {
|
|
3750
|
+
availableForSale: matchingVariants.some(isVariantAvailableForSale),
|
|
3751
|
+
isUnlimited: matchingVariants.some((variant) => variant.isUnlimited),
|
|
3752
|
+
stock: null,
|
|
3753
|
+
reservedStock: null,
|
|
3754
|
+
availableStock: null
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
function resolveProductSelection(detail, selection = {}, options) {
|
|
3758
|
+
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
3759
|
+
const effectiveSelection = hasExplicitSelection(selection) || detail.listing.selectionHintVariant == null ? selection : { ...selection, variantId: detail.listing.selectionHintVariant };
|
|
3760
|
+
const normalizedSelection = normalizeProductSelection(
|
|
3761
|
+
detail,
|
|
3762
|
+
effectiveSelection,
|
|
3763
|
+
options
|
|
3764
|
+
);
|
|
3765
|
+
const matchingVariantEntries = getMatchingVariantEntries(
|
|
3766
|
+
matrix,
|
|
3767
|
+
normalizedSelection
|
|
3768
|
+
);
|
|
3769
|
+
const activeMatchingVariantEntries = activeVariantEntries(
|
|
3770
|
+
matchingVariantEntries
|
|
3771
|
+
);
|
|
3772
|
+
const selectedVariantEntry = getExactSelectedVariantEntry(
|
|
3773
|
+
matrix,
|
|
3774
|
+
normalizedSelection,
|
|
3775
|
+
matchingVariantEntries
|
|
3776
|
+
);
|
|
3777
|
+
const selectedVariant = selectedVariantEntry?.source ?? null;
|
|
3778
|
+
const matchingVariants = activeMatchingVariantEntries.map(
|
|
3779
|
+
(variant) => variant.source
|
|
3780
|
+
);
|
|
3781
|
+
const selectedValues = matrix.optionIds.map((optionId) => normalizedSelection.byOptionId[optionId]).map((valueId) => valueId ? matrix.valueById.get(valueId) : void 0).filter((value) => value !== void 0);
|
|
3782
|
+
const availableValuesByOptionId = Object.fromEntries(
|
|
3783
|
+
matrix.options.map((option) => {
|
|
3784
|
+
const availableValueIds = new Set(
|
|
3785
|
+
getAvailableOptionValues(
|
|
3786
|
+
matrix,
|
|
3787
|
+
option.id,
|
|
3788
|
+
normalizedSelection.valueIds
|
|
3789
|
+
).map((value) => value.id)
|
|
3790
|
+
);
|
|
3791
|
+
return [
|
|
3792
|
+
option.id,
|
|
3793
|
+
option.values.map((value) => ({
|
|
3794
|
+
valueId: value.id,
|
|
3795
|
+
value: value.label,
|
|
3796
|
+
slug: requireValueSlug(value),
|
|
3797
|
+
selected: normalizedSelection.byOptionId[option.id] === value.id,
|
|
3798
|
+
available: availableValueIds.has(value.id),
|
|
3799
|
+
swatchColor: value.swatchColor ?? null,
|
|
3800
|
+
thumbnail: value.thumbnail ?? null,
|
|
3801
|
+
images: value.images ?? null
|
|
3802
|
+
}))
|
|
3803
|
+
];
|
|
3804
|
+
})
|
|
3805
|
+
);
|
|
3806
|
+
const availableValuesByOptionSlug = Object.fromEntries(
|
|
3807
|
+
matrix.options.map((option) => [
|
|
3808
|
+
option.slug,
|
|
3809
|
+
availableValuesByOptionId[option.id] ?? []
|
|
3810
|
+
])
|
|
3811
|
+
);
|
|
3812
|
+
const allOptionsSelected = matrix.optionIds.every(
|
|
3813
|
+
(optionId) => Boolean(normalizedSelection.byOptionId[optionId])
|
|
3814
|
+
);
|
|
3815
|
+
const priceVariants = selectedVariant ? [selectedVariant] : matchingVariants;
|
|
3816
|
+
return {
|
|
3817
|
+
normalizedSelection,
|
|
3818
|
+
selectedVariant,
|
|
3819
|
+
matchingVariants,
|
|
3820
|
+
partialVariants: selectedVariant ? [] : matchingVariants,
|
|
3821
|
+
availableValuesByOptionSlug,
|
|
3822
|
+
availableValuesByOptionId,
|
|
3823
|
+
allOptionsSelected,
|
|
3824
|
+
price: buildSelectionPrice(priceVariants),
|
|
3825
|
+
media: buildSelectionMedia(
|
|
3826
|
+
detail,
|
|
3827
|
+
selectedVariant,
|
|
3828
|
+
matchingVariants,
|
|
3829
|
+
selectedValues
|
|
3830
|
+
),
|
|
3831
|
+
stock: buildSelectionStock(selectedVariant, matchingVariants)
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3221
3834
|
function compareVariantOrder(a, b) {
|
|
3222
3835
|
const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
|
|
3223
3836
|
const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
|