@vio-live/web-sdk 0.33.0 → 0.35.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/CHANGELOG.md +75 -0
- package/README.md +137 -13
- package/dist/chunk-3YFJFDSN.js +3 -0
- package/dist/{chunk-QGJUBVH3.js.map → chunk-3YFJFDSN.js.map} +1 -1
- package/dist/{chunk-T5B74YBQ.js → chunk-F7LE5AZY.js} +315 -51
- package/dist/chunk-F7LE5AZY.js.map +1 -0
- package/dist/{chunk-ZORYG4W3.js → chunk-QA5R4VYF.js} +279 -40
- package/dist/chunk-QA5R4VYF.js.map +1 -0
- package/dist/core/index.d.ts +64 -3
- package/dist/core/index.js +2 -2
- package/dist/detail-layout-BI556w8p.d.ts +76 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +4 -2
- package/dist/react/index.js +5 -4
- package/dist/react/index.js.map +1 -1
- package/dist/{tokens-B7GVA_qB.d.ts → tokens-C-LsqKQH.d.ts} +116 -11
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-QGJUBVH3.js +0 -3
- package/dist/chunk-T5B74YBQ.js.map +0 -1
- package/dist/chunk-ZORYG4W3.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vio, __decorateClass, primaryImageUrl, displayPrice, toCampaignId, getGlobalCurrency, ensureVippsButton, formatPrice, shippingDescriptionOf, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, storageGet, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, getGlobalCountryCode, isValidPhone, gatewayHasBuyerRef, rememberedCheckoutSponsor, checkoutSponsorOf, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, sponsorIdentityFrom, Configuration, normalizeScope, campaignCandidates } from './chunk-
|
|
1
|
+
import { Vio, __decorateClass, primaryImageUrl, displayPrice, resolveDetailLayout, toCampaignId, getGlobalCurrency, copyAppliedLayout, onGlobalDetailLayoutChange, layoutSignature, layoutOriginOf, ensureVippsButton, formatPrice, shippingDescriptionOf, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, storageGet, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, getGlobalCountryCode, isValidPhone, gatewayHasBuyerRef, rememberedCheckoutSponsor, checkoutSponsorOf, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, sponsorIdentityFrom, Configuration, normalizeScope, campaignCandidates } from './chunk-QA5R4VYF.js';
|
|
2
2
|
import { LitElement, css, html, nothing } from 'lit';
|
|
3
3
|
import { property, state, query } from 'lit/decorators.js';
|
|
4
4
|
|
|
@@ -201,7 +201,8 @@ var THEME_KEY_TO_CSS_VAR = {
|
|
|
201
201
|
confirmTitleSize: "--vio-confirm-title-size",
|
|
202
202
|
confirmCheckBg: "--vio-confirm-check-bg",
|
|
203
203
|
redirectBg: "--vio-redirect-bg",
|
|
204
|
-
redirectSpinner: "--vio-redirect-spinner"
|
|
204
|
+
redirectSpinner: "--vio-redirect-spinner",
|
|
205
|
+
vippsCartPayment: "--vio-vipps-cart-payment"
|
|
205
206
|
};
|
|
206
207
|
function applyVioTheme(overrides, target = typeof document !== "undefined" ? document.documentElement : null) {
|
|
207
208
|
if (!target) return;
|
|
@@ -1556,6 +1557,21 @@ function returnCancelled(detail) {
|
|
|
1556
1557
|
emit("vio:payment-redirect-cancel", { ...detail, reason: "cancelled" });
|
|
1557
1558
|
}
|
|
1558
1559
|
|
|
1560
|
+
// src/ui/vipps-cart-payment.ts
|
|
1561
|
+
var VIPPS_CART_PAYMENT_VAR = "--vio-vipps-cart-payment";
|
|
1562
|
+
function resolveVippsCartPayment(el, own) {
|
|
1563
|
+
let fromTheme = "";
|
|
1564
|
+
if (!own) {
|
|
1565
|
+
try {
|
|
1566
|
+
fromTheme = typeof getComputedStyle === "function" ? getComputedStyle(el).getPropertyValue(VIPPS_CART_PAYMENT_VAR) : "";
|
|
1567
|
+
} catch {
|
|
1568
|
+
fromTheme = "";
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
const value = String(own || fromTheme || "").trim().replace(/^["']|["']$/g, "").toLowerCase();
|
|
1572
|
+
return value === "checkout" ? "checkout" : "express";
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1559
1575
|
// src/core/checkout/method-taxonomy.ts
|
|
1560
1576
|
function normalizeMethodName(name) {
|
|
1561
1577
|
return String(name ?? "").toLowerCase().replace(/[^a-z]/g, "");
|
|
@@ -2037,6 +2053,19 @@ function unlockPageScroll() {
|
|
|
2037
2053
|
}
|
|
2038
2054
|
|
|
2039
2055
|
// src/ui/components/vio-product-detail.ts
|
|
2056
|
+
var layoutAttribute = {
|
|
2057
|
+
fromAttribute(value) {
|
|
2058
|
+
if (value === null || value.trim() === "") return null;
|
|
2059
|
+
try {
|
|
2060
|
+
return JSON.parse(value);
|
|
2061
|
+
} catch {
|
|
2062
|
+
return value;
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
toAttribute(value) {
|
|
2066
|
+
return value === null || value === void 0 ? null : JSON.stringify(value);
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2040
2069
|
var ADDED_TOAST_MS = 8e3;
|
|
2041
2070
|
var STRIPE_LOGO_SRC = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
2042
2071
|
'<svg width="360" height="150" viewBox="0 0 360 150" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M360 77.4001C360 51.8001 347.6 31.6001 323.9 31.6001C300.1 31.6001 285.7 51.8001 285.7 77.2001C285.7 107.3 302.7 122.5 327.1 122.5C339 122.5 348 119.8 354.8 116V96.0001C348 99.4001 340.2 101.5 330.3 101.5C320.6 101.5 312 98.1001 310.9 86.3001H359.8C359.8 85.0001 360 79.8001 360 77.4001ZM310.6 67.9001C310.6 56.6001 317.5 51.9001 323.8 51.9001C329.9 51.9001 336.4 56.6001 336.4 67.9001H310.6Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 31.6001C237.3 31.6001 231 36.2001 227.5 39.4001L226.2 33.2001H204.2V149.8L229.2 144.5L229.3 116.2C232.9 118.8 238.2 122.5 247 122.5C264.9 122.5 281.2 108.1 281.2 76.4001C281.1 47.4001 264.6 31.6001 247.1 31.6001ZM241.1 100.5C235.2 100.5 231.7 98.4001 229.3 95.8001L229.2 58.7001C231.8 55.8001 235.4 53.8001 241.1 53.8001C250.2 53.8001 256.5 64.0001 256.5 77.1001C256.5 90.5001 250.3 100.5 241.1 100.5Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 25.7L194.9 20.3V0L169.8 5.3V25.7Z" fill="#533AFD"/><path d="M194.9 33.3H169.8V120.8H194.9V33.3Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 40.7L141.3 33.3H119.7V120.8H144.7V61.5C150.6 53.8 160.6 55.2 163.7 56.3V33.3C160.5 32.1 148.8 29.9 142.9 40.7Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 11.6001L68.4999 16.8001L68.3999 96.9001C68.3999 111.7 79.4999 122.6 94.2999 122.6C102.5 122.6 108.5 121.1 111.8 119.3V99.0001C108.6 100.3 92.7999 104.9 92.7999 90.1001V54.6001H111.8V33.3001H92.7999L92.8999 11.6001Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 58.7001C25.3 54.8001 28.5 53.3001 33.8 53.3001C41.4 53.3001 51 55.6001 58.6 59.7001V36.2001C50.3 32.9001 42.1 31.6001 33.8 31.6001C13.5 31.6001 0 42.2001 0 59.9001C0 87.5001 38 83.1001 38 95.0001C38 99.6001 34 101.1 28.4 101.1C20.1 101.1 9.5 97.7001 1.1 93.1001V116.9C10.4 120.9 19.8 122.6 28.4 122.6C49.2 122.6 63.5 112.3 63.5 94.4001C63.4 64.6001 25.3 69.9001 25.3 58.7001Z" fill="#533AFD"/></svg>'
|
|
@@ -2087,11 +2116,18 @@ var VioProductDetail = class extends LitElement {
|
|
|
2087
2116
|
this.vioContentId = "";
|
|
2088
2117
|
this.vioContentTitle = "";
|
|
2089
2118
|
this.noAddedToast = false;
|
|
2119
|
+
this.vippsCartPayment = "";
|
|
2090
2120
|
this.gallery = "";
|
|
2091
2121
|
this.descriptionMode = "";
|
|
2092
2122
|
this.highlights = [];
|
|
2093
2123
|
this.deliveryTime = "";
|
|
2094
2124
|
this.carrierLogos = {};
|
|
2125
|
+
this.layout = null;
|
|
2126
|
+
/** The layout the detail renders with now (resolved before each render). */
|
|
2127
|
+
this.layoutNow = resolveDetailLayout(null);
|
|
2128
|
+
/** The product and layout `vio:detail-layout-applied` was last said for. */
|
|
2129
|
+
this.appliedFor = null;
|
|
2130
|
+
this.unsubscribeLayout = null;
|
|
2095
2131
|
this.slotRegistration = new SlotRegistration(this, "detail");
|
|
2096
2132
|
/** What the click that opened the detail carried ('' when nothing), and for which product. */
|
|
2097
2133
|
this.eventSlot = "";
|
|
@@ -2180,6 +2216,10 @@ var VioProductDetail = class extends LitElement {
|
|
|
2180
2216
|
this.show();
|
|
2181
2217
|
void this.fetchProduct();
|
|
2182
2218
|
};
|
|
2219
|
+
/** The cart changed (another tab, the drawer): the line under Vipps counts it again. */
|
|
2220
|
+
this.boundCartChange = () => {
|
|
2221
|
+
if (this.open) this.requestUpdate();
|
|
2222
|
+
};
|
|
2183
2223
|
/** The photo in view is the active one (dots, Apple Pay sheet, events). */
|
|
2184
2224
|
this.onCarouselScroll = () => {
|
|
2185
2225
|
const track = this.carouselTrack();
|
|
@@ -2198,6 +2238,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
2198
2238
|
if (atEnd !== this.thumbsAtEnd) this.thumbsAtEnd = atEnd;
|
|
2199
2239
|
};
|
|
2200
2240
|
}
|
|
2241
|
+
/**
|
|
2242
|
+
* The layout this detail applies (0.34): `{ id, revision, experimentId?,
|
|
2243
|
+
* variantId?, sections, adjusted, source }` — `source` is `element` (its own
|
|
2244
|
+
* `layout`), `global` (`Vio.configureDetail`) or `default`. A copy.
|
|
2245
|
+
*/
|
|
2246
|
+
get appliedLayout() {
|
|
2247
|
+
return copyAppliedLayout(resolveDetailLayout(this.layout));
|
|
2248
|
+
}
|
|
2201
2249
|
/** The card's context applies to the product it opened — not to one the host opened by hand later. */
|
|
2202
2250
|
get fromCard() {
|
|
2203
2251
|
return this.eventProductId !== "" && this.eventProductId === String(this.productId);
|
|
@@ -2714,6 +2762,10 @@ var VioProductDetail = class extends LitElement {
|
|
|
2714
2762
|
}
|
|
2715
2763
|
/* The sizes follow the price closely: they are the next thing to do. */
|
|
2716
2764
|
.headline + .purchase { margin-top: calc(var(--vio-detail-sizes-gap, 2px) - var(--vio-detail-info-gap, 20px)); }
|
|
2765
|
+
/* A layout with the sizes apart from the buy zone (0.34): they follow the
|
|
2766
|
+
price closely instead, and the buy zone keeps the normal gap. */
|
|
2767
|
+
.headline + .buy-options.options-apart { margin-top: calc(var(--vio-detail-sizes-gap, 2px) - var(--vio-detail-info-gap, 20px)); }
|
|
2768
|
+
.headline + .purchase.options-apart { margin-top: 0; }
|
|
2717
2769
|
.price {
|
|
2718
2770
|
font-size: var(--vio-detail-price-size, 24px);
|
|
2719
2771
|
font-weight: var(--vio-detail-price-weight, 700);
|
|
@@ -3205,6 +3257,9 @@ var VioProductDetail = class extends LitElement {
|
|
|
3205
3257
|
}
|
|
3206
3258
|
connectedCallback() {
|
|
3207
3259
|
super.connectedCallback();
|
|
3260
|
+
Vio.cart.addEventListener("change", this.boundCartChange);
|
|
3261
|
+
this.unsubscribeLayout?.();
|
|
3262
|
+
this.unsubscribeLayout = onGlobalDetailLayoutChange(() => this.requestUpdate());
|
|
3208
3263
|
this.slotRegistration.connect(this.slotAttributes());
|
|
3209
3264
|
void this.loadVippsButton();
|
|
3210
3265
|
document.addEventListener("vio:product-click", this.boundProductClick);
|
|
@@ -3213,8 +3268,11 @@ var VioProductDetail = class extends LitElement {
|
|
|
3213
3268
|
if (this.addedToastTimer) clearTimeout(this.addedToastTimer);
|
|
3214
3269
|
this.addedToastTimer = null;
|
|
3215
3270
|
document.removeEventListener("vio:product-click", this.boundProductClick);
|
|
3271
|
+
Vio.cart.removeEventListener("change", this.boundCartChange);
|
|
3216
3272
|
this.slotRegistration.clear();
|
|
3217
3273
|
this.bottomSheet.disconnect();
|
|
3274
|
+
this.unsubscribeLayout?.();
|
|
3275
|
+
this.unsubscribeLayout = null;
|
|
3218
3276
|
super.disconnectedCallback();
|
|
3219
3277
|
}
|
|
3220
3278
|
updated(changed) {
|
|
@@ -3238,17 +3296,38 @@ var VioProductDetail = class extends LitElement {
|
|
|
3238
3296
|
else this.revealActiveSlide();
|
|
3239
3297
|
}
|
|
3240
3298
|
this.measureLogo();
|
|
3299
|
+
this.announceLayout();
|
|
3241
3300
|
if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName") || changed.has("vioCampaign")) {
|
|
3242
3301
|
this.slotRegistration.sync(this.slotAttributes());
|
|
3243
3302
|
}
|
|
3244
3303
|
}
|
|
3245
3304
|
willUpdate(changed) {
|
|
3305
|
+
this.layoutNow = resolveDetailLayout(this.layout);
|
|
3246
3306
|
if (changed.has("gallery") || changed.has("open") && this.open) this.galleryMode = this.resolveGalleryMode();
|
|
3247
3307
|
if (changed.has("descriptionMode") || changed.has("open") && this.open) {
|
|
3248
3308
|
const value = this.themeChoice(this.descriptionMode, "--vio-detail-description-mode");
|
|
3249
3309
|
this.resolvedDescriptionMode = value === "folded" || value === "open" ? value : "preview";
|
|
3250
3310
|
}
|
|
3251
3311
|
}
|
|
3312
|
+
/**
|
|
3313
|
+
* `vio:detail-layout-applied` (bubbles, composed), with the applied layout,
|
|
3314
|
+
* each time a product renders with a layout it has not been shown with yet:
|
|
3315
|
+
* every open of a product, and every change of layout while it is open.
|
|
3316
|
+
*/
|
|
3317
|
+
announceLayout() {
|
|
3318
|
+
const product = this.product;
|
|
3319
|
+
if (!product || this.fetchError) return;
|
|
3320
|
+
const signature = layoutSignature(this.layoutNow);
|
|
3321
|
+
if (this.appliedFor && this.appliedFor.product === product && this.appliedFor.signature === signature) return;
|
|
3322
|
+
this.appliedFor = { product, signature };
|
|
3323
|
+
this.dispatchEvent(
|
|
3324
|
+
new CustomEvent("vio:detail-layout-applied", {
|
|
3325
|
+
bubbles: true,
|
|
3326
|
+
composed: true,
|
|
3327
|
+
detail: { ...copyAppliedLayout(this.layoutNow), productId: product.id, sponsorId: this.sponsorId }
|
|
3328
|
+
})
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3252
3331
|
/** An option as the element says it, else as the theme's custom property says it (lower case, unquoted). */
|
|
3253
3332
|
themeChoice(own, cssVar) {
|
|
3254
3333
|
const fromTheme = typeof getComputedStyle === "function" ? getComputedStyle(this).getPropertyValue(cssVar) : "";
|
|
@@ -3378,10 +3457,17 @@ var VioProductDetail = class extends LitElement {
|
|
|
3378
3457
|
const price = Number(
|
|
3379
3458
|
this.product.price?.amount_incl_taxes ?? this.product.price?.amount
|
|
3380
3459
|
);
|
|
3460
|
+
const shown = layoutOriginOf(resolveDetailLayout(this.layout));
|
|
3381
3461
|
Vio.analytics.track("view_item", {
|
|
3382
3462
|
context: {
|
|
3383
3463
|
sponsorId: this.sponsorId > 0 ? Number(this.sponsorId) : void 0,
|
|
3384
|
-
...this.attribution()
|
|
3464
|
+
...this.attribution(),
|
|
3465
|
+
...shown.layoutId ? {
|
|
3466
|
+
layoutId: shown.layoutId,
|
|
3467
|
+
layoutRevision: shown.layoutRevision ?? "",
|
|
3468
|
+
experimentId: shown.experimentId,
|
|
3469
|
+
variantId: shown.variantId
|
|
3470
|
+
} : {}
|
|
3385
3471
|
},
|
|
3386
3472
|
commerce: {
|
|
3387
3473
|
items: [
|
|
@@ -3712,7 +3798,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
3712
3798
|
this.selectionPrompt = missing.name;
|
|
3713
3799
|
void this.updateComplete.then(() => {
|
|
3714
3800
|
const group = this.renderRoot.querySelector(".option-group.needs-choice");
|
|
3715
|
-
group?.
|
|
3801
|
+
const apart = !!group?.closest(".options-apart");
|
|
3802
|
+
group?.scrollIntoView?.({ behavior: "smooth", block: apart ? "center" : "nearest" });
|
|
3716
3803
|
});
|
|
3717
3804
|
return false;
|
|
3718
3805
|
}
|
|
@@ -3782,13 +3869,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
3782
3869
|
const variant = this.selectedVariant();
|
|
3783
3870
|
const sponsorName = resolveSponsorIdentity(this.sponsorId)?.name || void 0;
|
|
3784
3871
|
const { campaignId, slot, contentId, componentTemplateId, contentUrl, contentTitle } = this.attribution();
|
|
3872
|
+
const layout = layoutOriginOf(this.layoutNow);
|
|
3785
3873
|
Vio.cart.addProduct({
|
|
3786
3874
|
product: this.product,
|
|
3787
3875
|
sponsorId: this.sponsorId,
|
|
3788
3876
|
sponsorName,
|
|
3789
3877
|
quantity: this.quantity,
|
|
3790
3878
|
variantId: variant?.id,
|
|
3791
|
-
origin: { campaignId, slot, contentId, componentKind: componentTemplateId, contentUrl, contentTitle }
|
|
3879
|
+
origin: { campaignId, slot, contentId, componentKind: componentTemplateId, contentUrl, contentTitle, ...layout }
|
|
3792
3880
|
});
|
|
3793
3881
|
return variant?.id;
|
|
3794
3882
|
}
|
|
@@ -3807,7 +3895,9 @@ var VioProductDetail = class extends LitElement {
|
|
|
3807
3895
|
sponsorId: this.sponsorId,
|
|
3808
3896
|
quantity: this.quantity,
|
|
3809
3897
|
variantId,
|
|
3810
|
-
...this.attribution()
|
|
3898
|
+
...this.attribution(),
|
|
3899
|
+
// The detail layout of the add (0.34) — `variantId` above is the product's.
|
|
3900
|
+
...this.layoutNow.source !== "default" ? { layout: layoutOriginOf(this.layoutNow) } : {}
|
|
3811
3901
|
}
|
|
3812
3902
|
})
|
|
3813
3903
|
);
|
|
@@ -3920,7 +4010,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
3920
4010
|
if (!this.product || this.availableQuantity <= 0 || this.adding) return;
|
|
3921
4011
|
if (!this.requireSelection()) return;
|
|
3922
4012
|
if (!(this.unitPrice > 0)) return;
|
|
3923
|
-
if (
|
|
4013
|
+
if (this.cartHasLines() && this.vippsCartMode() === "checkout") {
|
|
3924
4014
|
this.addCurrentToCart();
|
|
3925
4015
|
this.close();
|
|
3926
4016
|
this.openCheckoutWithVipps();
|
|
@@ -3932,12 +4022,46 @@ var VioProductDetail = class extends LitElement {
|
|
|
3932
4022
|
void this.startVippsExpressOrCheckout(redirect);
|
|
3933
4023
|
}
|
|
3934
4024
|
/**
|
|
3935
|
-
* The Vipps button will open the app with Express
|
|
3936
|
-
* brand has Express on, and this brand's cart is
|
|
3937
|
-
*
|
|
4025
|
+
* The Vipps button will open the app with Express for this product alone
|
|
4026
|
+
* (see buyWithVipps): the brand has Express on, and this brand's cart is
|
|
4027
|
+
* empty.
|
|
3938
4028
|
*/
|
|
3939
4029
|
vippsGoesStraightToApp() {
|
|
3940
|
-
return this.vippsExpress &&
|
|
4030
|
+
return this.vippsExpress && !this.cartHasLines();
|
|
4031
|
+
}
|
|
4032
|
+
/** This brand's cart already has lines. */
|
|
4033
|
+
cartHasLines() {
|
|
4034
|
+
return (Vio.cart.getCart(this.sponsorId)?.items.length ?? 0) > 0;
|
|
4035
|
+
}
|
|
4036
|
+
/** `vipps-cart-payment`, else the theme's, else `express`. */
|
|
4037
|
+
vippsCartMode() {
|
|
4038
|
+
return resolveVippsCartPayment(this, this.vippsCartPayment);
|
|
4039
|
+
}
|
|
4040
|
+
/**
|
|
4041
|
+
* What the Vipps button will pay when the brand's cart already has lines
|
|
4042
|
+
* (`express` mode): every unit in the cart plus this product at the chosen
|
|
4043
|
+
* quantity, and their total, as the cart sums it (shipping is chosen in
|
|
4044
|
+
* Vipps). null with an empty cart, or in `checkout` mode.
|
|
4045
|
+
*/
|
|
4046
|
+
wholeCartToPay() {
|
|
4047
|
+
if (this.vippsCartMode() !== "express") return null;
|
|
4048
|
+
const cart = Vio.cart.getCart(this.sponsorId);
|
|
4049
|
+
const items = cart?.items ?? [];
|
|
4050
|
+
if (items.length === 0) return null;
|
|
4051
|
+
const units = items.reduce((n, i) => n + (Number(i.quantity) || 0), 0) + this.quantity;
|
|
4052
|
+
const total = items.reduce((sum, i) => sum + (Number(i.unitPrice) || 0) * (Number(i.quantity) || 0), 0) + this.unitPrice * this.quantity;
|
|
4053
|
+
return { units, total, currency: cart?.currency || this.currency };
|
|
4054
|
+
}
|
|
4055
|
+
/** The line under the Vipps button: what is paid (a cart with lines) or what happens in the app (Express, empty cart). */
|
|
4056
|
+
renderVippsNote() {
|
|
4057
|
+
const whole = this.wholeCartToPay();
|
|
4058
|
+
if (whole) {
|
|
4059
|
+
return html`<p class="vipps-note vipps-note-cart" part="vipps-note">Betaler hele handlekurven · ${whole.units} varer · ${formatPrice(whole.total, whole.currency)}</p>`;
|
|
4060
|
+
}
|
|
4061
|
+
if (this.vippsGoesStraightToApp()) {
|
|
4062
|
+
return html`<p class="vipps-note" part="vipps-note">Vipps fyller inn navn og adresse – du godkjenner og betaler i appen</p>`;
|
|
4063
|
+
}
|
|
4064
|
+
return "";
|
|
3941
4065
|
}
|
|
3942
4066
|
async startVippsExpressOrCheckout(redirect) {
|
|
3943
4067
|
let fallback = true;
|
|
@@ -4012,8 +4136,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
4012
4136
|
* after an await, so it cannot wait for the server's cart), while the
|
|
4013
4137
|
* charge is the sponsor's WHOLE cart. So the sheet opens only when it shows
|
|
4014
4138
|
* what will be charged: an empty cart, and a sheet prepared for this
|
|
4015
|
-
* product × quantity. Otherwise — lines already in the cart
|
|
4016
|
-
*
|
|
4139
|
+
* product × quantity. Otherwise — lines already in the cart, or a quantity
|
|
4140
|
+
* whose sheet is still being prepared —
|
|
4017
4141
|
* the product is added and the checkout opens with Apple Pay preselected:
|
|
4018
4142
|
* every line and the total on screen, and its own Apple Pay button pays
|
|
4019
4143
|
* that total.
|
|
@@ -4318,9 +4442,6 @@ var VioProductDetail = class extends LitElement {
|
|
|
4318
4442
|
}
|
|
4319
4443
|
if (!this.product) return html``;
|
|
4320
4444
|
const p = this.product;
|
|
4321
|
-
const stock = this.stockLabel();
|
|
4322
|
-
const compare = this.compareAt;
|
|
4323
|
-
const showCompare = compare !== null && compare > this.unitPrice;
|
|
4324
4445
|
const images = this.allImages();
|
|
4325
4446
|
const activeImage = this.activeImage;
|
|
4326
4447
|
return html`
|
|
@@ -4365,24 +4486,110 @@ var VioProductDetail = class extends LitElement {
|
|
|
4365
4486
|
</div>`}
|
|
4366
4487
|
|
|
4367
4488
|
<div class="info" part="info">
|
|
4368
|
-
${
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4489
|
+
${this.renderSections(this.layoutNow.sections)}
|
|
4490
|
+
</div>
|
|
4491
|
+
</div>
|
|
4492
|
+
`;
|
|
4493
|
+
}
|
|
4494
|
+
/**
|
|
4495
|
+
* The info column in the applied layout's order (0.34) — the real DOM in
|
|
4496
|
+
* that order, never CSS `order`, the same on every screen. The default
|
|
4497
|
+
* order renders exactly the column before 0.34: brand, `.headline` (the
|
|
4498
|
+
* title row with the stock, the price row), then `.purchase` — `.buy` with
|
|
4499
|
+
* the options first — and highlights, description, «Frakt & retur» in it.
|
|
4500
|
+
*
|
|
4501
|
+
* A run of `title` / `stock` / `price` is one `.headline`; `title`
|
|
4502
|
+
* right before `stock` share the title row. `options` right before
|
|
4503
|
+
* `purchase` live in `.buy` (a pinned buy zone keeps them); elsewhere they
|
|
4504
|
+
* are a block of their own. Everything after `purchase` goes in
|
|
4505
|
+
* `.purchase`, as the description does by default: a pinned buy zone may
|
|
4506
|
+
* slide over it, never over what comes before.
|
|
4507
|
+
*/
|
|
4508
|
+
renderSections(sections) {
|
|
4509
|
+
const out = [];
|
|
4510
|
+
for (let i = 0; i < sections.length; i++) {
|
|
4511
|
+
const s = sections[i];
|
|
4512
|
+
if (s === "title" || s === "stock" || s === "price") {
|
|
4513
|
+
let j = i;
|
|
4514
|
+
while (j < sections.length && ["title", "stock", "price"].includes(sections[j])) j++;
|
|
4515
|
+
out.push(this.renderHeadline(sections.slice(i, j)));
|
|
4516
|
+
i = j - 1;
|
|
4517
|
+
continue;
|
|
4518
|
+
}
|
|
4519
|
+
if (s === "options" && sections[i + 1] === "purchase") continue;
|
|
4520
|
+
if (s === "purchase") {
|
|
4521
|
+
const optionsInBuy = sections[i - 1] === "options";
|
|
4522
|
+
out.push(this.renderPurchase(optionsInBuy, sections.slice(i + 1)));
|
|
4523
|
+
break;
|
|
4524
|
+
}
|
|
4525
|
+
out.push(this.renderSection(s));
|
|
4526
|
+
}
|
|
4527
|
+
return out;
|
|
4528
|
+
}
|
|
4529
|
+
/** A run of title / stock / price: one `.headline`. */
|
|
4530
|
+
renderHeadline(run) {
|
|
4531
|
+
const p = this.product;
|
|
4532
|
+
const stock = this.stockLabel();
|
|
4533
|
+
const compare = this.compareAt;
|
|
4534
|
+
const showCompare = compare !== null && compare > this.unitPrice;
|
|
4535
|
+
const name = html`<h2 class="name" part="title">${p.title}</h2>`;
|
|
4536
|
+
const stockLine = html`<div class="stock ${stock.cls}" part="stock">${stock.text}</div>`;
|
|
4537
|
+
const parts = [];
|
|
4538
|
+
for (let i = 0; i < run.length; i++) {
|
|
4539
|
+
const s = run[i];
|
|
4540
|
+
if (s === "title" && run[i + 1] === "stock") {
|
|
4541
|
+
parts.push(html`<div class="title-row">
|
|
4542
|
+
${name}
|
|
4543
|
+
${stockLine}
|
|
4544
|
+
</div>`);
|
|
4545
|
+
i++;
|
|
4546
|
+
} else if (s === "title") {
|
|
4547
|
+
parts.push(html`<div class="title-row">${name}</div>`);
|
|
4548
|
+
} else if (s === "stock") {
|
|
4549
|
+
parts.push(stockLine);
|
|
4550
|
+
} else {
|
|
4551
|
+
parts.push(html`<div class="price-row">
|
|
4375
4552
|
<span class="price" part="price">${formatPrice(this.unitPrice, p.price.currency_code)}</span>
|
|
4376
4553
|
${showCompare ? html`<span class="compare-at">${formatPrice(
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
</div
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4554
|
+
compare,
|
|
4555
|
+
p.price.currency_code
|
|
4556
|
+
)}</span>` : ""}
|
|
4557
|
+
</div>`);
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
return html`<div class="headline">
|
|
4561
|
+
${parts}
|
|
4562
|
+
</div>`;
|
|
4563
|
+
}
|
|
4564
|
+
/** One section that is not the headline nor the purchase group. */
|
|
4565
|
+
renderSection(section) {
|
|
4566
|
+
const p = this.product;
|
|
4567
|
+
switch (section) {
|
|
4568
|
+
case "brand":
|
|
4569
|
+
return p.brand ? html`<div class="brand" part="brand">${p.brand}</div>` : "";
|
|
4570
|
+
case "options":
|
|
4571
|
+
return p.options.length > 0 ? html`<div class="buy-options options-apart" part="buy-options">${this.renderOptionPickers(p.options)}</div>` : "";
|
|
4572
|
+
case "highlights":
|
|
4573
|
+
return this.renderHighlights();
|
|
4574
|
+
case "description":
|
|
4575
|
+
return p.description ? this.renderDescription(this.stripHtml(p.description)) : "";
|
|
4576
|
+
case "shippingReturns":
|
|
4577
|
+
return this.renderShippingReturns();
|
|
4578
|
+
default:
|
|
4579
|
+
return "";
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
/**
|
|
4583
|
+
* The SDK-managed buy group, never split: «Frakt fra…», Vipps / Apple Pay /
|
|
4584
|
+
* the other ways to pay, quantity + «Legg i handlekurv», «Du handler direkte
|
|
4585
|
+
* hos …». The options when they come right before it; then what follows it.
|
|
4586
|
+
*/
|
|
4587
|
+
renderPurchase(optionsInBuy, rest) {
|
|
4588
|
+
const p = this.product;
|
|
4589
|
+
const optionsApart = !optionsInBuy && p.options.length > 0;
|
|
4590
|
+
return html`<div class=${optionsApart ? "purchase options-apart" : "purchase"}>
|
|
4384
4591
|
<div class="buy" part="buy">
|
|
4385
|
-
${p.options.length > 0 ? html`<div class="buy-options" part="buy-options">${this.renderOptionPickers(p.options)}</div>` : ""}
|
|
4592
|
+
${optionsInBuy && p.options.length > 0 ? html`<div class="buy-options" part="buy-options">${this.renderOptionPickers(p.options)}</div>` : ""}
|
|
4386
4593
|
${this.renderDeliveryNote()}
|
|
4387
4594
|
${this.methodsPending && this.availableQuantity > 0 ? html`<div class="buy-pending" part="buy-pending" role="status" aria-label="Laster betalingsmåter"></div>` : ""}
|
|
4388
4595
|
${this.availableQuantity > 0 && this.hasPrice && this.methodEnabled("vipps") ? this.vippsOfficial ? html`
|
|
@@ -4411,7 +4618,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
4411
4618
|
<span class="vipps-badge">vipps</span>
|
|
4412
4619
|
</button>
|
|
4413
4620
|
` : ""}
|
|
4414
|
-
${this.availableQuantity > 0 && this.hasPrice && this.methodEnabled("vipps")
|
|
4621
|
+
${this.availableQuantity > 0 && this.hasPrice && this.methodEnabled("vipps") ? this.renderVippsNote() : ""}
|
|
4415
4622
|
|
|
4416
4623
|
<div class="qty-cta-row" part="quantity-row">
|
|
4417
4624
|
<div class="qty-stepper">
|
|
@@ -4499,14 +4706,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
4499
4706
|
${this.renderSellerNote()}
|
|
4500
4707
|
</div>
|
|
4501
4708
|
|
|
4502
|
-
${this.
|
|
4503
|
-
|
|
4504
|
-
${p.description ? this.renderDescription(this.stripHtml(p.description)) : ""}
|
|
4505
|
-
${this.renderShippingReturns()}
|
|
4506
|
-
</div>
|
|
4507
|
-
</div>
|
|
4508
|
-
</div>
|
|
4509
|
-
`;
|
|
4709
|
+
${this.renderSections(rest)}
|
|
4710
|
+
</div>`;
|
|
4510
4711
|
}
|
|
4511
4712
|
renderOptionPickers(options) {
|
|
4512
4713
|
if (options.length === 0) return "";
|
|
@@ -4590,6 +4791,9 @@ __decorateClass([
|
|
|
4590
4791
|
__decorateClass([
|
|
4591
4792
|
property({ type: Boolean, attribute: "no-added-toast" })
|
|
4592
4793
|
], VioProductDetail.prototype, "noAddedToast", 2);
|
|
4794
|
+
__decorateClass([
|
|
4795
|
+
property({ type: String, attribute: "vipps-cart-payment" })
|
|
4796
|
+
], VioProductDetail.prototype, "vippsCartPayment", 2);
|
|
4593
4797
|
__decorateClass([
|
|
4594
4798
|
property({ type: String })
|
|
4595
4799
|
], VioProductDetail.prototype, "gallery", 2);
|
|
@@ -4605,6 +4809,9 @@ __decorateClass([
|
|
|
4605
4809
|
__decorateClass([
|
|
4606
4810
|
property({ type: Object, attribute: "carrier-logos" })
|
|
4607
4811
|
], VioProductDetail.prototype, "carrierLogos", 2);
|
|
4812
|
+
__decorateClass([
|
|
4813
|
+
property({ attribute: "layout", converter: layoutAttribute })
|
|
4814
|
+
], VioProductDetail.prototype, "layout", 2);
|
|
4608
4815
|
__decorateClass([
|
|
4609
4816
|
state()
|
|
4610
4817
|
], VioProductDetail.prototype, "product", 2);
|
|
@@ -4731,12 +4938,15 @@ var VioCart = class extends LitElement {
|
|
|
4731
4938
|
this.heading = "Handlekurv";
|
|
4732
4939
|
this.emptyLabel = "Handlekurven er tom";
|
|
4733
4940
|
this.subtotalLabel = "Sum";
|
|
4941
|
+
this.vippsCartPayment = "";
|
|
4734
4942
|
this.carts = /* @__PURE__ */ new Map();
|
|
4735
4943
|
this.itemCount = 0;
|
|
4736
4944
|
this.applePayAvailable = false;
|
|
4737
4945
|
this.availableMethods = null;
|
|
4738
4946
|
this.methodsLoading = false;
|
|
4739
4947
|
this.vippsOfficial = false;
|
|
4948
|
+
/** A Vipps payment is being started from here: one at a time. */
|
|
4949
|
+
this.vippsStarting = false;
|
|
4740
4950
|
/** Whether the method lookup has been attempted — see boundOnCartChange. */
|
|
4741
4951
|
this.paymentMethodsAttempted = false;
|
|
4742
4952
|
this.confirmedOrder = null;
|
|
@@ -5312,7 +5522,6 @@ var VioCart = class extends LitElement {
|
|
|
5312
5522
|
})
|
|
5313
5523
|
);
|
|
5314
5524
|
}
|
|
5315
|
-
/** Vipps — opens the checkout with Vipps preselected. */
|
|
5316
5525
|
/**
|
|
5317
5526
|
* Open the checkout without preselecting a method.
|
|
5318
5527
|
*
|
|
@@ -5334,21 +5543,73 @@ var VioCart = class extends LitElement {
|
|
|
5334
5543
|
);
|
|
5335
5544
|
}
|
|
5336
5545
|
/**
|
|
5337
|
-
* Vipps from the cart:
|
|
5338
|
-
*
|
|
5339
|
-
*
|
|
5340
|
-
*
|
|
5341
|
-
*
|
|
5546
|
+
* Vipps from the cart (2026-10-29: «el botón negro va con el nuestro y el
|
|
5547
|
+
* botón de Vipps paga el carrito entero»): Vipps Express for the sponsor's
|
|
5548
|
+
* WHOLE cart — the same Express as the detail's «Kjøp nå med Vipps»: the
|
|
5549
|
+
* hand-over overlay, `vio:payment-redirect-*` with source `'cart'`, the
|
|
5550
|
+
* return and its confirmation. «Til kassen» is the way into our checkout.
|
|
5551
|
+
* When the brand has Express off, or this cart cannot have it (several
|
|
5552
|
+
* suppliers, no rate), the checkout opens with Vipps preselected.
|
|
5553
|
+
*
|
|
5554
|
+
* `vipps-cart-payment="checkout"` keeps the 0.31–0.34 behaviour: the
|
|
5555
|
+
* checkout opens with Vipps preselected, the standard payment.
|
|
5342
5556
|
*/
|
|
5343
5557
|
onVipps() {
|
|
5344
5558
|
if (!this.payable) return;
|
|
5345
|
-
|
|
5346
|
-
|
|
5559
|
+
if (resolveVippsCartPayment(this, this.vippsCartPayment) === "checkout") {
|
|
5560
|
+
const firstSponsorId = [...this.carts.keys()][0];
|
|
5561
|
+
if (firstSponsorId === void 0) return;
|
|
5562
|
+
this.openCheckoutWithVipps(firstSponsorId);
|
|
5563
|
+
return;
|
|
5564
|
+
}
|
|
5565
|
+
if (this.vippsStarting) return;
|
|
5566
|
+
const sponsorId = this.vippsSponsorId();
|
|
5567
|
+
if (sponsorId === void 0) return;
|
|
5568
|
+
this.vippsStarting = true;
|
|
5569
|
+
const redirect = beginPaymentRedirect({ method: "vipps", sponsorId, source: "cart" });
|
|
5570
|
+
void this.startVippsExpressOrCheckout(sponsorId, redirect).finally(() => {
|
|
5571
|
+
this.vippsStarting = false;
|
|
5572
|
+
});
|
|
5573
|
+
}
|
|
5574
|
+
/**
|
|
5575
|
+
* The one sponsor whose cart the Vipps button pays: the first with
|
|
5576
|
+
* something to pay for. Express is built on THAT sponsor's backend cart
|
|
5577
|
+
* only — another sponsor's lines are never in it.
|
|
5578
|
+
*/
|
|
5579
|
+
vippsSponsorId() {
|
|
5580
|
+
for (const cart of this.carts.values()) {
|
|
5581
|
+
const total = cart.items.reduce((sum, i) => sum + i.unitPrice * i.quantity, 0);
|
|
5582
|
+
if (cart.items.length > 0 && total > 0) return cart.sponsorId;
|
|
5583
|
+
}
|
|
5584
|
+
return void 0;
|
|
5585
|
+
}
|
|
5586
|
+
async startVippsExpressOrCheckout(sponsorId, redirect) {
|
|
5587
|
+
let fallback = true;
|
|
5588
|
+
try {
|
|
5589
|
+
await Vio.cart.waitForCartMutations(sponsorId);
|
|
5590
|
+
if (!await Vio.checkout.getVippsExpressEnabled(sponsorId)) {
|
|
5591
|
+
redirect.abort("express-off");
|
|
5592
|
+
} else {
|
|
5593
|
+
const res = await Vio.checkout.startVippsExpress(sponsorId);
|
|
5594
|
+
fallback = !res?.payment_url;
|
|
5595
|
+
if (fallback) redirect.abort("not-started");
|
|
5596
|
+
}
|
|
5597
|
+
} catch (err) {
|
|
5598
|
+
if (typeof console !== "undefined") {
|
|
5599
|
+
console.warn("[VioCart] Vipps Express not started:", err);
|
|
5600
|
+
}
|
|
5601
|
+
redirect.fail(err instanceof Error ? err.message : String(err));
|
|
5602
|
+
}
|
|
5603
|
+
if (!fallback) return;
|
|
5604
|
+
this.openCheckoutWithVipps(sponsorId);
|
|
5605
|
+
}
|
|
5606
|
+
/** Our checkout with Vipps preselected: the host opens it on `vio:checkout-open`. */
|
|
5607
|
+
openCheckoutWithVipps(sponsorId) {
|
|
5347
5608
|
this.dispatchEvent(
|
|
5348
5609
|
new CustomEvent("vio:checkout-open", {
|
|
5349
5610
|
bubbles: true,
|
|
5350
5611
|
composed: true,
|
|
5351
|
-
detail: { sponsorId
|
|
5612
|
+
detail: { sponsorId, paymentMethod: "vipps", express: false, ...sponsorDetail(sponsorId) }
|
|
5352
5613
|
})
|
|
5353
5614
|
);
|
|
5354
5615
|
}
|
|
@@ -5616,6 +5877,9 @@ __decorateClass([
|
|
|
5616
5877
|
__decorateClass([
|
|
5617
5878
|
property({ type: String, attribute: "subtotal-label" })
|
|
5618
5879
|
], VioCart.prototype, "subtotalLabel", 2);
|
|
5880
|
+
__decorateClass([
|
|
5881
|
+
property({ type: String, attribute: "vipps-cart-payment" })
|
|
5882
|
+
], VioCart.prototype, "vippsCartPayment", 2);
|
|
5619
5883
|
__decorateClass([
|
|
5620
5884
|
state()
|
|
5621
5885
|
], VioCart.prototype, "carts", 2);
|
|
@@ -9391,5 +9655,5 @@ function registerVioElements() {
|
|
|
9391
9655
|
}
|
|
9392
9656
|
|
|
9393
9657
|
export { VioCart, VioCheckout, VioProduct, VioProductCarousel, VioProductDetail, VioTokens, applyVioTheme, registerVioElements };
|
|
9394
|
-
//# sourceMappingURL=chunk-
|
|
9395
|
-
//# sourceMappingURL=chunk-
|
|
9658
|
+
//# sourceMappingURL=chunk-F7LE5AZY.js.map
|
|
9659
|
+
//# sourceMappingURL=chunk-F7LE5AZY.js.map
|