@vio-live/web-sdk 0.26.1 → 0.28.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 +289 -0
- package/README.md +184 -0
- package/dist/chunk-67QESEV2.js +3 -0
- package/dist/{chunk-NCP76AXW.js.map → chunk-67QESEV2.js.map} +1 -1
- package/dist/{chunk-NXIYIY2D.js → chunk-AFJ5M53F.js} +492 -57
- package/dist/chunk-AFJ5M53F.js.map +1 -0
- package/dist/{chunk-6Q2M3MIK.js → chunk-Q2NALS7P.js} +1053 -165
- package/dist/chunk-Q2NALS7P.js.map +1 -0
- package/dist/core/index.d.ts +229 -19
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{tokens-Bn7fK9ta.d.ts → tokens-y0KzN7bP.d.ts} +153 -3
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +3 -2
- package/dist/chunk-6Q2M3MIK.js.map +0 -1
- package/dist/chunk-NCP76AXW.js +0 -3
- package/dist/chunk-NXIYIY2D.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Vio, __decorateClass, primaryImageUrl, displayPrice, toCampaignId, getGlobalCurrency, ensureVippsButton, formatPrice, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, gatewayHasBuyerRef, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, normalizeScope, campaignCandidates } from './chunk-
|
|
2
|
-
import { LitElement, css, html } from 'lit';
|
|
1
|
+
import { Vio, __decorateClass, primaryImageUrl, displayPrice, toCampaignId, getGlobalCurrency, ensureVippsButton, formatPrice, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, gatewayHasBuyerRef, rememberedCheckoutSponsor, checkoutSponsorOf, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, sponsorIdentityFrom, Configuration, normalizeScope, campaignCandidates } from './chunk-AFJ5M53F.js';
|
|
2
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
3
3
|
import { property, state, query } from 'lit/decorators.js';
|
|
4
4
|
|
|
5
5
|
// src/ui/tokens.ts
|
|
@@ -151,7 +151,38 @@ var THEME_KEY_TO_CSS_VAR = {
|
|
|
151
151
|
spaceSm: "--vio-space-sm",
|
|
152
152
|
spaceMd: "--vio-space-md",
|
|
153
153
|
spaceLg: "--vio-space-lg",
|
|
154
|
-
spaceXl: "--vio-space-xl"
|
|
154
|
+
spaceXl: "--vio-space-xl",
|
|
155
|
+
detailWidth: "--vio-detail-width",
|
|
156
|
+
detailPaddingX: "--vio-detail-padding-x",
|
|
157
|
+
detailLogoHeight: "--vio-detail-logo-height",
|
|
158
|
+
detailLogoZoom: "--vio-detail-logo-zoom",
|
|
159
|
+
detailTitleSize: "--vio-detail-title-size",
|
|
160
|
+
detailPriceSize: "--vio-detail-price-size",
|
|
161
|
+
detailImageFit: "--vio-detail-image-fit",
|
|
162
|
+
detailImageAspect: "--vio-detail-image-aspect",
|
|
163
|
+
detailImageMaxHeight: "--vio-detail-image-max-height",
|
|
164
|
+
detailImageZoom: "--vio-detail-image-zoom",
|
|
165
|
+
detailImageInset: "--vio-detail-image-inset",
|
|
166
|
+
detailImageBg: "--vio-detail-image-bg",
|
|
167
|
+
detailThumbSize: "--vio-detail-thumb-size",
|
|
168
|
+
detailThumbGap: "--vio-detail-thumb-gap",
|
|
169
|
+
detailThumbRadius: "--vio-detail-thumb-radius",
|
|
170
|
+
detailThumbActiveColor: "--vio-detail-thumb-active-color",
|
|
171
|
+
detailOptionHeight: "--vio-detail-option-height",
|
|
172
|
+
detailOptionGap: "--vio-detail-option-gap",
|
|
173
|
+
detailOptionRadius: "--vio-detail-option-radius",
|
|
174
|
+
detailOptionWrap: "--vio-detail-option-wrap",
|
|
175
|
+
detailButtonHeight: "--vio-detail-button-height",
|
|
176
|
+
detailButtonRadius: "--vio-detail-button-radius",
|
|
177
|
+
detailBuyPosition: "--vio-detail-buy-position",
|
|
178
|
+
detailBuyShadow: "--vio-detail-buy-shadow",
|
|
179
|
+
cartWidth: "--vio-cart-width",
|
|
180
|
+
checkoutWidth: "--vio-checkout-width",
|
|
181
|
+
confirmLogoHeight: "--vio-confirm-logo-height",
|
|
182
|
+
confirmTitleSize: "--vio-confirm-title-size",
|
|
183
|
+
confirmCheckBg: "--vio-confirm-check-bg",
|
|
184
|
+
redirectBg: "--vio-redirect-bg",
|
|
185
|
+
redirectSpinner: "--vio-redirect-spinner"
|
|
155
186
|
};
|
|
156
187
|
function applyVioTheme(overrides, target = typeof document !== "undefined" ? document.documentElement : null) {
|
|
157
188
|
if (!target) return;
|
|
@@ -705,6 +736,54 @@ __decorateClass([
|
|
|
705
736
|
__decorateClass([
|
|
706
737
|
state()
|
|
707
738
|
], VioProduct.prototype, "justAdded", 2);
|
|
739
|
+
function resolveSponsorIdentity(sponsorId, checkoutId) {
|
|
740
|
+
try {
|
|
741
|
+
if (typeof Vio.sponsorIdentity === "function") return Vio.sponsorIdentity(sponsorId, { checkoutId });
|
|
742
|
+
return sponsorIdentityFrom(Vio.bootstrapCache, sponsorId);
|
|
743
|
+
} catch {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function sponsorDetail(sponsorId, checkoutId) {
|
|
748
|
+
const sponsor = checkoutSponsorOf(resolveSponsorIdentity(sponsorId, checkoutId));
|
|
749
|
+
return sponsor ? { sponsor } : {};
|
|
750
|
+
}
|
|
751
|
+
var failedLogos = /* @__PURE__ */ new Set();
|
|
752
|
+
function usableLogo(identity) {
|
|
753
|
+
const url = identity?.logoUrl;
|
|
754
|
+
return url && !failedLogos.has(url) ? url : null;
|
|
755
|
+
}
|
|
756
|
+
function renderSponsorMark(host, identity, classes) {
|
|
757
|
+
if (!identity) return nothing;
|
|
758
|
+
const logo = usableLogo(identity);
|
|
759
|
+
if (logo) {
|
|
760
|
+
return html`<img
|
|
761
|
+
class=${classes.logo}
|
|
762
|
+
src=${logo}
|
|
763
|
+
alt=${identity.name}
|
|
764
|
+
decoding="async"
|
|
765
|
+
@error=${() => {
|
|
766
|
+
failedLogos.add(logo);
|
|
767
|
+
host.requestUpdate();
|
|
768
|
+
}}
|
|
769
|
+
/>`;
|
|
770
|
+
}
|
|
771
|
+
return identity.name ? html`<span class=${classes.name}>${identity.name}</span>` : nothing;
|
|
772
|
+
}
|
|
773
|
+
var INIT_WAIT_MS = 1e4;
|
|
774
|
+
async function whenBootstrapped() {
|
|
775
|
+
try {
|
|
776
|
+
if (Vio.bootstrapCache) return true;
|
|
777
|
+
if (!Configuration.isInitialized) await Configuration.whenReady(INIT_WAIT_MS);
|
|
778
|
+
if (!Configuration.isInitialized || typeof Vio.bootstrap !== "function") return false;
|
|
779
|
+
await Vio.bootstrap();
|
|
780
|
+
return !!Vio.bootstrapCache;
|
|
781
|
+
} catch {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/ui/components/vio-product-carousel.ts
|
|
708
787
|
var VioProductCarousel = class extends LitElement {
|
|
709
788
|
constructor() {
|
|
710
789
|
super(...arguments);
|
|
@@ -1077,7 +1156,7 @@ var VioProductCarousel = class extends LitElement {
|
|
|
1077
1156
|
if (this.fetched && this.fetched.length > 0) {
|
|
1078
1157
|
return html`${this.fetched.map(({ product: p, sponsorId }) => {
|
|
1079
1158
|
const img = primaryImageUrl(p) ?? "";
|
|
1080
|
-
const retailer =
|
|
1159
|
+
const retailer = resolveSponsorIdentity(sponsorId)?.name || p.brand || "";
|
|
1081
1160
|
return html`
|
|
1082
1161
|
<vio-product
|
|
1083
1162
|
product-id=${String(p.id)}
|
|
@@ -1195,11 +1274,101 @@ __decorateClass([
|
|
|
1195
1274
|
__decorateClass([
|
|
1196
1275
|
state()
|
|
1197
1276
|
], VioProductCarousel.prototype, "fetchError", 2);
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1277
|
+
|
|
1278
|
+
// src/ui/payment-redirect.ts
|
|
1279
|
+
var OVERLAY_CLASS = "vio-redirect-overlay";
|
|
1280
|
+
var STYLE_ID = "vio-redirect-overlay-style";
|
|
1281
|
+
var LABEL = { vipps: "Vipps", stripe: "Stripe", klarna: "Klarna" };
|
|
1282
|
+
function emit(name, detail, cancelable = false) {
|
|
1283
|
+
if (typeof document === "undefined") return true;
|
|
1284
|
+
return document.dispatchEvent(new CustomEvent(name, { detail, cancelable }));
|
|
1285
|
+
}
|
|
1286
|
+
function injectStyle() {
|
|
1287
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
1288
|
+
const style = document.createElement("style");
|
|
1289
|
+
style.id = STYLE_ID;
|
|
1290
|
+
style.textContent = `
|
|
1291
|
+
.${OVERLAY_CLASS} {
|
|
1292
|
+
position: fixed; inset: 0; z-index: 2147483000;
|
|
1293
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
|
|
1294
|
+
background: var(--vio-redirect-bg, rgba(255, 255, 255, 0.94));
|
|
1295
|
+
color: var(--vio-redirect-color, var(--vio-color-text, #0a0a0a));
|
|
1296
|
+
font: 600 16px/1.3 var(--vio-redirect-font, var(--vio-font-sans, -apple-system, sans-serif));
|
|
1297
|
+
opacity: 0; transition: opacity 0.18s ease; cursor: progress;
|
|
1298
|
+
}
|
|
1299
|
+
.${OVERLAY_CLASS}.is-shown { opacity: 1; }
|
|
1300
|
+
.${OVERLAY_CLASS} .vio-redirect-spinner {
|
|
1301
|
+
width: 36px; height: 36px; border-radius: 50%;
|
|
1302
|
+
border: 3px solid rgba(0, 0, 0, 0.1); border-top-color: var(--vio-redirect-spinner, #ff5b24);
|
|
1303
|
+
animation: vio-redirect-spin 0.8s linear infinite;
|
|
1304
|
+
}
|
|
1305
|
+
@keyframes vio-redirect-spin { to { transform: rotate(360deg); } }
|
|
1306
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1307
|
+
.${OVERLAY_CLASS} { transition: none; }
|
|
1308
|
+
.${OVERLAY_CLASS} .vio-redirect-spinner { animation: none; }
|
|
1309
|
+
}`;
|
|
1310
|
+
document.head.appendChild(style);
|
|
1311
|
+
}
|
|
1312
|
+
var current = null;
|
|
1313
|
+
function removeOverlay() {
|
|
1314
|
+
const el = current?.el;
|
|
1315
|
+
if (!el) return;
|
|
1316
|
+
el.classList.remove("is-shown");
|
|
1317
|
+
setTimeout(() => el.remove(), 200);
|
|
1318
|
+
current.el = null;
|
|
1319
|
+
}
|
|
1320
|
+
function onPageShow(e) {
|
|
1321
|
+
if (!e.persisted || !current || current.settled) return;
|
|
1322
|
+
current.settled = true;
|
|
1323
|
+
removeOverlay();
|
|
1324
|
+
emit("vio:payment-redirect-cancel", { ...current.detail, reason: "back" });
|
|
1325
|
+
current = null;
|
|
1326
|
+
}
|
|
1327
|
+
function beginPaymentRedirect(detail) {
|
|
1328
|
+
if (current && !current.settled) {
|
|
1329
|
+
current.settled = true;
|
|
1330
|
+
removeOverlay();
|
|
1331
|
+
}
|
|
1332
|
+
const hostTookOver = !emit("vio:payment-redirect-start", detail, true);
|
|
1333
|
+
const state6 = { el: null, detail, settled: false };
|
|
1334
|
+
current = state6;
|
|
1335
|
+
if (!hostTookOver && typeof document !== "undefined" && document.body) {
|
|
1336
|
+
injectStyle();
|
|
1337
|
+
const el = document.createElement("div");
|
|
1338
|
+
el.className = OVERLAY_CLASS;
|
|
1339
|
+
el.setAttribute("role", "status");
|
|
1340
|
+
el.setAttribute("aria-live", "polite");
|
|
1341
|
+
const spinner = document.createElement("div");
|
|
1342
|
+
spinner.className = "vio-redirect-spinner";
|
|
1343
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
1344
|
+
const text = document.createElement("div");
|
|
1345
|
+
text.className = "vio-redirect-text";
|
|
1346
|
+
text.textContent = `\xC5pner ${LABEL[detail.method] ?? detail.method} \u2026`;
|
|
1347
|
+
el.append(spinner, text);
|
|
1348
|
+
el.addEventListener("click", (e) => e.stopPropagation());
|
|
1349
|
+
document.body.appendChild(el);
|
|
1350
|
+
requestAnimationFrame(() => el.classList.add("is-shown"));
|
|
1351
|
+
state6.el = el;
|
|
1352
|
+
}
|
|
1353
|
+
if (typeof window !== "undefined") window.addEventListener("pageshow", onPageShow);
|
|
1354
|
+
const settle = (event, reason) => {
|
|
1355
|
+
if (state6.settled) return;
|
|
1356
|
+
state6.settled = true;
|
|
1357
|
+
if (current === state6) {
|
|
1358
|
+
removeOverlay();
|
|
1359
|
+
current = null;
|
|
1360
|
+
} else if (state6.el) {
|
|
1361
|
+
state6.el.remove();
|
|
1362
|
+
}
|
|
1363
|
+
emit(event, { ...detail, reason });
|
|
1364
|
+
};
|
|
1365
|
+
return {
|
|
1366
|
+
fail: (reason) => settle("vio:payment-redirect-error", reason),
|
|
1367
|
+
abort: (reason = "not-started") => settle("vio:payment-redirect-cancel", reason)
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
function returnCancelled(detail) {
|
|
1371
|
+
emit("vio:payment-redirect-cancel", { ...detail, reason: "cancelled" });
|
|
1203
1372
|
}
|
|
1204
1373
|
|
|
1205
1374
|
// src/core/checkout/method-taxonomy.ts
|
|
@@ -1409,6 +1578,207 @@ function isWithin(node, ancestor) {
|
|
|
1409
1578
|
return false;
|
|
1410
1579
|
}
|
|
1411
1580
|
|
|
1581
|
+
// src/ui/bottom-sheet.ts
|
|
1582
|
+
var PHONE = "(max-width: 600px)";
|
|
1583
|
+
var SLOP = 6;
|
|
1584
|
+
var CLOSE_DISTANCE = 120;
|
|
1585
|
+
var FLICK_VELOCITY = 0.5;
|
|
1586
|
+
var FLICK_MIN = 24;
|
|
1587
|
+
var CLICK_AFTER_DRAG_MS = 400;
|
|
1588
|
+
function isPhoneSheet() {
|
|
1589
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(PHONE).matches;
|
|
1590
|
+
}
|
|
1591
|
+
var BottomSheet = class {
|
|
1592
|
+
constructor(opts) {
|
|
1593
|
+
this.opts = opts;
|
|
1594
|
+
this.active = false;
|
|
1595
|
+
this.locked = false;
|
|
1596
|
+
this.drag = null;
|
|
1597
|
+
this.dragEndedAt = -Infinity;
|
|
1598
|
+
this.onPointerDown = (e) => {
|
|
1599
|
+
if (!e.isPrimary || e.pointerType === "mouse" && e.button !== 0) return;
|
|
1600
|
+
if (this.drag || !isPhoneSheet()) return;
|
|
1601
|
+
const sheet = this.opts.sheet();
|
|
1602
|
+
if (!sheet || !inZone(e, sheet, this.opts.dragZones)) return;
|
|
1603
|
+
this.drag = {
|
|
1604
|
+
pointerId: e.pointerId,
|
|
1605
|
+
startY: e.clientY,
|
|
1606
|
+
lastY: e.clientY,
|
|
1607
|
+
lastT: e.timeStamp,
|
|
1608
|
+
velocity: 0,
|
|
1609
|
+
moving: false,
|
|
1610
|
+
offset: 0
|
|
1611
|
+
};
|
|
1612
|
+
window.addEventListener("pointermove", this.onPointerMove);
|
|
1613
|
+
window.addEventListener("pointerup", this.onPointerUp);
|
|
1614
|
+
window.addEventListener("pointercancel", this.onPointerCancel);
|
|
1615
|
+
};
|
|
1616
|
+
this.onPointerMove = (e) => {
|
|
1617
|
+
const drag = this.drag;
|
|
1618
|
+
if (!drag || e.pointerId !== drag.pointerId) return;
|
|
1619
|
+
const dy = e.clientY - drag.startY;
|
|
1620
|
+
if (!drag.moving) {
|
|
1621
|
+
if (dy < -SLOP) return this.abandon();
|
|
1622
|
+
if (dy <= SLOP) return;
|
|
1623
|
+
drag.moving = true;
|
|
1624
|
+
const sheet2 = this.opts.sheet();
|
|
1625
|
+
if (sheet2) sheet2.style.transition = "none";
|
|
1626
|
+
const backdrop2 = this.opts.backdrop?.();
|
|
1627
|
+
if (backdrop2) backdrop2.style.transition = "none";
|
|
1628
|
+
}
|
|
1629
|
+
const dt = e.timeStamp - drag.lastT;
|
|
1630
|
+
if (dt > 0) drag.velocity = 0.8 * ((e.clientY - drag.lastY) / dt) + 0.2 * drag.velocity;
|
|
1631
|
+
drag.lastY = e.clientY;
|
|
1632
|
+
drag.lastT = e.timeStamp;
|
|
1633
|
+
drag.offset = Math.max(0, dy);
|
|
1634
|
+
const sheet = this.opts.sheet();
|
|
1635
|
+
if (sheet) sheet.style.transform = `translateY(${drag.offset}px)`;
|
|
1636
|
+
const backdrop = this.opts.backdrop?.();
|
|
1637
|
+
if (backdrop && sheet) {
|
|
1638
|
+
const height = sheet.getBoundingClientRect().height || window.innerHeight;
|
|
1639
|
+
backdrop.style.opacity = String(Math.max(0, 1 - drag.offset / height));
|
|
1640
|
+
}
|
|
1641
|
+
if (e.cancelable) e.preventDefault();
|
|
1642
|
+
};
|
|
1643
|
+
this.onPointerUp = (e) => {
|
|
1644
|
+
const drag = this.drag;
|
|
1645
|
+
if (!drag || e.pointerId !== drag.pointerId) return;
|
|
1646
|
+
this.stopTracking();
|
|
1647
|
+
this.drag = null;
|
|
1648
|
+
if (!drag.moving) return;
|
|
1649
|
+
this.dragEndedAt = performance.now();
|
|
1650
|
+
const sheet = this.opts.sheet();
|
|
1651
|
+
const height = sheet?.getBoundingClientRect().height || window.innerHeight;
|
|
1652
|
+
const far = drag.offset >= Math.min(CLOSE_DISTANCE, height / 4);
|
|
1653
|
+
const flick = drag.velocity >= FLICK_VELOCITY && drag.offset >= FLICK_MIN;
|
|
1654
|
+
const allowed = !this.opts.canClose || this.opts.canClose();
|
|
1655
|
+
this.release();
|
|
1656
|
+
if ((far || flick) && allowed) {
|
|
1657
|
+
if (sheet) sheet.style.transform = "translateY(100%)";
|
|
1658
|
+
this.opts.close();
|
|
1659
|
+
} else if (sheet) {
|
|
1660
|
+
sheet.style.transform = "";
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
this.onPointerCancel = (e) => {
|
|
1664
|
+
if (!this.drag || e.pointerId !== this.drag.pointerId) return;
|
|
1665
|
+
this.abandon();
|
|
1666
|
+
};
|
|
1667
|
+
/** The press that ended a drag also fires a click on ✕ or the grab bar: swallow it. */
|
|
1668
|
+
this.onClickCapture = (e) => {
|
|
1669
|
+
if (performance.now() - this.dragEndedAt > CLICK_AFTER_DRAG_MS) return;
|
|
1670
|
+
this.dragEndedAt = -Infinity;
|
|
1671
|
+
e.stopPropagation();
|
|
1672
|
+
e.preventDefault();
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
/** Call from `updated()` when `open` changed. Same value twice is a no-op. */
|
|
1676
|
+
update(open) {
|
|
1677
|
+
if (open === this.active) return;
|
|
1678
|
+
this.active = open;
|
|
1679
|
+
if (open) this.opened();
|
|
1680
|
+
else this.closed();
|
|
1681
|
+
}
|
|
1682
|
+
/** Call from `disconnectedCallback()`: a sheet torn down while open must not keep the page locked. */
|
|
1683
|
+
disconnect() {
|
|
1684
|
+
if (this.active) {
|
|
1685
|
+
this.active = false;
|
|
1686
|
+
this.closed();
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
opened() {
|
|
1690
|
+
if (isPhoneSheet()) {
|
|
1691
|
+
lockPageScroll();
|
|
1692
|
+
this.locked = true;
|
|
1693
|
+
}
|
|
1694
|
+
this.opts.host.addEventListener("pointerdown", this.onPointerDown);
|
|
1695
|
+
this.opts.host.addEventListener("click", this.onClickCapture, true);
|
|
1696
|
+
}
|
|
1697
|
+
closed() {
|
|
1698
|
+
this.opts.host.removeEventListener("pointerdown", this.onPointerDown);
|
|
1699
|
+
this.opts.host.removeEventListener("click", this.onClickCapture, true);
|
|
1700
|
+
this.stopTracking();
|
|
1701
|
+
this.drag = null;
|
|
1702
|
+
const sheet = this.opts.sheet();
|
|
1703
|
+
if (sheet) {
|
|
1704
|
+
sheet.style.transform = "";
|
|
1705
|
+
sheet.style.transition = "";
|
|
1706
|
+
}
|
|
1707
|
+
const backdrop = this.opts.backdrop?.();
|
|
1708
|
+
if (backdrop) {
|
|
1709
|
+
backdrop.style.opacity = "";
|
|
1710
|
+
backdrop.style.transition = "";
|
|
1711
|
+
}
|
|
1712
|
+
if (this.locked) {
|
|
1713
|
+
this.locked = false;
|
|
1714
|
+
unlockPageScroll();
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
/** Give up on this press and put the sheet back where it was. */
|
|
1718
|
+
abandon() {
|
|
1719
|
+
const moving = this.drag?.moving;
|
|
1720
|
+
this.stopTracking();
|
|
1721
|
+
this.drag = null;
|
|
1722
|
+
if (!moving) return;
|
|
1723
|
+
this.release();
|
|
1724
|
+
const sheet = this.opts.sheet();
|
|
1725
|
+
if (sheet) sheet.style.transform = "";
|
|
1726
|
+
}
|
|
1727
|
+
/** Hand the sheet and backdrop back to their CSS transitions. */
|
|
1728
|
+
release() {
|
|
1729
|
+
const sheet = this.opts.sheet();
|
|
1730
|
+
if (sheet) sheet.style.transition = "";
|
|
1731
|
+
const backdrop = this.opts.backdrop?.();
|
|
1732
|
+
if (backdrop) {
|
|
1733
|
+
backdrop.style.transition = "";
|
|
1734
|
+
backdrop.style.opacity = "";
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
stopTracking() {
|
|
1738
|
+
window.removeEventListener("pointermove", this.onPointerMove);
|
|
1739
|
+
window.removeEventListener("pointerup", this.onPointerUp);
|
|
1740
|
+
window.removeEventListener("pointercancel", this.onPointerCancel);
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
function inZone(e, sheet, zones) {
|
|
1744
|
+
for (const node of e.composedPath()) {
|
|
1745
|
+
if (node === sheet) return false;
|
|
1746
|
+
if (node instanceof Element && node.matches(zones)) return sheet.contains(node);
|
|
1747
|
+
}
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1750
|
+
var locks = 0;
|
|
1751
|
+
var saved = null;
|
|
1752
|
+
var LOCKED = ["position", "top", "left", "right", "width", "overflow"];
|
|
1753
|
+
function lockPageScroll() {
|
|
1754
|
+
if (typeof document === "undefined") return;
|
|
1755
|
+
locks += 1;
|
|
1756
|
+
if (locks > 1) return;
|
|
1757
|
+
const body = document.body;
|
|
1758
|
+
const scrollY = window.scrollY || document.documentElement.scrollTop || 0;
|
|
1759
|
+
saved = { scrollY, body: {} };
|
|
1760
|
+
for (const p of LOCKED) saved.body[p] = body.style[p];
|
|
1761
|
+
body.style.position = "fixed";
|
|
1762
|
+
body.style.top = `-${scrollY}px`;
|
|
1763
|
+
body.style.left = "0";
|
|
1764
|
+
body.style.right = "0";
|
|
1765
|
+
body.style.width = "100%";
|
|
1766
|
+
body.style.overflow = "hidden";
|
|
1767
|
+
}
|
|
1768
|
+
function unlockPageScroll() {
|
|
1769
|
+
if (typeof document === "undefined" || locks === 0) return;
|
|
1770
|
+
locks -= 1;
|
|
1771
|
+
if (locks > 0 || !saved) return;
|
|
1772
|
+
const { scrollY, body } = saved;
|
|
1773
|
+
saved = null;
|
|
1774
|
+
for (const p of LOCKED) document.body.style[p] = body[p] ?? "";
|
|
1775
|
+
const root = document.documentElement;
|
|
1776
|
+
const behavior = root.style.scrollBehavior;
|
|
1777
|
+
root.style.scrollBehavior = "auto";
|
|
1778
|
+
window.scrollTo(0, scrollY);
|
|
1779
|
+
root.style.scrollBehavior = behavior;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1412
1782
|
// src/ui/components/vio-product-detail.ts
|
|
1413
1783
|
var STRIPE_LOGO_SRC = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
1414
1784
|
'<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>'
|
|
@@ -1443,6 +1813,10 @@ var VioProductDetail = class extends LitElement {
|
|
|
1443
1813
|
this.selectedOptions = {};
|
|
1444
1814
|
this.quantity = 1;
|
|
1445
1815
|
this.activeImageIndex = 0;
|
|
1816
|
+
this.thumbsOverflow = false;
|
|
1817
|
+
this.thumbsAtStart = true;
|
|
1818
|
+
this.thumbsAtEnd = false;
|
|
1819
|
+
this.logoSquare = false;
|
|
1446
1820
|
this.adding = false;
|
|
1447
1821
|
this.applePayOk = false;
|
|
1448
1822
|
this.availableMethods = null;
|
|
@@ -1460,6 +1834,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
1460
1834
|
container: () => this.renderRoot.querySelector(".modal"),
|
|
1461
1835
|
close: () => this.close()
|
|
1462
1836
|
});
|
|
1837
|
+
/** Phones: the page behind is locked, and the sheet closes by pulling the bar or the top bar down. */
|
|
1838
|
+
this.bottomSheet = new BottomSheet({
|
|
1839
|
+
host: this,
|
|
1840
|
+
sheet: () => this.renderRoot.querySelector(".modal"),
|
|
1841
|
+
backdrop: () => this.renderRoot.querySelector(".backdrop"),
|
|
1842
|
+
dragZones: ".handle, .topbar",
|
|
1843
|
+
close: () => this.close()
|
|
1844
|
+
});
|
|
1463
1845
|
this.boundProductClick = (e) => {
|
|
1464
1846
|
if (!this.autoOpen) return;
|
|
1465
1847
|
const ev = e;
|
|
@@ -1487,6 +1869,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
1487
1869
|
this.show();
|
|
1488
1870
|
void this.fetchProduct();
|
|
1489
1871
|
};
|
|
1872
|
+
this.onThumbsScroll = () => {
|
|
1873
|
+
const row = this.thumbRow();
|
|
1874
|
+
if (!row) return;
|
|
1875
|
+
const atStart = row.scrollLeft <= 1;
|
|
1876
|
+
const atEnd = row.scrollLeft + row.clientWidth >= row.scrollWidth - 1;
|
|
1877
|
+
if (atStart !== this.thumbsAtStart) this.thumbsAtStart = atStart;
|
|
1878
|
+
if (atEnd !== this.thumbsAtEnd) this.thumbsAtEnd = atEnd;
|
|
1879
|
+
};
|
|
1490
1880
|
}
|
|
1491
1881
|
/** The card's context applies to the product it opened — not to one the host opened by hand later. */
|
|
1492
1882
|
get fromCard() {
|
|
@@ -1574,7 +1964,10 @@ var VioProductDetail = class extends LitElement {
|
|
|
1574
1964
|
height: 92vh;
|
|
1575
1965
|
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
1576
1966
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
1967
|
+
overscroll-behavior: contain;
|
|
1577
1968
|
}
|
|
1969
|
+
/* Pulled down to close (bottom-sheet.ts): the browser must not take the gesture for a scroll. */
|
|
1970
|
+
.handle, .topbar { touch-action: none; }
|
|
1578
1971
|
.handle {
|
|
1579
1972
|
display: flex;
|
|
1580
1973
|
justify-content: center;
|
|
@@ -1595,8 +1988,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1595
1988
|
@media (min-width: 601px) {
|
|
1596
1989
|
.modal {
|
|
1597
1990
|
inset: 0 0 0 auto;
|
|
1598
|
-
width: min(440px, 100%);
|
|
1599
|
-
max-width: 440px;
|
|
1991
|
+
width: min(var(--vio-detail-width, 440px), 100%);
|
|
1992
|
+
max-width: var(--vio-detail-width, 440px);
|
|
1600
1993
|
border-radius: 0;
|
|
1601
1994
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.1);
|
|
1602
1995
|
transform: translateX(100%);
|
|
@@ -1610,29 +2003,59 @@ var VioProductDetail = class extends LitElement {
|
|
|
1610
2003
|
max-width: none;
|
|
1611
2004
|
grid-template-columns: 1fr;
|
|
1612
2005
|
gap: 20px;
|
|
1613
|
-
padding:
|
|
2006
|
+
padding: var(--vio-detail-padding-top, 0px) var(--vio-detail-padding-x, var(--vio-space-lg, 24px)) 40px;
|
|
1614
2007
|
}
|
|
1615
|
-
.modal .name { font-size: 26px; line-height: 1.2; }
|
|
1616
|
-
.modal .gallery-main { width:
|
|
2008
|
+
.modal .name { font-size: var(--vio-detail-title-size, 26px); line-height: 1.2; }
|
|
2009
|
+
.modal .gallery-main { width: auto; }
|
|
1617
2010
|
}
|
|
1618
2011
|
|
|
1619
2012
|
.topbar {
|
|
1620
2013
|
display: flex;
|
|
1621
2014
|
justify-content: space-between;
|
|
1622
2015
|
align-items: center;
|
|
1623
|
-
|
|
2016
|
+
gap: 16px;
|
|
2017
|
+
min-height: var(--vio-detail-topbar-height, 64px);
|
|
2018
|
+
box-sizing: border-box;
|
|
2019
|
+
padding: 10px var(--vio-detail-padding-x, var(--vio-space-lg, 24px));
|
|
1624
2020
|
position: sticky;
|
|
1625
2021
|
top: 0;
|
|
1626
2022
|
background: var(--vio-color-surface, #fff);
|
|
1627
2023
|
border-bottom: 1px solid var(--vio-color-border, #e5e5e5);
|
|
1628
2024
|
z-index: 1;
|
|
1629
2025
|
}
|
|
1630
|
-
.
|
|
1631
|
-
|
|
2026
|
+
/* Who sells it, top left beside the close: the sponsor's logo, else its name. */
|
|
2027
|
+
.topbar-sponsor { flex: 1 1 auto; display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
2028
|
+
/* The logo box clips, so --vio-detail-logo-zoom can crop the white a
|
|
2029
|
+
square file carries around its mark. */
|
|
2030
|
+
.topbar-logo-box {
|
|
2031
|
+
flex: 0 0 auto;
|
|
2032
|
+
display: flex;
|
|
2033
|
+
align-items: center;
|
|
2034
|
+
height: var(--vio-detail-logo-height, 44px);
|
|
2035
|
+
max-width: var(--vio-detail-logo-max-width, 220px);
|
|
2036
|
+
overflow: hidden;
|
|
2037
|
+
}
|
|
2038
|
+
.topbar-logo {
|
|
2039
|
+
display: block;
|
|
2040
|
+
width: auto;
|
|
2041
|
+
height: 100%;
|
|
2042
|
+
max-width: var(--vio-detail-logo-max-width, 220px);
|
|
2043
|
+
object-fit: contain;
|
|
2044
|
+
object-position: left center;
|
|
2045
|
+
transform: scale(var(--vio-detail-logo-zoom, 1));
|
|
2046
|
+
transform-origin: center;
|
|
2047
|
+
}
|
|
2048
|
+
/* A square (or near) logo is a mark, not a wordmark: say whose it is. */
|
|
2049
|
+
.topbar-sponsor:not(.is-square) .topbar-mark-name { display: none; }
|
|
2050
|
+
.topbar-name {
|
|
2051
|
+
font-size: var(--vio-detail-logo-name-size, 13px);
|
|
1632
2052
|
text-transform: uppercase;
|
|
1633
2053
|
letter-spacing: var(--vio-tracking-label, 0.1em);
|
|
1634
2054
|
color: var(--vio-color-text-secondary, #666);
|
|
1635
|
-
font-weight:
|
|
2055
|
+
font-weight: 600;
|
|
2056
|
+
white-space: nowrap;
|
|
2057
|
+
overflow: hidden;
|
|
2058
|
+
text-overflow: ellipsis;
|
|
1636
2059
|
}
|
|
1637
2060
|
.close {
|
|
1638
2061
|
background: none;
|
|
@@ -1658,51 +2081,110 @@ var VioProductDetail = class extends LitElement {
|
|
|
1658
2081
|
gap: 64px;
|
|
1659
2082
|
}
|
|
1660
2083
|
@media (max-width: 800px) {
|
|
1661
|
-
.body {
|
|
2084
|
+
.body {
|
|
2085
|
+
grid-template-columns: 1fr;
|
|
2086
|
+
gap: 24px;
|
|
2087
|
+
padding: var(--vio-detail-padding-top, 0px) var(--vio-detail-padding-x, var(--vio-space-lg, 24px)) 64px;
|
|
2088
|
+
}
|
|
1662
2089
|
}
|
|
1663
2090
|
|
|
1664
|
-
/* Gallery
|
|
1665
|
-
|
|
2091
|
+
/* Gallery. One fixed proportion for every image (4:5 by default) that the
|
|
2092
|
+
image fills (cover): no white bars, and the panel never jumps between
|
|
2093
|
+
photos of different shapes (Angelo, 2026-10-22). Its side margin is
|
|
2094
|
+
--vio-detail-image-inset (the panel's padding by default; 0 = edge to
|
|
2095
|
+
edge). Fit, proportion, a height cap and a zoom (to crop a white margin
|
|
2096
|
+
baked into the file) are the host's to set. */
|
|
2097
|
+
.gallery { display: flex; flex-direction: column; gap: var(--vio-detail-gallery-gap, 12px); min-width: 0; }
|
|
1666
2098
|
.gallery-main {
|
|
1667
|
-
|
|
1668
|
-
|
|
2099
|
+
position: relative;
|
|
2100
|
+
margin-inline: calc(
|
|
2101
|
+
var(--vio-detail-image-inset, var(--vio-detail-padding-x, var(--vio-space-lg, 24px))) -
|
|
2102
|
+
var(--vio-detail-padding-x, var(--vio-space-lg, 24px))
|
|
2103
|
+
);
|
|
2104
|
+
aspect-ratio: var(--vio-detail-image-aspect, 4 / 5);
|
|
2105
|
+
max-height: var(--vio-detail-image-max-height, 85vh);
|
|
2106
|
+
background: var(--vio-detail-image-bg, var(--vio-color-surface-muted, #f2f2f2));
|
|
2107
|
+
border-radius: var(--vio-detail-image-radius, 0);
|
|
1669
2108
|
display: flex;
|
|
1670
2109
|
align-items: center;
|
|
1671
2110
|
justify-content: center;
|
|
1672
2111
|
overflow: hidden;
|
|
1673
2112
|
}
|
|
1674
2113
|
.gallery-main img {
|
|
2114
|
+
display: block;
|
|
1675
2115
|
width: 100%;
|
|
1676
2116
|
height: 100%;
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2117
|
+
max-height: var(--vio-detail-image-max-height, 85vh);
|
|
2118
|
+
object-fit: var(--vio-detail-image-fit, cover);
|
|
2119
|
+
object-position: var(--vio-detail-image-position, center);
|
|
2120
|
+
transform: scale(var(--vio-detail-image-zoom, 1));
|
|
2121
|
+
}
|
|
2122
|
+
/* Thumbnails: one row, never a second line — swipe on touch, arrows with a
|
|
2123
|
+
mouse; the selected one scrolls into view by itself. */
|
|
2124
|
+
.thumbs { position: relative; min-width: 0; }
|
|
1681
2125
|
.gallery-thumbs {
|
|
1682
2126
|
display: flex;
|
|
1683
|
-
|
|
1684
|
-
|
|
2127
|
+
flex-wrap: nowrap;
|
|
2128
|
+
gap: var(--vio-detail-thumb-gap, 8px);
|
|
2129
|
+
overflow-x: auto;
|
|
2130
|
+
scroll-snap-type: x proximity;
|
|
2131
|
+
scroll-behavior: smooth;
|
|
2132
|
+
scrollbar-width: none;
|
|
2133
|
+
-webkit-overflow-scrolling: touch;
|
|
1685
2134
|
}
|
|
2135
|
+
.gallery-thumbs::-webkit-scrollbar { display: none; }
|
|
1686
2136
|
.gallery-thumb {
|
|
1687
|
-
|
|
1688
|
-
|
|
2137
|
+
flex: 0 0 auto;
|
|
2138
|
+
width: var(--vio-detail-thumb-size, 64px);
|
|
2139
|
+
height: var(--vio-detail-thumb-size, 64px);
|
|
1689
2140
|
background: var(--vio-color-surface-muted, #f2f2f2);
|
|
1690
|
-
border: 1px solid transparent;
|
|
2141
|
+
border: var(--vio-detail-thumb-border-width, 1px) solid transparent;
|
|
2142
|
+
border-radius: var(--vio-detail-thumb-radius, 0);
|
|
1691
2143
|
cursor: pointer;
|
|
1692
2144
|
padding: 0;
|
|
1693
2145
|
overflow: hidden;
|
|
2146
|
+
scroll-snap-align: start;
|
|
2147
|
+
box-sizing: border-box;
|
|
2148
|
+
opacity: var(--vio-detail-thumb-opacity, 1);
|
|
1694
2149
|
}
|
|
1695
2150
|
.gallery-thumb img {
|
|
2151
|
+
display: block;
|
|
1696
2152
|
width: 100%;
|
|
1697
2153
|
height: 100%;
|
|
1698
|
-
object-fit:
|
|
1699
|
-
|
|
1700
|
-
|
|
2154
|
+
object-fit: var(--vio-detail-thumb-fit, cover);
|
|
2155
|
+
}
|
|
2156
|
+
.gallery-thumb.active {
|
|
2157
|
+
border-color: var(--vio-detail-thumb-active-color, var(--vio-color-text, #0a0a0a));
|
|
2158
|
+
opacity: 1;
|
|
2159
|
+
}
|
|
2160
|
+
.thumbs-arrow {
|
|
2161
|
+
display: none;
|
|
2162
|
+
position: absolute;
|
|
2163
|
+
top: 50%;
|
|
2164
|
+
transform: translateY(-50%);
|
|
2165
|
+
z-index: 1;
|
|
2166
|
+
width: 28px;
|
|
2167
|
+
height: 28px;
|
|
2168
|
+
border-radius: 50%;
|
|
2169
|
+
border: 1px solid var(--vio-color-border, #e5e5e5);
|
|
2170
|
+
background: var(--vio-color-surface, #fff);
|
|
2171
|
+
color: var(--vio-color-text, #0a0a0a);
|
|
2172
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
2173
|
+
cursor: pointer;
|
|
2174
|
+
align-items: center;
|
|
2175
|
+
justify-content: center;
|
|
2176
|
+
padding: 0;
|
|
2177
|
+
font: 16px/1 sans-serif;
|
|
2178
|
+
}
|
|
2179
|
+
.thumbs-arrow.prev { left: -6px; }
|
|
2180
|
+
.thumbs-arrow.next { right: -6px; }
|
|
2181
|
+
.thumbs-arrow:disabled { visibility: hidden; }
|
|
2182
|
+
@media (hover: hover) and (pointer: fine) {
|
|
2183
|
+
.thumbs.overflows .thumbs-arrow { display: flex; }
|
|
1701
2184
|
}
|
|
1702
|
-
.gallery-thumb.active { border-color: var(--vio-color-text, #0a0a0a); }
|
|
1703
2185
|
|
|
1704
2186
|
/* Info column */
|
|
1705
|
-
.info { display: flex; flex-direction: column; gap:
|
|
2187
|
+
.info { display: flex; flex-direction: column; gap: var(--vio-detail-info-gap, 20px); min-width: 0; }
|
|
1706
2188
|
.brand {
|
|
1707
2189
|
font-size: 12px;
|
|
1708
2190
|
text-transform: uppercase;
|
|
@@ -1712,8 +2194,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1712
2194
|
}
|
|
1713
2195
|
.name {
|
|
1714
2196
|
font-family: var(--vio-font-serif, Georgia, serif);
|
|
1715
|
-
font-size: clamp(28px, 4vw, 40px);
|
|
1716
|
-
font-weight: 400;
|
|
2197
|
+
font-size: var(--vio-detail-title-size, clamp(28px, 4vw, 40px));
|
|
2198
|
+
font-weight: var(--vio-detail-title-weight, 400);
|
|
1717
2199
|
line-height: 1.15;
|
|
1718
2200
|
margin: 0;
|
|
1719
2201
|
letter-spacing: -0.01em;
|
|
@@ -1724,8 +2206,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1724
2206
|
gap: 16px;
|
|
1725
2207
|
}
|
|
1726
2208
|
.price {
|
|
1727
|
-
font-size: 24px;
|
|
1728
|
-
font-weight: 700;
|
|
2209
|
+
font-size: var(--vio-detail-price-size, 24px);
|
|
2210
|
+
font-weight: var(--vio-detail-price-weight, 700);
|
|
1729
2211
|
}
|
|
1730
2212
|
.compare-at {
|
|
1731
2213
|
font-size: 16px;
|
|
@@ -1743,7 +2225,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
1743
2225
|
.stock.out-of-stock { color: var(--vio-color-text-tertiary, #999); }
|
|
1744
2226
|
|
|
1745
2227
|
/* Option pickers */
|
|
1746
|
-
.option-group { display: flex; flex-direction: column; gap:
|
|
2228
|
+
.option-group { display: flex; flex-direction: column; gap: 6px; }
|
|
1747
2229
|
.option-label {
|
|
1748
2230
|
font-size: 11px;
|
|
1749
2231
|
text-transform: uppercase;
|
|
@@ -1751,14 +2233,28 @@ var VioProductDetail = class extends LitElement {
|
|
|
1751
2233
|
color: var(--vio-color-text-secondary, #666);
|
|
1752
2234
|
font-weight: 500;
|
|
1753
2235
|
}
|
|
1754
|
-
|
|
2236
|
+
/* Sizes: compact, still comfortable to tap. wrap → several lines;
|
|
2237
|
+
nowrap → one line that scrolls sideways. */
|
|
2238
|
+
.option-values {
|
|
2239
|
+
display: flex;
|
|
2240
|
+
flex-wrap: var(--vio-detail-option-wrap, wrap);
|
|
2241
|
+
gap: var(--vio-detail-option-gap, 6px);
|
|
2242
|
+
overflow-x: auto;
|
|
2243
|
+
scrollbar-width: none;
|
|
2244
|
+
}
|
|
2245
|
+
.option-values::-webkit-scrollbar { display: none; }
|
|
1755
2246
|
.option-pill {
|
|
1756
|
-
|
|
2247
|
+
flex: 0 0 auto;
|
|
2248
|
+
box-sizing: border-box;
|
|
2249
|
+
min-height: var(--vio-detail-option-height, 36px);
|
|
2250
|
+
min-width: var(--vio-detail-option-height, 36px);
|
|
2251
|
+
padding: 0 var(--vio-detail-option-padding-x, 12px);
|
|
2252
|
+
border-radius: var(--vio-detail-option-radius, 0);
|
|
1757
2253
|
background: #fff;
|
|
1758
2254
|
border: 1px solid var(--vio-color-border, #e5e5e5);
|
|
1759
2255
|
cursor: pointer;
|
|
1760
2256
|
font-family: inherit;
|
|
1761
|
-
font-size: 13px;
|
|
2257
|
+
font-size: var(--vio-detail-option-font-size, 13px);
|
|
1762
2258
|
font-weight: 500;
|
|
1763
2259
|
color: inherit;
|
|
1764
2260
|
transition: border-color 0.15s, background 0.15s;
|
|
@@ -1777,6 +2273,34 @@ var VioProductDetail = class extends LitElement {
|
|
|
1777
2273
|
}
|
|
1778
2274
|
.option-pill.unavailable:hover { border-color: var(--vio-color-border, #e5e5e5); }
|
|
1779
2275
|
|
|
2276
|
+
/* The buy zone: Vipps first, then quantity + add to cart, then the other
|
|
2277
|
+
ways to pay. --vio-detail-buy-position: sticky pins it to the bottom of
|
|
2278
|
+
the panel while the shopper scrolls (mobile, mostly). */
|
|
2279
|
+
.buy {
|
|
2280
|
+
display: flex;
|
|
2281
|
+
flex-direction: column;
|
|
2282
|
+
gap: var(--vio-detail-buy-gap, 10px);
|
|
2283
|
+
position: var(--vio-detail-buy-position, static);
|
|
2284
|
+
bottom: 0;
|
|
2285
|
+
z-index: 2;
|
|
2286
|
+
background: var(--vio-detail-buy-bg, var(--vio-color-surface, #fff));
|
|
2287
|
+
padding: var(--vio-detail-buy-padding, 0);
|
|
2288
|
+
box-shadow: var(--vio-detail-buy-shadow, none);
|
|
2289
|
+
}
|
|
2290
|
+
.buy > * { margin-top: 0 !important; }
|
|
2291
|
+
/* Under the buttons: who the shopper is buying from. */
|
|
2292
|
+
.seller-note {
|
|
2293
|
+
margin: 2px 0 0;
|
|
2294
|
+
font-size: var(--vio-detail-seller-note-size, 12px);
|
|
2295
|
+
line-height: 1.4;
|
|
2296
|
+
color: var(--vio-color-text-secondary, #666);
|
|
2297
|
+
text-align: var(--vio-detail-seller-note-align, center);
|
|
2298
|
+
}
|
|
2299
|
+
.seller-note strong { font-weight: 600; color: var(--vio-color-text, #0a0a0a); }
|
|
2300
|
+
.seller-links { display: block; margin-top: 2px; }
|
|
2301
|
+
.seller-links a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
|
|
2302
|
+
.seller-links a + a::before { content: '·'; display: inline-block; margin: 0 6px; text-decoration: none; }
|
|
2303
|
+
|
|
1780
2304
|
/* Qty + CTA row */
|
|
1781
2305
|
.qty-cta-row {
|
|
1782
2306
|
display: grid;
|
|
@@ -1791,7 +2315,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
1791
2315
|
}
|
|
1792
2316
|
.qty-btn {
|
|
1793
2317
|
width: 44px;
|
|
1794
|
-
height:
|
|
2318
|
+
height: var(--vio-detail-button-height, 52px);
|
|
1795
2319
|
background: #fff;
|
|
1796
2320
|
border: none;
|
|
1797
2321
|
cursor: pointer;
|
|
@@ -1809,7 +2333,9 @@ var VioProductDetail = class extends LitElement {
|
|
|
1809
2333
|
background: var(--vio-color-text, #0a0a0a);
|
|
1810
2334
|
color: var(--vio-color-text-on-primary, #fff);
|
|
1811
2335
|
border: none;
|
|
1812
|
-
|
|
2336
|
+
min-height: var(--vio-detail-button-height, 52px);
|
|
2337
|
+
border-radius: var(--vio-detail-button-radius, 0);
|
|
2338
|
+
padding: 0 24px;
|
|
1813
2339
|
font-size: 12px;
|
|
1814
2340
|
text-transform: uppercase;
|
|
1815
2341
|
letter-spacing: var(--vio-tracking-label, 0.1em);
|
|
@@ -1919,19 +2445,49 @@ var VioProductDetail = class extends LitElement {
|
|
|
1919
2445
|
.buy-applepay:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1920
2446
|
|
|
1921
2447
|
/* Description */
|
|
2448
|
+
/* The description folds: a tap opens it, so it does not take the page. */
|
|
2449
|
+
.description-block { border-top: 1px solid var(--vio-color-border, #e5e5e5); }
|
|
2450
|
+
.description-block > summary {
|
|
2451
|
+
list-style: none;
|
|
2452
|
+
cursor: pointer;
|
|
2453
|
+
display: flex;
|
|
2454
|
+
align-items: center;
|
|
2455
|
+
justify-content: space-between;
|
|
2456
|
+
padding: 14px 0;
|
|
2457
|
+
font-size: 11px;
|
|
2458
|
+
text-transform: uppercase;
|
|
2459
|
+
letter-spacing: var(--vio-tracking-label, 0.1em);
|
|
2460
|
+
color: var(--vio-color-text-secondary, #666);
|
|
2461
|
+
font-weight: 600;
|
|
2462
|
+
}
|
|
2463
|
+
.description-block > summary::-webkit-details-marker { display: none; }
|
|
2464
|
+
.description-block > summary::after {
|
|
2465
|
+
content: '';
|
|
2466
|
+
width: 8px;
|
|
2467
|
+
height: 8px;
|
|
2468
|
+
border-right: 1.5px solid currentColor;
|
|
2469
|
+
border-bottom: 1.5px solid currentColor;
|
|
2470
|
+
transform: rotate(45deg) translateY(-2px);
|
|
2471
|
+
transition: transform 0.15s ease;
|
|
2472
|
+
}
|
|
2473
|
+
.description-block[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
|
|
1922
2474
|
.description {
|
|
1923
2475
|
font-size: 14px;
|
|
1924
2476
|
line-height: 1.6;
|
|
1925
2477
|
color: var(--vio-color-text-secondary, #444);
|
|
1926
|
-
|
|
2478
|
+
padding-bottom: 14px;
|
|
2479
|
+
white-space: pre-line;
|
|
1927
2480
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2481
|
+
/* Desktop: the description above, the buy zone at the bottom of the panel
|
|
2482
|
+
(sticky). Phones keep the order as it is: buy, then the description. */
|
|
2483
|
+
@media (min-width: 601px) {
|
|
2484
|
+
.description-block { order: 1; }
|
|
2485
|
+
.buy {
|
|
2486
|
+
order: 2;
|
|
2487
|
+
position: var(--vio-detail-buy-position, sticky);
|
|
2488
|
+
padding: var(--vio-detail-buy-padding, 12px 0 16px);
|
|
2489
|
+
box-shadow: var(--vio-detail-buy-shadow, 0 -1px 0 var(--vio-color-border, #e5e5e5));
|
|
2490
|
+
}
|
|
1935
2491
|
}
|
|
1936
2492
|
|
|
1937
2493
|
/* Skeleton loader: shimmer placeholders mirroring the product layout
|
|
@@ -1987,14 +2543,47 @@ var VioProductDetail = class extends LitElement {
|
|
|
1987
2543
|
disconnectedCallback() {
|
|
1988
2544
|
document.removeEventListener("vio:product-click", this.boundProductClick);
|
|
1989
2545
|
this.slotRegistration.clear();
|
|
2546
|
+
this.bottomSheet.disconnect();
|
|
1990
2547
|
super.disconnectedCallback();
|
|
1991
2548
|
}
|
|
1992
2549
|
updated(changed) {
|
|
1993
|
-
if (changed.has("open"))
|
|
2550
|
+
if (changed.has("open")) {
|
|
2551
|
+
this.dialogFocus.update(this.open);
|
|
2552
|
+
this.bottomSheet.update(this.open);
|
|
2553
|
+
}
|
|
2554
|
+
if (changed.has("product") || changed.has("open")) this.measureThumbs();
|
|
2555
|
+
if (changed.has("activeImageIndex")) this.revealActiveThumb();
|
|
2556
|
+
this.measureLogo();
|
|
1994
2557
|
if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName") || changed.has("vioCampaign")) {
|
|
1995
2558
|
this.slotRegistration.sync(this.slotAttributes());
|
|
1996
2559
|
}
|
|
1997
2560
|
}
|
|
2561
|
+
thumbRow() {
|
|
2562
|
+
return this.renderRoot.querySelector(".gallery-thumbs");
|
|
2563
|
+
}
|
|
2564
|
+
/** Arrows only when the row overflows; each one off at its own end. */
|
|
2565
|
+
measureThumbs() {
|
|
2566
|
+
const row = this.thumbRow();
|
|
2567
|
+
if (!row) return;
|
|
2568
|
+
const overflow = row.scrollWidth > row.clientWidth + 1;
|
|
2569
|
+
if (overflow !== this.thumbsOverflow) this.thumbsOverflow = overflow;
|
|
2570
|
+
this.onThumbsScroll();
|
|
2571
|
+
}
|
|
2572
|
+
/** One arrow press moves most of a visible row. */
|
|
2573
|
+
scrollThumbs(direction) {
|
|
2574
|
+
const row = this.thumbRow();
|
|
2575
|
+
if (row) row.scrollBy({ left: direction * row.clientWidth * 0.8 });
|
|
2576
|
+
}
|
|
2577
|
+
/** The selected thumbnail is always visible: the row scrolls to it, the page never does. */
|
|
2578
|
+
revealActiveThumb() {
|
|
2579
|
+
const row = this.thumbRow();
|
|
2580
|
+
const thumb = row?.querySelectorAll(".gallery-thumb")[this.activeImageIndex];
|
|
2581
|
+
if (!row || !thumb) return;
|
|
2582
|
+
const left = thumb.offsetLeft - row.offsetLeft;
|
|
2583
|
+
if (left < row.scrollLeft || left + thumb.offsetWidth > row.scrollLeft + row.clientWidth) {
|
|
2584
|
+
row.scrollTo({ left: Math.max(0, left - (row.clientWidth - thumb.offsetWidth) / 2) });
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
1998
2587
|
show() {
|
|
1999
2588
|
this.open = true;
|
|
2000
2589
|
}
|
|
@@ -2384,9 +2973,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2384
2973
|
addCurrentToCart() {
|
|
2385
2974
|
if (!this.product) return void 0;
|
|
2386
2975
|
const variant = this.selectedVariant();
|
|
2387
|
-
const
|
|
2388
|
-
const secondaries = Vio.bootstrapCache?.secondarySponsors ?? [];
|
|
2389
|
-
const sponsorName = sponsor?.id === this.sponsorId ? sponsor.name : secondaries.find((s) => s.id === this.sponsorId)?.name;
|
|
2976
|
+
const sponsorName = resolveSponsorIdentity(this.sponsorId)?.name || void 0;
|
|
2390
2977
|
const { campaignId, slot, contentId, componentTemplateId, contentUrl, contentTitle } = this.attribution();
|
|
2391
2978
|
Vio.cart.addProduct({
|
|
2392
2979
|
product: this.product,
|
|
@@ -2437,7 +3024,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2437
3024
|
new CustomEvent("vio:checkout-open", {
|
|
2438
3025
|
bubbles: true,
|
|
2439
3026
|
composed: true,
|
|
2440
|
-
detail: { sponsorId: this.sponsorId, paymentMethod: "klarna", express: true, ...this.attribution() }
|
|
3027
|
+
detail: { sponsorId: this.sponsorId, paymentMethod: "klarna", express: true, ...this.attribution(), ...sponsorDetail(this.sponsorId) }
|
|
2441
3028
|
})
|
|
2442
3029
|
);
|
|
2443
3030
|
} finally {
|
|
@@ -2467,7 +3054,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2467
3054
|
new CustomEvent("vio:checkout-open", {
|
|
2468
3055
|
bubbles: true,
|
|
2469
3056
|
composed: true,
|
|
2470
|
-
detail: { sponsorId: this.sponsorId, express: false, ...this.attribution() }
|
|
3057
|
+
detail: { sponsorId: this.sponsorId, express: false, ...this.attribution(), ...sponsorDetail(this.sponsorId) }
|
|
2471
3058
|
})
|
|
2472
3059
|
);
|
|
2473
3060
|
}
|
|
@@ -2483,27 +3070,33 @@ var VioProductDetail = class extends LitElement {
|
|
|
2483
3070
|
}
|
|
2484
3071
|
buyWithVipps() {
|
|
2485
3072
|
if (!this.product || this.availableQuantity <= 0 || this.adding) return;
|
|
3073
|
+
const redirect = beginPaymentRedirect({ method: "vipps", sponsorId: this.sponsorId, source: "detail" });
|
|
2486
3074
|
this.addCurrentToCart();
|
|
2487
3075
|
this.close();
|
|
2488
|
-
void this.startVippsExpressOrCheckout();
|
|
3076
|
+
void this.startVippsExpressOrCheckout(redirect);
|
|
2489
3077
|
}
|
|
2490
|
-
async startVippsExpressOrCheckout() {
|
|
3078
|
+
async startVippsExpressOrCheckout(redirect) {
|
|
2491
3079
|
let fallback = true;
|
|
2492
3080
|
try {
|
|
2493
|
-
if (!await Vio.checkout.getVippsExpressEnabled(this.sponsorId))
|
|
2494
|
-
|
|
2495
|
-
|
|
3081
|
+
if (!await Vio.checkout.getVippsExpressEnabled(this.sponsorId)) {
|
|
3082
|
+
redirect.abort("express-off");
|
|
3083
|
+
} else {
|
|
3084
|
+
const res = await Vio.checkout.startVippsExpress(this.sponsorId);
|
|
3085
|
+
fallback = !res?.payment_url;
|
|
3086
|
+
if (fallback) redirect.abort("not-started");
|
|
3087
|
+
}
|
|
2496
3088
|
} catch (err) {
|
|
2497
3089
|
if (typeof console !== "undefined") {
|
|
2498
3090
|
console.warn("[VioProductDetail] Vipps Express not started:", err);
|
|
2499
3091
|
}
|
|
3092
|
+
redirect.fail(err instanceof Error ? err.message : String(err));
|
|
2500
3093
|
}
|
|
2501
3094
|
if (!fallback) return;
|
|
2502
3095
|
this.dispatchEvent(
|
|
2503
3096
|
new CustomEvent("vio:checkout-open", {
|
|
2504
3097
|
bubbles: true,
|
|
2505
3098
|
composed: true,
|
|
2506
|
-
detail: { sponsorId: this.sponsorId, paymentMethod: "vipps", express: false, ...this.attribution() }
|
|
3099
|
+
detail: { sponsorId: this.sponsorId, paymentMethod: "vipps", express: false, ...this.attribution(), ...sponsorDetail(this.sponsorId) }
|
|
2507
3100
|
})
|
|
2508
3101
|
);
|
|
2509
3102
|
}
|
|
@@ -2516,7 +3109,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2516
3109
|
new CustomEvent("vio:checkout-open", {
|
|
2517
3110
|
bubbles: true,
|
|
2518
3111
|
composed: true,
|
|
2519
|
-
detail: { sponsorId: this.sponsorId, paymentMethod: "stripe", express: false, ...this.attribution() }
|
|
3112
|
+
detail: { sponsorId: this.sponsorId, paymentMethod: "stripe", express: false, ...this.attribution(), ...sponsorDetail(this.sponsorId) }
|
|
2520
3113
|
})
|
|
2521
3114
|
);
|
|
2522
3115
|
}
|
|
@@ -2556,14 +3149,68 @@ var VioProductDetail = class extends LitElement {
|
|
|
2556
3149
|
this.sellerName = name;
|
|
2557
3150
|
this.sellerSettled = true;
|
|
2558
3151
|
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Who sells the product, top left beside the close (Mote & Livsstil,
|
|
3154
|
+
* 2026-10-22): the logo of the sponsor the product was fetched with — the
|
|
3155
|
+
* product's brand, never the article's — with its name as `alt`; without a
|
|
3156
|
+
* logo that loads, its name; without an identity, the seller's Company name
|
|
3157
|
+
* (or the product's brand) once known. Size: --vio-detail-logo-height.
|
|
3158
|
+
*/
|
|
3159
|
+
renderTopbarSponsor() {
|
|
3160
|
+
const identity = resolveSponsorIdentity(this.sponsorId);
|
|
3161
|
+
const mark = identity ? renderSponsorMark(this, identity, { logo: "topbar-logo", name: "topbar-name" }) : nothing;
|
|
3162
|
+
if (mark !== nothing) {
|
|
3163
|
+
if (identity && usableLogo(identity)) {
|
|
3164
|
+
return html`<span class="topbar-logo-box">${mark}</span
|
|
3165
|
+
><span class="topbar-name topbar-mark-name">${identity.name}</span>`;
|
|
3166
|
+
}
|
|
3167
|
+
return mark;
|
|
3168
|
+
}
|
|
3169
|
+
const name = this.headerName();
|
|
3170
|
+
return name ? html`<span class="topbar-name">${name}</span>` : nothing;
|
|
3171
|
+
}
|
|
3172
|
+
/**
|
|
3173
|
+
* Under the buy buttons: «Du handler direkte hos {brand}.» — the brand the
|
|
3174
|
+
* product was fetched with (the sponsor; its Company name or the product's
|
|
3175
|
+
* brand when no identity is known), never the article's. Nothing when no
|
|
3176
|
+
* name is known.
|
|
3177
|
+
*/
|
|
3178
|
+
renderSellerNote() {
|
|
3179
|
+
const identity = resolveSponsorIdentity(this.sponsorId);
|
|
3180
|
+
const name = identity?.name || this.headerName();
|
|
3181
|
+
if (!name) return nothing;
|
|
3182
|
+
const links = [
|
|
3183
|
+
identity?.termsUrl ? html`<a href=${identity.termsUrl} target="_blank" rel="noopener noreferrer" part="seller-terms">Kjøpsvilkår</a>` : null,
|
|
3184
|
+
identity?.refundPolicyUrl ? html`<a href=${identity.refundPolicyUrl} target="_blank" rel="noopener noreferrer" part="seller-refund">Angrerett og retur</a>` : null
|
|
3185
|
+
].filter(Boolean);
|
|
3186
|
+
return html`<p class="seller-note" part="seller-note">
|
|
3187
|
+
Du handler direkte hos <strong>${name}</strong>.${links.length ? html`<span class="seller-links">${links}</span>` : nothing}
|
|
3188
|
+
</p>`;
|
|
3189
|
+
}
|
|
3190
|
+
/** Square-ish logo (4:5 to 5:4) → the top bar shows the name beside it. */
|
|
3191
|
+
measureLogo() {
|
|
3192
|
+
const img = this.renderRoot.querySelector(".topbar-logo");
|
|
3193
|
+
if (!img) {
|
|
3194
|
+
if (this.logoSquare) this.logoSquare = false;
|
|
3195
|
+
return;
|
|
3196
|
+
}
|
|
3197
|
+
const decide = () => {
|
|
3198
|
+
if (!img.naturalWidth || !img.naturalHeight) return;
|
|
3199
|
+
const ratio = img.naturalWidth / img.naturalHeight;
|
|
3200
|
+
const square = ratio >= 0.8 && ratio <= 1.25;
|
|
3201
|
+
if (square !== this.logoSquare) this.logoSquare = square;
|
|
3202
|
+
};
|
|
3203
|
+
if (img.complete) decide();
|
|
3204
|
+
else img.addEventListener("load", decide, { once: true });
|
|
3205
|
+
}
|
|
2559
3206
|
render() {
|
|
2560
3207
|
return html`
|
|
2561
3208
|
<div class="backdrop" @click=${this.close}></div>
|
|
2562
3209
|
<div class="modal" role="dialog" aria-modal="true" aria-label="Produkt" tabindex="-1">
|
|
2563
3210
|
<div class="handle" @click=${this.close} aria-hidden="true"></div>
|
|
2564
|
-
<div class="topbar">
|
|
2565
|
-
<
|
|
2566
|
-
<button class="close" @click=${this.close} aria-label="Lukk">×</button>
|
|
3211
|
+
<div class="topbar" part="topbar">
|
|
3212
|
+
<div class="topbar-sponsor ${this.logoSquare ? "is-square" : ""}" part="sponsor">${this.renderTopbarSponsor()}</div>
|
|
3213
|
+
<button class="close" part="close" @click=${this.close} aria-label="Lukk">×</button>
|
|
2567
3214
|
</div>
|
|
2568
3215
|
|
|
2569
3216
|
${this.renderBody()}
|
|
@@ -2595,44 +3242,90 @@ var VioProductDetail = class extends LitElement {
|
|
|
2595
3242
|
const activeImage = this.activeImage;
|
|
2596
3243
|
return html`
|
|
2597
3244
|
<div class="body">
|
|
2598
|
-
<div class="gallery">
|
|
2599
|
-
<div class="gallery-main">
|
|
3245
|
+
<div class="gallery" part="gallery">
|
|
3246
|
+
<div class="gallery-main" part="image">
|
|
2600
3247
|
${activeImage?.url ? html`<img src=${activeImage.url} alt=${p.title} />` : ""}
|
|
2601
3248
|
</div>
|
|
2602
3249
|
${images.length > 1 ? html`
|
|
2603
|
-
<div class="
|
|
2604
|
-
|
|
3250
|
+
<div class="thumbs ${this.thumbsOverflow ? "overflows" : ""}">
|
|
3251
|
+
<button
|
|
3252
|
+
class="thumbs-arrow prev"
|
|
3253
|
+
part="thumbs-arrow"
|
|
3254
|
+
aria-label="Forrige bilder"
|
|
3255
|
+
?disabled=${this.thumbsAtStart}
|
|
3256
|
+
@click=${() => this.scrollThumbs(-1)}
|
|
3257
|
+
>‹</button>
|
|
3258
|
+
<div class="gallery-thumbs" part="thumbs" @scroll=${this.onThumbsScroll}>
|
|
3259
|
+
${images.map(
|
|
2605
3260
|
(img, idx) => html`
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
3261
|
+
<button
|
|
3262
|
+
class="gallery-thumb ${idx === this.activeImageIndex ? "active" : ""}"
|
|
3263
|
+
part=${idx === this.activeImageIndex ? "thumb thumb-active" : "thumb"}
|
|
3264
|
+
@click=${() => this.activeImageIndex = idx}
|
|
3265
|
+
aria-label=${`Bilde ${idx + 1}`}
|
|
3266
|
+
aria-current=${idx === this.activeImageIndex ? "true" : "false"}
|
|
3267
|
+
>
|
|
3268
|
+
<img src=${img.url} alt="" loading="lazy" />
|
|
3269
|
+
</button>
|
|
3270
|
+
`
|
|
2614
3271
|
)}
|
|
3272
|
+
</div>
|
|
3273
|
+
<button
|
|
3274
|
+
class="thumbs-arrow next"
|
|
3275
|
+
part="thumbs-arrow"
|
|
3276
|
+
aria-label="Neste bilder"
|
|
3277
|
+
?disabled=${this.thumbsAtEnd}
|
|
3278
|
+
@click=${() => this.scrollThumbs(1)}
|
|
3279
|
+
>›</button>
|
|
2615
3280
|
</div>
|
|
2616
3281
|
` : ""}
|
|
2617
3282
|
</div>
|
|
2618
3283
|
|
|
2619
|
-
<div class="info">
|
|
2620
|
-
${p.brand ? html`<div class="brand">${p.brand}</div>` : ""}
|
|
2621
|
-
<h2 class="name">${p.title}</h2>
|
|
3284
|
+
<div class="info" part="info">
|
|
3285
|
+
${p.brand ? html`<div class="brand" part="brand">${p.brand}</div>` : ""}
|
|
3286
|
+
<h2 class="name" part="title">${p.title}</h2>
|
|
2622
3287
|
|
|
2623
3288
|
<div class="price-row">
|
|
2624
|
-
<span class="price">${formatPrice(this.unitPrice, p.price.currency_code)}</span>
|
|
3289
|
+
<span class="price" part="price">${formatPrice(this.unitPrice, p.price.currency_code)}</span>
|
|
2625
3290
|
${showCompare ? html`<span class="compare-at">${formatPrice(
|
|
2626
3291
|
compare,
|
|
2627
3292
|
p.price.currency_code
|
|
2628
3293
|
)}</span>` : ""}
|
|
2629
3294
|
</div>
|
|
2630
3295
|
|
|
2631
|
-
<div class="stock ${stock.cls}">${stock.text}</div>
|
|
3296
|
+
<div class="stock ${stock.cls}" part="stock">${stock.text}</div>
|
|
2632
3297
|
|
|
2633
3298
|
${this.renderOptionPickers(p.options)}
|
|
2634
3299
|
|
|
2635
|
-
<div class="
|
|
3300
|
+
<div class="buy" part="buy">
|
|
3301
|
+
${this.availableQuantity > 0 && this.methodEnabled("vipps") ? this.vippsOfficial ? html`
|
|
3302
|
+
<vipps-mobilepay-button
|
|
3303
|
+
class="buy-vipps-official"
|
|
3304
|
+
part="buy-vipps"
|
|
3305
|
+
brand="vipps"
|
|
3306
|
+
language="no"
|
|
3307
|
+
verb="buy"
|
|
3308
|
+
variant="primary"
|
|
3309
|
+
rounded="true"
|
|
3310
|
+
stretched="true"
|
|
3311
|
+
role="button"
|
|
3312
|
+
aria-label="Kjøp nå med Vipps"
|
|
3313
|
+
@click=${this.buyWithVipps}
|
|
3314
|
+
></vipps-mobilepay-button>
|
|
3315
|
+
` : html`
|
|
3316
|
+
<button
|
|
3317
|
+
class="buy-vipps"
|
|
3318
|
+
part="buy-vipps"
|
|
3319
|
+
@click=${this.buyWithVipps}
|
|
3320
|
+
?disabled=${this.adding}
|
|
3321
|
+
aria-label="Kjøp nå med Vipps"
|
|
3322
|
+
>
|
|
3323
|
+
<span>Kjøp nå med</span>
|
|
3324
|
+
<span class="vipps-badge">vipps</span>
|
|
3325
|
+
</button>
|
|
3326
|
+
` : ""}
|
|
3327
|
+
|
|
3328
|
+
<div class="qty-cta-row" part="quantity-row">
|
|
2636
3329
|
<div class="qty-stepper">
|
|
2637
3330
|
<button
|
|
2638
3331
|
class="qty-btn"
|
|
@@ -2653,6 +3346,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2653
3346
|
</div>
|
|
2654
3347
|
<button
|
|
2655
3348
|
class="add-to-cart"
|
|
3349
|
+
part="add-to-cart"
|
|
2656
3350
|
@click=${this.addToCart}
|
|
2657
3351
|
?disabled=${this.availableQuantity <= 0 || this.adding}
|
|
2658
3352
|
>
|
|
@@ -2714,36 +3408,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
2714
3408
|
</button>
|
|
2715
3409
|
` : ""}
|
|
2716
3410
|
|
|
2717
|
-
${this.
|
|
2718
|
-
|
|
2719
|
-
class="buy-vipps-official"
|
|
2720
|
-
brand="vipps"
|
|
2721
|
-
language="no"
|
|
2722
|
-
verb="buy"
|
|
2723
|
-
variant="primary"
|
|
2724
|
-
rounded="true"
|
|
2725
|
-
stretched="true"
|
|
2726
|
-
role="button"
|
|
2727
|
-
aria-label="Kjøp nå med Vipps"
|
|
2728
|
-
@click=${this.buyWithVipps}
|
|
2729
|
-
></vipps-mobilepay-button>
|
|
2730
|
-
` : html`
|
|
2731
|
-
<button
|
|
2732
|
-
class="buy-vipps"
|
|
2733
|
-
@click=${this.buyWithVipps}
|
|
2734
|
-
?disabled=${this.adding}
|
|
2735
|
-
aria-label="Kjøp nå med Vipps"
|
|
2736
|
-
>
|
|
2737
|
-
<span>Kjøp nå med</span>
|
|
2738
|
-
<span class="vipps-badge">vipps</span>
|
|
2739
|
-
</button>
|
|
2740
|
-
` : ""}
|
|
3411
|
+
${this.renderSellerNote()}
|
|
3412
|
+
</div>
|
|
2741
3413
|
|
|
2742
3414
|
${p.description ? html`
|
|
2743
|
-
<
|
|
2744
|
-
<
|
|
3415
|
+
<details class="description-block" part="description">
|
|
3416
|
+
<summary>Beskrivelse</summary>
|
|
2745
3417
|
<div class="description">${this.stripHtml(p.description)}</div>
|
|
2746
|
-
</
|
|
3418
|
+
</details>
|
|
2747
3419
|
` : ""}
|
|
2748
3420
|
</div>
|
|
2749
3421
|
</div>
|
|
@@ -2755,9 +3427,9 @@ var VioProductDetail = class extends LitElement {
|
|
|
2755
3427
|
const values = normalizeValues(opt.values);
|
|
2756
3428
|
if (values.length === 0) return "";
|
|
2757
3429
|
return html`
|
|
2758
|
-
<div class="option-group">
|
|
2759
|
-
<div class="option-label">${opt.name}</div>
|
|
2760
|
-
<div class="option-values">
|
|
3430
|
+
<div class="option-group" part="option-group">
|
|
3431
|
+
<div class="option-label" part="option-label">${opt.name}</div>
|
|
3432
|
+
<div class="option-values" part="options">
|
|
2761
3433
|
${values.map((val) => {
|
|
2762
3434
|
const available = this.isValueAvailable(opt.name, val);
|
|
2763
3435
|
const currentVal = this.selectedOptions[opt.name] ?? "";
|
|
@@ -2765,6 +3437,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2765
3437
|
return html`
|
|
2766
3438
|
<button
|
|
2767
3439
|
class="option-pill ${available ? "" : "unavailable"}"
|
|
3440
|
+
part=${isSelected ? "option option-selected" : "option"}
|
|
2768
3441
|
@click=${() => this.selectOption(opt.name, val)}
|
|
2769
3442
|
aria-pressed=${isSelected}
|
|
2770
3443
|
?disabled=${!available}
|
|
@@ -2780,8 +3453,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
2780
3453
|
})}`;
|
|
2781
3454
|
}
|
|
2782
3455
|
/** Best-effort HTML strip for description. */
|
|
2783
|
-
stripHtml(
|
|
2784
|
-
return
|
|
3456
|
+
stripHtml(html7) {
|
|
3457
|
+
return html7.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").trim();
|
|
2785
3458
|
}
|
|
2786
3459
|
};
|
|
2787
3460
|
__decorateClass([
|
|
@@ -2844,6 +3517,18 @@ __decorateClass([
|
|
|
2844
3517
|
__decorateClass([
|
|
2845
3518
|
state()
|
|
2846
3519
|
], VioProductDetail.prototype, "activeImageIndex", 2);
|
|
3520
|
+
__decorateClass([
|
|
3521
|
+
state()
|
|
3522
|
+
], VioProductDetail.prototype, "thumbsOverflow", 2);
|
|
3523
|
+
__decorateClass([
|
|
3524
|
+
state()
|
|
3525
|
+
], VioProductDetail.prototype, "thumbsAtStart", 2);
|
|
3526
|
+
__decorateClass([
|
|
3527
|
+
state()
|
|
3528
|
+
], VioProductDetail.prototype, "thumbsAtEnd", 2);
|
|
3529
|
+
__decorateClass([
|
|
3530
|
+
state()
|
|
3531
|
+
], VioProductDetail.prototype, "logoSquare", 2);
|
|
2847
3532
|
__decorateClass([
|
|
2848
3533
|
state()
|
|
2849
3534
|
], VioProductDetail.prototype, "adding", 2);
|
|
@@ -2895,6 +3580,14 @@ var VioCart = class extends LitElement {
|
|
|
2895
3580
|
container: () => this.renderRoot.querySelector(".drawer"),
|
|
2896
3581
|
close: () => this.close()
|
|
2897
3582
|
});
|
|
3583
|
+
/** Phones: the page behind is locked, and the sheet closes by pulling the bar or the header down. */
|
|
3584
|
+
this.bottomSheet = new BottomSheet({
|
|
3585
|
+
host: this,
|
|
3586
|
+
sheet: () => this.renderRoot.querySelector(".drawer"),
|
|
3587
|
+
backdrop: () => this.renderRoot.querySelector(".backdrop"),
|
|
3588
|
+
dragZones: ".handle, .header",
|
|
3589
|
+
close: () => this.close()
|
|
3590
|
+
});
|
|
2898
3591
|
this.boundOnCartChange = (e) => {
|
|
2899
3592
|
const detail = e.detail;
|
|
2900
3593
|
this.carts = detail.cartsBySponsor;
|
|
@@ -2925,7 +3618,7 @@ var VioCart = class extends LitElement {
|
|
|
2925
3618
|
top: 0;
|
|
2926
3619
|
right: 0;
|
|
2927
3620
|
bottom: 0;
|
|
2928
|
-
width: min(440px, 100%);
|
|
3621
|
+
width: min(var(--vio-cart-width, 440px), 100%);
|
|
2929
3622
|
background: var(--vio-color-surface, #fff);
|
|
2930
3623
|
transform: translateX(100%);
|
|
2931
3624
|
transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
|
@@ -2954,8 +3647,11 @@ var VioCart = class extends LitElement {
|
|
|
2954
3647
|
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
2955
3648
|
transform: translateY(100%);
|
|
2956
3649
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
3650
|
+
overscroll-behavior: contain;
|
|
2957
3651
|
}
|
|
2958
3652
|
:host([open]) .drawer { transform: translateY(0); }
|
|
3653
|
+
/* Pulled down to close (bottom-sheet.ts): the browser must not take the gesture for a scroll. */
|
|
3654
|
+
.handle, .header { touch-action: none; }
|
|
2959
3655
|
.handle {
|
|
2960
3656
|
display: flex;
|
|
2961
3657
|
justify-content: center;
|
|
@@ -3275,6 +3971,7 @@ var VioCart = class extends LitElement {
|
|
|
3275
3971
|
}
|
|
3276
3972
|
disconnectedCallback() {
|
|
3277
3973
|
Vio.cart.removeEventListener("change", this.boundOnCartChange);
|
|
3974
|
+
this.bottomSheet.disconnect();
|
|
3278
3975
|
super.disconnectedCallback();
|
|
3279
3976
|
}
|
|
3280
3977
|
close() {
|
|
@@ -3321,7 +4018,10 @@ var VioCart = class extends LitElement {
|
|
|
3321
4018
|
return isMethodEnabled(this.availableMethods, ...names);
|
|
3322
4019
|
}
|
|
3323
4020
|
updated(changed) {
|
|
3324
|
-
if (changed.has("open"))
|
|
4021
|
+
if (changed.has("open")) {
|
|
4022
|
+
this.dialogFocus.update(this.open);
|
|
4023
|
+
this.bottomSheet.update(this.open);
|
|
4024
|
+
}
|
|
3325
4025
|
if (changed.has("open") && this.open) {
|
|
3326
4026
|
this.confirmedOrder = null;
|
|
3327
4027
|
this.preparedTotal = -1;
|
|
@@ -3356,6 +4056,8 @@ var VioCart = class extends LitElement {
|
|
|
3356
4056
|
const handle = this.applePayHandle;
|
|
3357
4057
|
if (!handle || !handle.available || this.itemCount === 0) return;
|
|
3358
4058
|
const snapshot = this.buildConfirmation();
|
|
4059
|
+
const sponsorIds = [...this.carts.keys()];
|
|
4060
|
+
const sponsor = sponsorIds.length === 1 ? sponsorDetail(sponsorIds[0]) : {};
|
|
3359
4061
|
void handle.show().then((result) => {
|
|
3360
4062
|
if (!result) return;
|
|
3361
4063
|
this.confirmedOrder = snapshot;
|
|
@@ -3367,7 +4069,7 @@ var VioCart = class extends LitElement {
|
|
|
3367
4069
|
new CustomEvent("vio:payment-success", {
|
|
3368
4070
|
bubbles: true,
|
|
3369
4071
|
composed: true,
|
|
3370
|
-
detail: { method: "apple-pay", result, ...buyerRef ? { buyerRef } : {} }
|
|
4072
|
+
detail: { method: "apple-pay", result, ...buyerRef ? { buyerRef } : {}, ...sponsor }
|
|
3371
4073
|
})
|
|
3372
4074
|
);
|
|
3373
4075
|
});
|
|
@@ -3403,7 +4105,7 @@ var VioCart = class extends LitElement {
|
|
|
3403
4105
|
new CustomEvent("vio:checkout-open", {
|
|
3404
4106
|
bubbles: true,
|
|
3405
4107
|
composed: true,
|
|
3406
|
-
detail: { sponsorId: firstSponsorId, paymentMethod: "klarna", express: true }
|
|
4108
|
+
detail: { sponsorId: firstSponsorId, paymentMethod: "klarna", express: true, ...sponsorDetail(firstSponsorId) }
|
|
3407
4109
|
})
|
|
3408
4110
|
);
|
|
3409
4111
|
}
|
|
@@ -3424,7 +4126,7 @@ var VioCart = class extends LitElement {
|
|
|
3424
4126
|
new CustomEvent("vio:checkout-open", {
|
|
3425
4127
|
bubbles: true,
|
|
3426
4128
|
composed: true,
|
|
3427
|
-
detail: { sponsorId: firstSponsorId, express: false }
|
|
4129
|
+
detail: { sponsorId: firstSponsorId, express: false, ...sponsorDetail(firstSponsorId) }
|
|
3428
4130
|
})
|
|
3429
4131
|
);
|
|
3430
4132
|
}
|
|
@@ -3438,30 +4140,36 @@ var VioCart = class extends LitElement {
|
|
|
3438
4140
|
if (this.itemCount === 0) return;
|
|
3439
4141
|
const firstSponsorId = [...this.carts.keys()][0];
|
|
3440
4142
|
if (firstSponsorId === void 0) return;
|
|
3441
|
-
|
|
4143
|
+
const redirect = beginPaymentRedirect({ method: "vipps", sponsorId: firstSponsorId, source: "cart" });
|
|
4144
|
+
void this.startVippsExpressOrCheckout(firstSponsorId, redirect);
|
|
3442
4145
|
}
|
|
3443
4146
|
/** Vipps' own button when its script answers; our orange one otherwise. */
|
|
3444
4147
|
async loadVippsButton() {
|
|
3445
4148
|
this.vippsOfficial = await ensureVippsButton();
|
|
3446
4149
|
}
|
|
3447
|
-
async startVippsExpressOrCheckout(sponsorId) {
|
|
4150
|
+
async startVippsExpressOrCheckout(sponsorId, redirect) {
|
|
3448
4151
|
let fallback = true;
|
|
3449
4152
|
try {
|
|
3450
4153
|
await Vio.cart.waitForCartMutations(sponsorId);
|
|
3451
|
-
if (!await Vio.checkout.getVippsExpressEnabled(sponsorId))
|
|
3452
|
-
|
|
3453
|
-
|
|
4154
|
+
if (!await Vio.checkout.getVippsExpressEnabled(sponsorId)) {
|
|
4155
|
+
redirect.abort("express-off");
|
|
4156
|
+
} else {
|
|
4157
|
+
const res = await Vio.checkout.startVippsExpress(sponsorId);
|
|
4158
|
+
fallback = !res?.payment_url;
|
|
4159
|
+
if (fallback) redirect.abort("not-started");
|
|
4160
|
+
}
|
|
3454
4161
|
} catch (err) {
|
|
3455
4162
|
if (typeof console !== "undefined") {
|
|
3456
4163
|
console.warn("[VioCart] Vipps Express not started:", err);
|
|
3457
4164
|
}
|
|
4165
|
+
redirect.fail(err instanceof Error ? err.message : String(err));
|
|
3458
4166
|
}
|
|
3459
4167
|
if (!fallback) return;
|
|
3460
4168
|
this.dispatchEvent(
|
|
3461
4169
|
new CustomEvent("vio:checkout-open", {
|
|
3462
4170
|
bubbles: true,
|
|
3463
4171
|
composed: true,
|
|
3464
|
-
detail: { sponsorId, paymentMethod: "vipps", express: false }
|
|
4172
|
+
detail: { sponsorId, paymentMethod: "vipps", express: false, ...sponsorDetail(sponsorId) }
|
|
3465
4173
|
})
|
|
3466
4174
|
);
|
|
3467
4175
|
}
|
|
@@ -3474,10 +4182,14 @@ var VioCart = class extends LitElement {
|
|
|
3474
4182
|
new CustomEvent("vio:checkout-open", {
|
|
3475
4183
|
bubbles: true,
|
|
3476
4184
|
composed: true,
|
|
3477
|
-
detail: { sponsorId: firstSponsorId, paymentMethod: "stripe", express: false }
|
|
4185
|
+
detail: { sponsorId: firstSponsorId, paymentMethod: "stripe", express: false, ...sponsorDetail(firstSponsorId) }
|
|
3478
4186
|
})
|
|
3479
4187
|
);
|
|
3480
4188
|
}
|
|
4189
|
+
/** The name over a sponsor's lines: the brand's display name by the one rule, else what the cart kept. */
|
|
4190
|
+
sponsorLabel(cart) {
|
|
4191
|
+
return resolveSponsorIdentity(cart.sponsorId)?.name || cart.sponsorName || "";
|
|
4192
|
+
}
|
|
3481
4193
|
get totalAcrossSponsors() {
|
|
3482
4194
|
let sum = 0;
|
|
3483
4195
|
for (const cart of this.carts.values()) {
|
|
@@ -3518,7 +4230,7 @@ var VioCart = class extends LitElement {
|
|
|
3518
4230
|
${sponsorCarts.map(
|
|
3519
4231
|
(cart) => html`
|
|
3520
4232
|
<div class="sponsor-group">
|
|
3521
|
-
${cart
|
|
4233
|
+
${this.sponsorLabel(cart) ? html`<div class="sponsor-label">${this.sponsorLabel(cart)}</div>` : ""}
|
|
3522
4234
|
${cart.items.map(
|
|
3523
4235
|
(item) => html`
|
|
3524
4236
|
<div class="item">
|
|
@@ -3755,6 +4467,9 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3755
4467
|
this.verifyingReturn = null;
|
|
3756
4468
|
/** The checkout whose confirmation is on screen — see fillOrderNumber. */
|
|
3757
4469
|
this.confirmedCheckoutId = null;
|
|
4470
|
+
this.orderSponsor = null;
|
|
4471
|
+
/** The bootstrap that names the checkout's sponsor was asked for (once per checkout) — see watchSponsorBootstrap. */
|
|
4472
|
+
this.sponsorBootstrapAsked = false;
|
|
3758
4473
|
this.confirmedOrder = null;
|
|
3759
4474
|
/** Active Klarna Payments widget handle + the subtotal it was mounted for. */
|
|
3760
4475
|
this.klarnaHandle = null;
|
|
@@ -3853,6 +4568,15 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3853
4568
|
close: () => this.close(),
|
|
3854
4569
|
canClose: () => !this.paymentInFlight
|
|
3855
4570
|
});
|
|
4571
|
+
/** Phones: the page behind is locked, and the sheet closes by pulling the bar or the top bar down — not mid-payment. */
|
|
4572
|
+
this.bottomSheet = new BottomSheet({
|
|
4573
|
+
host: this,
|
|
4574
|
+
sheet: () => this.renderRoot.querySelector(".modal"),
|
|
4575
|
+
backdrop: () => this.renderRoot.querySelector(".backdrop"),
|
|
4576
|
+
dragZones: ".handle, .topbar",
|
|
4577
|
+
close: () => this.close(),
|
|
4578
|
+
canClose: () => !this.paymentInFlight
|
|
4579
|
+
});
|
|
3856
4580
|
this.paymentMethodsResolved = false;
|
|
3857
4581
|
/** Guards `autoSelectSoleMethod` to ONE attempt per opening — see there. */
|
|
3858
4582
|
this.autoSelectAttempted = false;
|
|
@@ -3905,6 +4629,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3905
4629
|
this.paymentNotice = null;
|
|
3906
4630
|
this.verifyingReturn = null;
|
|
3907
4631
|
this.confirmedCheckoutId = null;
|
|
4632
|
+
this.orderSponsor = null;
|
|
4633
|
+
this.sponsorBootstrapAsked = false;
|
|
3908
4634
|
this.express = false;
|
|
3909
4635
|
this.availableMethods = null;
|
|
3910
4636
|
this.paymentMethodsResolved = false;
|
|
@@ -4003,8 +4729,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4003
4729
|
from the right instead of the full-screen overlay. */
|
|
4004
4730
|
.modal.as-side {
|
|
4005
4731
|
inset: 0 0 0 auto;
|
|
4006
|
-
width: min(440px, 100%);
|
|
4007
|
-
max-width: 440px;
|
|
4732
|
+
width: min(var(--vio-checkout-width, 440px), 100%);
|
|
4733
|
+
max-width: var(--vio-checkout-width, 440px);
|
|
4008
4734
|
transform: translateX(100%);
|
|
4009
4735
|
border-radius: 0;
|
|
4010
4736
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.1);
|
|
@@ -4019,8 +4745,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4019
4745
|
@media (min-width: 601px) {
|
|
4020
4746
|
.modal {
|
|
4021
4747
|
inset: 0 0 0 auto;
|
|
4022
|
-
width: min(440px, 100%);
|
|
4023
|
-
max-width: 440px;
|
|
4748
|
+
width: min(var(--vio-checkout-width, 440px), 100%);
|
|
4749
|
+
max-width: var(--vio-checkout-width, 440px);
|
|
4024
4750
|
transform: translateX(100%);
|
|
4025
4751
|
border-radius: 0;
|
|
4026
4752
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.1);
|
|
@@ -4057,7 +4783,10 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4057
4783
|
height: 92vh;
|
|
4058
4784
|
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
4059
4785
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
4786
|
+
overscroll-behavior: contain;
|
|
4060
4787
|
}
|
|
4788
|
+
/* Pulled down to close (bottom-sheet.ts): the browser must not take the gesture for a scroll. */
|
|
4789
|
+
.handle, .topbar { touch-action: none; }
|
|
4061
4790
|
.handle {
|
|
4062
4791
|
display: flex;
|
|
4063
4792
|
justify-content: center;
|
|
@@ -4090,6 +4819,33 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4090
4819
|
font-weight: 400;
|
|
4091
4820
|
margin: 0;
|
|
4092
4821
|
}
|
|
4822
|
+
.topbar-title {
|
|
4823
|
+
display: flex;
|
|
4824
|
+
flex-direction: column;
|
|
4825
|
+
gap: 4px;
|
|
4826
|
+
min-width: 0;
|
|
4827
|
+
}
|
|
4828
|
+
/* Who the shopper buys from — the checkout's sponsor, compact under the heading. */
|
|
4829
|
+
.topbar-sponsor {
|
|
4830
|
+
margin: 0;
|
|
4831
|
+
display: flex;
|
|
4832
|
+
align-items: center;
|
|
4833
|
+
gap: 6px;
|
|
4834
|
+
font-size: 12px;
|
|
4835
|
+
line-height: 1.3;
|
|
4836
|
+
color: var(--vio-color-text-secondary, #666);
|
|
4837
|
+
}
|
|
4838
|
+
.topbar-sponsor-logo {
|
|
4839
|
+
display: block;
|
|
4840
|
+
width: auto;
|
|
4841
|
+
height: 18px;
|
|
4842
|
+
max-width: 120px;
|
|
4843
|
+
object-fit: contain;
|
|
4844
|
+
}
|
|
4845
|
+
.topbar-sponsor-name {
|
|
4846
|
+
color: var(--vio-color-text, #0a0a0a);
|
|
4847
|
+
font-weight: 600;
|
|
4848
|
+
}
|
|
4093
4849
|
.close {
|
|
4094
4850
|
background: none;
|
|
4095
4851
|
border: none;
|
|
@@ -4405,7 +5161,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4405
5161
|
width: 48px;
|
|
4406
5162
|
height: 48px;
|
|
4407
5163
|
border-radius: 50%;
|
|
4408
|
-
background: #2e7d32;
|
|
5164
|
+
background: var(--vio-confirm-check-bg, #2e7d32);
|
|
4409
5165
|
color: #fff;
|
|
4410
5166
|
display: flex;
|
|
4411
5167
|
align-items: center;
|
|
@@ -4415,10 +5171,26 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4415
5171
|
}
|
|
4416
5172
|
.confirm-title {
|
|
4417
5173
|
font-family: var(--vio-font-serif, Georgia, serif);
|
|
4418
|
-
font-size: 24px;
|
|
4419
|
-
font-weight: 400;
|
|
5174
|
+
font-size: var(--vio-confirm-title-size, 24px);
|
|
5175
|
+
font-weight: var(--vio-confirm-title-weight, 400);
|
|
4420
5176
|
margin: 0;
|
|
4421
5177
|
}
|
|
5178
|
+
/* The brand the order is with: its logo over the thanks, its name in it. */
|
|
5179
|
+
.confirm-brand { display: flex; align-items: center; }
|
|
5180
|
+
.confirm-brand-logo {
|
|
5181
|
+
display: block;
|
|
5182
|
+
width: auto;
|
|
5183
|
+
height: var(--vio-confirm-logo-height, 40px);
|
|
5184
|
+
max-width: var(--vio-confirm-logo-max-width, 220px);
|
|
5185
|
+
object-fit: contain;
|
|
5186
|
+
}
|
|
5187
|
+
.confirm-sponsor {
|
|
5188
|
+
font-size: var(--vio-confirm-text-size, 14px);
|
|
5189
|
+
line-height: 1.4;
|
|
5190
|
+
color: var(--vio-color-text, #0a0a0a);
|
|
5191
|
+
margin: 4px 0 0;
|
|
5192
|
+
}
|
|
5193
|
+
.confirm-sponsor strong { font-weight: 600; }
|
|
4422
5194
|
.confirm-text {
|
|
4423
5195
|
font-size: 14px;
|
|
4424
5196
|
color: var(--vio-color-text-secondary, #666);
|
|
@@ -4619,6 +5391,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4619
5391
|
* Vipps-specific (GetVippsStatus) return-verification paths. */
|
|
4620
5392
|
applyReturnOutcome(outcome, sawStatus, vioMethod, sponsorId, checkoutId) {
|
|
4621
5393
|
if (outcome === "paid") {
|
|
5394
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
4622
5395
|
if (sponsorId > 0) Vio.cart.clearSponsorCart(sponsorId);
|
|
4623
5396
|
this.confirmedOrder = {
|
|
4624
5397
|
items: [],
|
|
@@ -4636,6 +5409,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4636
5409
|
} else if (outcome === "failed") {
|
|
4637
5410
|
this.paymentError = "Betalingen ble avbrutt eller feilet. Vennligst pr\xF8v igjen.";
|
|
4638
5411
|
this.open = true;
|
|
5412
|
+
returnCancelled({ method: vioMethod, sponsorId, source: "checkout", checkoutId });
|
|
4639
5413
|
} else if (sawStatus) {
|
|
4640
5414
|
this.paymentNotice = "Betalingen behandles fortsatt. Du f\xE5r ordrebekreftelse p\xE5 e-post n\xE5r den er gjennomf\xF8rt. Handlekurven er uendret.";
|
|
4641
5415
|
this.open = true;
|
|
@@ -4679,6 +5453,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4679
5453
|
if (kustomReturn?.kind === "confirmation") {
|
|
4680
5454
|
const kustomOrderId = kustomReturn.orderId;
|
|
4681
5455
|
this.returningFrom = "kustom";
|
|
5456
|
+
this.setOrderSponsor(sponsorId, kustomReturn.checkoutId);
|
|
4682
5457
|
if (!this.checkoutState) {
|
|
4683
5458
|
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
4684
5459
|
if (spIdToUse) {
|
|
@@ -4722,6 +5497,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4722
5497
|
if (!vioPayment && !checkoutId) return;
|
|
4723
5498
|
if (urlParams.get("payment_processor") === "QLIRO" && checkoutId) {
|
|
4724
5499
|
this.returningFrom = "qliro";
|
|
5500
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
4725
5501
|
if (!this.checkoutState) {
|
|
4726
5502
|
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
4727
5503
|
if (spIdToUse) {
|
|
@@ -4776,6 +5552,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4776
5552
|
}
|
|
4777
5553
|
if (urlParams.get("payment_processor") === "WALLEY" && checkoutId) {
|
|
4778
5554
|
this.returningFrom = "walley";
|
|
5555
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
4779
5556
|
if (!this.checkoutState) {
|
|
4780
5557
|
const spIdToUse = sponsorId || [...Vio.cart.getAllCarts().keys()][0] || 0;
|
|
4781
5558
|
if (spIdToUse) {
|
|
@@ -4832,6 +5609,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4832
5609
|
this.cleanReturnQueryParams(ADYEN_RETURN_QUERY_KEYS);
|
|
4833
5610
|
if (!adyenReturn) return;
|
|
4834
5611
|
this.returningFrom = "adyen";
|
|
5612
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
4835
5613
|
this.adyenSettled = true;
|
|
4836
5614
|
this.autoSelectAttempted = true;
|
|
4837
5615
|
if (!this.checkoutState) {
|
|
@@ -4888,6 +5666,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4888
5666
|
}
|
|
4889
5667
|
}
|
|
4890
5668
|
}
|
|
5669
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
4891
5670
|
this.open = true;
|
|
4892
5671
|
this.verifyingReturn = "Bekrefter betalingen med Vipps\u2026";
|
|
4893
5672
|
const { outcome, sawStatus } = await this.pollVippsStatus(checkoutId, sponsorId);
|
|
@@ -4898,6 +5677,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4898
5677
|
let outcome = "pending";
|
|
4899
5678
|
let sawStatus = false;
|
|
4900
5679
|
if (checkoutId) {
|
|
5680
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
4901
5681
|
this.open = true;
|
|
4902
5682
|
this.verifyingReturn = "Bekrefter betalingen\u2026";
|
|
4903
5683
|
for (const delayMs of _VioCheckout.RETURN_VERIFY_DELAYS_MS) {
|
|
@@ -4972,10 +5752,14 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4972
5752
|
this.unmountNexi();
|
|
4973
5753
|
this.unmountAdyen();
|
|
4974
5754
|
for (const settle of [...this.pendingSuccessEmits]) settle();
|
|
5755
|
+
this.bottomSheet.disconnect();
|
|
4975
5756
|
super.disconnectedCallback();
|
|
4976
5757
|
}
|
|
4977
5758
|
updated(changed) {
|
|
4978
|
-
if (changed.has("open"))
|
|
5759
|
+
if (changed.has("open")) {
|
|
5760
|
+
this.dialogFocus.update(this.open);
|
|
5761
|
+
this.bottomSheet.update(this.open);
|
|
5762
|
+
}
|
|
4979
5763
|
if (changed.has("open") && this.open && this.items.length > 0 && this.availableShippingsList.length === 0) {
|
|
4980
5764
|
void this.loadAvailableShippings();
|
|
4981
5765
|
}
|
|
@@ -4996,6 +5780,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4996
5780
|
this.unmountStripe();
|
|
4997
5781
|
this.paymentError = null;
|
|
4998
5782
|
}
|
|
5783
|
+
this.watchSponsorBootstrap();
|
|
4999
5784
|
if (!this.isConnected) return;
|
|
5000
5785
|
void this.mountKlarnaIfNeeded();
|
|
5001
5786
|
void this.mountKustomIfNeeded();
|
|
@@ -5427,10 +6212,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5427
6212
|
}
|
|
5428
6213
|
if (method === "vipps") {
|
|
5429
6214
|
this.vippsLoading = true;
|
|
6215
|
+
const redirect = beginPaymentRedirect({ method: "vipps", sponsorId: this.checkoutState?.sponsorId, source: "checkout" });
|
|
5430
6216
|
void this.startVippsFromCheckout().catch((err) => {
|
|
5431
6217
|
if (typeof console !== "undefined") {
|
|
5432
6218
|
console.warn("[VioCheckout] Vipps payment could not start:", err);
|
|
5433
6219
|
}
|
|
6220
|
+
redirect.fail(errorText(err));
|
|
5434
6221
|
this.paymentError = friendlyPaymentError(errorText(err));
|
|
5435
6222
|
}).finally(() => {
|
|
5436
6223
|
this.vippsLoading = false;
|
|
@@ -5472,6 +6259,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5472
6259
|
const email = String(this.form.email ?? "").trim();
|
|
5473
6260
|
const checkoutId = returnCheckoutId || this.checkoutState?.checkoutId;
|
|
5474
6261
|
const providerOrderId = r.order?.orderId != null ? String(r.order.orderId) : "";
|
|
6262
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
5475
6263
|
this.confirmedOrder = {
|
|
5476
6264
|
items: [...this.items],
|
|
5477
6265
|
currency: this.checkoutState?.currency ?? "",
|
|
@@ -5514,17 +6302,27 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5514
6302
|
* `visibilitychange`) or the element leaves, the event goes out at once
|
|
5515
6303
|
* without the reference. Exactly one event per call. `afterwards` runs
|
|
5516
6304
|
* right after the event, so a path keeps the order it always had (the cart
|
|
5517
|
-
* is cleared after the event).
|
|
6305
|
+
* is cleared after the event). When that answer already has Vio's order
|
|
6306
|
+
* number, the event carries it too (`orderNumber`): it is the purchase's
|
|
6307
|
+
* `order_id`, and analytics need not ask again.
|
|
5518
6308
|
*/
|
|
5519
6309
|
async emitPaymentSuccess(detail, checkoutId, sponsorId, afterwards) {
|
|
5520
6310
|
let done = false;
|
|
5521
|
-
const
|
|
6311
|
+
const sponsorAtStart = this.sponsorOfOrder(sponsorId, checkoutId);
|
|
6312
|
+
const finish = (found = {}) => {
|
|
5522
6313
|
if (done) return;
|
|
5523
6314
|
done = true;
|
|
6315
|
+
const named = this.sponsorOfOrder(sponsorId, checkoutId);
|
|
6316
|
+
const sponsor = named.sponsor ? named : sponsorAtStart;
|
|
6317
|
+
const withSponsor = sponsor.sponsor ? { ...detail, ...sponsor } : detail;
|
|
5524
6318
|
const event = new CustomEvent("vio:payment-success", {
|
|
5525
6319
|
bubbles: true,
|
|
5526
6320
|
composed: true,
|
|
5527
|
-
detail: buyerRef ? {
|
|
6321
|
+
detail: found.buyerRef || found.orderNumber ? {
|
|
6322
|
+
...withSponsor,
|
|
6323
|
+
...found.buyerRef ? { buyerRef: found.buyerRef } : {},
|
|
6324
|
+
...found.orderNumber ? { orderNumber: found.orderNumber } : {}
|
|
6325
|
+
} : withSponsor
|
|
5528
6326
|
});
|
|
5529
6327
|
this.dispatchEvent(event);
|
|
5530
6328
|
if (!this.isConnected && typeof window !== "undefined") {
|
|
@@ -5537,7 +6335,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5537
6335
|
};
|
|
5538
6336
|
const known = buyerRefOf(detail);
|
|
5539
6337
|
if (known || !checkoutId || !this.shouldAskBuyerRef(detail)) {
|
|
5540
|
-
finish(known);
|
|
6338
|
+
finish({ buyerRef: known });
|
|
5541
6339
|
return;
|
|
5542
6340
|
}
|
|
5543
6341
|
const settleEarly = () => finish();
|
|
@@ -5569,8 +6367,9 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5569
6367
|
* failed or has neither the reference nor an order yet (the webhook has not
|
|
5570
6368
|
* written the checkout), again at 1.2 s — all within 2.5 s. An order
|
|
5571
6369
|
* without a reference is a settled answer: the backend has none, asking
|
|
5572
|
-
* again cannot help.
|
|
5573
|
-
*
|
|
6370
|
+
* again cannot help. Whatever the answer said of the order number comes
|
|
6371
|
+
* back with it. `{}` when the time is up, when the answer stopped mattering
|
|
6372
|
+
* (`stopped()`) or on failure.
|
|
5574
6373
|
*/
|
|
5575
6374
|
async askBuyerRef(checkoutId, sponsorId, stopped) {
|
|
5576
6375
|
const started = Date.now();
|
|
@@ -5583,19 +6382,20 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5583
6382
|
for (const at of [0, _VioCheckout.BUYER_REF_RETRY_AT_MS]) {
|
|
5584
6383
|
const wait = Math.min(at - (Date.now() - started), remaining());
|
|
5585
6384
|
if (wait > 0) await Promise.race([new Promise((resolve) => setTimeout(resolve, wait)), timeUp]);
|
|
5586
|
-
if (stopped() || remaining() <= 0) return
|
|
6385
|
+
if (stopped() || remaining() <= 0) return {};
|
|
5587
6386
|
try {
|
|
5588
6387
|
const reference = await Promise.race([
|
|
5589
6388
|
Vio.checkout.getOrderReference(checkoutId, sponsorId || void 0),
|
|
5590
6389
|
timeUp
|
|
5591
6390
|
]);
|
|
5592
|
-
if (reference === null) return
|
|
5593
|
-
|
|
5594
|
-
if (reference.
|
|
6391
|
+
if (reference === null) return {};
|
|
6392
|
+
const order = reference.orderNumber ? { orderNumber: reference.orderNumber } : {};
|
|
6393
|
+
if (reference.buyerRef) return { buyerRef: reference.buyerRef, ...order };
|
|
6394
|
+
if (reference.orderNumber !== null) return order;
|
|
5595
6395
|
} catch {
|
|
5596
6396
|
}
|
|
5597
6397
|
}
|
|
5598
|
-
return
|
|
6398
|
+
return {};
|
|
5599
6399
|
} finally {
|
|
5600
6400
|
clearTimeout(deadline);
|
|
5601
6401
|
}
|
|
@@ -5712,7 +6512,10 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5712
6512
|
>
|
|
5713
6513
|
<div class="handle" @click=${this.close} aria-hidden="true"></div>
|
|
5714
6514
|
<div class="topbar">
|
|
5715
|
-
<
|
|
6515
|
+
<div class="topbar-title">
|
|
6516
|
+
<h2 class="heading">${this.orderConfirmed ? "Takk!" : this.heading}</h2>
|
|
6517
|
+
${this.renderTopbarSponsor()}
|
|
6518
|
+
</div>
|
|
5716
6519
|
<button class="close" @click=${this.close} aria-label="Lukk">×</button>
|
|
5717
6520
|
</div>
|
|
5718
6521
|
|
|
@@ -5722,6 +6525,82 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5722
6525
|
</div>
|
|
5723
6526
|
`;
|
|
5724
6527
|
}
|
|
6528
|
+
/**
|
|
6529
|
+
* Remember whose order this is and how it is named right now, before the
|
|
6530
|
+
* cart that names it is cleared. Whose: the sponsor this tab remembered for
|
|
6531
|
+
* the checkout when it was created (exact for that checkout id), else the
|
|
6532
|
+
* one given (the return URL's `vio_sponsor`, the state it was paid in),
|
|
6533
|
+
* else the open state's — a return without `vio_sponsor` (Qliro, Walley)
|
|
6534
|
+
* re-opens the state with whichever cart comes first, which is a guess.
|
|
6535
|
+
*/
|
|
6536
|
+
setOrderSponsor(sponsorId, checkoutId) {
|
|
6537
|
+
try {
|
|
6538
|
+
const checkout = checkoutId || void 0;
|
|
6539
|
+
const id = rememberedCheckoutSponsor(checkout)?.sponsorId ?? (sponsorId && sponsorId > 0 ? sponsorId : void 0) ?? this.checkoutState?.sponsorId;
|
|
6540
|
+
if (!id) return;
|
|
6541
|
+
const named = checkoutSponsorOf(resolveSponsorIdentity(id, checkout));
|
|
6542
|
+
const previous = this.orderSponsor;
|
|
6543
|
+
const snapshot = named ?? (previous && previous.sponsorId === id ? previous.snapshot : void 0);
|
|
6544
|
+
this.orderSponsor = { sponsorId: id, ...checkout ? { checkoutId: checkout } : {}, ...snapshot ? { snapshot } : {} };
|
|
6545
|
+
} catch {
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
/**
|
|
6549
|
+
* Who this checkout is with, as a shopper sees it: the order being
|
|
6550
|
+
* confirmed / verified (`orderSponsor`), else the open checkout's own
|
|
6551
|
+
* sponsor (`checkoutState.sponsorId`) — named by `Vio.sponsorIdentity`,
|
|
6552
|
+
* which falls back to this tab's memory of the checkout and the cart; the
|
|
6553
|
+
* name taken before the cart was cleared last. Never the page's
|
|
6554
|
+
* attribution. `null` when nothing names it.
|
|
6555
|
+
*/
|
|
6556
|
+
checkoutSponsorIdentity() {
|
|
6557
|
+
const o = this.orderSponsor;
|
|
6558
|
+
if (o) return checkoutSponsorOf(resolveSponsorIdentity(o.sponsorId, o.checkoutId)) ?? o.snapshot ?? null;
|
|
6559
|
+
const s = this.checkoutState;
|
|
6560
|
+
return s?.sponsorId ? checkoutSponsorOf(resolveSponsorIdentity(s.sponsorId, s.checkoutId)) ?? null : null;
|
|
6561
|
+
}
|
|
6562
|
+
/**
|
|
6563
|
+
* `{ sponsor }` for the payment event — the same sponsor the screen names
|
|
6564
|
+
* (`orderSponsor`, set by every path right before it emits): named now, else
|
|
6565
|
+
* as named before the cart was cleared; `{}` when nothing names it.
|
|
6566
|
+
*/
|
|
6567
|
+
sponsorOfOrder(sponsorId, checkoutId) {
|
|
6568
|
+
try {
|
|
6569
|
+
const o = this.orderSponsor;
|
|
6570
|
+
const sameOrder = !!o && (o.checkoutId ?? void 0) === (checkoutId || void 0);
|
|
6571
|
+
const id = sameOrder ? o.sponsorId : sponsorId > 0 ? sponsorId : void 0;
|
|
6572
|
+
const live = sponsorDetail(id, checkoutId);
|
|
6573
|
+
if (live.sponsor) return live;
|
|
6574
|
+
if (sameOrder && o.snapshot) return { sponsor: o.snapshot };
|
|
6575
|
+
} catch {
|
|
6576
|
+
}
|
|
6577
|
+
return {};
|
|
6578
|
+
}
|
|
6579
|
+
/**
|
|
6580
|
+
* Ask once per checkout for the bootstrap when it is not there yet and a
|
|
6581
|
+
* sponsor has to be named (a redirect return renders before it), then
|
|
6582
|
+
* render again — the name and the logo appear when it lands.
|
|
6583
|
+
*/
|
|
6584
|
+
watchSponsorBootstrap() {
|
|
6585
|
+
if (this.sponsorBootstrapAsked || !this.open || Vio.bootstrapCache) return;
|
|
6586
|
+
if (!this.orderSponsor && !this.checkoutState?.sponsorId) return;
|
|
6587
|
+
this.sponsorBootstrapAsked = true;
|
|
6588
|
+
void whenBootstrapped().then(() => this.requestUpdate());
|
|
6589
|
+
}
|
|
6590
|
+
/**
|
|
6591
|
+
* The checkout's sponsor in the header — «Du handler hos <logo|name>»
|
|
6592
|
+
* («Du har handlet hos» on a provider's receipt). Not on our own
|
|
6593
|
+
* confirmation, which says it in its body.
|
|
6594
|
+
*/
|
|
6595
|
+
renderTopbarSponsor() {
|
|
6596
|
+
if (this.orderConfirmed) return nothing;
|
|
6597
|
+
const sponsor = this.checkoutSponsorIdentity();
|
|
6598
|
+
const mark = renderSponsorMark(this, sponsor, { logo: "topbar-sponsor-logo", name: "topbar-sponsor-name" });
|
|
6599
|
+
if (mark === nothing) return nothing;
|
|
6600
|
+
return html`<p class="topbar-sponsor" part="sponsor">
|
|
6601
|
+
<span>${this.providerReceipt ? "Du har handlet hos" : "Du handler hos"}</span> ${mark}
|
|
6602
|
+
</p>`;
|
|
6603
|
+
}
|
|
5725
6604
|
/**
|
|
5726
6605
|
* The provider's receipt on its own. It must not depend on the cart: the
|
|
5727
6606
|
* payment step is rendered from the cart, and clearing the cart after a
|
|
@@ -5749,6 +6628,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5749
6628
|
this.confirmedMethod = method;
|
|
5750
6629
|
this.open = true;
|
|
5751
6630
|
const checkoutId = returnCheckoutId || this.checkoutState?.checkoutId;
|
|
6631
|
+
this.setOrderSponsor(sponsorId, checkoutId);
|
|
5752
6632
|
void this.emitPaymentSuccess(
|
|
5753
6633
|
{ method, sponsorId, result, checkoutId },
|
|
5754
6634
|
checkoutId,
|
|
@@ -5777,19 +6657,24 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5777
6657
|
details.push(["Betalingsm\xE5te", o?.paidWith || this.methodLabel(this.confirmedMethod)]);
|
|
5778
6658
|
if (o?.providerRef) details.push([o.providerRef.label, o.providerRef.value]);
|
|
5779
6659
|
if (email) details.push(["Kvittering til", email]);
|
|
6660
|
+
const sponsor = this.checkoutSponsorIdentity();
|
|
5780
6661
|
return html`
|
|
5781
|
-
<section class="confirmation">
|
|
6662
|
+
<section class="confirmation" part="confirmation">
|
|
6663
|
+
${usableLogo(sponsor) ? html`<div class="confirm-brand" part="sponsor">
|
|
6664
|
+
${renderSponsorMark(this, sponsor, { logo: "confirm-brand-logo", name: "confirm-brand-name" })}
|
|
6665
|
+
</div>` : ""}
|
|
5782
6666
|
<header class="confirm-head">
|
|
5783
6667
|
<div class="confirm-check" aria-hidden="true">✓</div>
|
|
5784
6668
|
<div>
|
|
5785
|
-
<h3 class="confirm-title">Takk for bestillingen!</h3>
|
|
6669
|
+
<h3 class="confirm-title" part="confirm-title">Takk for bestillingen!</h3>
|
|
6670
|
+
${sponsor?.name ? html`<p class="confirm-sponsor" part="confirm-sponsor">Du har handlet hos <strong>${sponsor.name}</strong>.</p>` : ""}
|
|
5786
6671
|
<p class="confirm-text">
|
|
5787
6672
|
Betalingen er bekreftet.${email ? html` Du får en ordrebekreftelse på e-post.` : ""}
|
|
5788
6673
|
</p>
|
|
5789
6674
|
</div>
|
|
5790
6675
|
</header>
|
|
5791
6676
|
|
|
5792
|
-
<dl class="confirm-details">
|
|
6677
|
+
<dl class="confirm-details" part="confirm-details">
|
|
5793
6678
|
${details.map(
|
|
5794
6679
|
([k, v]) => html`<div><dt>${k}</dt><dd class=${k === o?.providerRef?.label ? "ref" : ""}>${v}</dd></div>`
|
|
5795
6680
|
)}
|
|
@@ -6199,12 +7084,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
6199
7084
|
for (const delay of _VioCheckout.NEXI_DETAILS_DELAYS_MS) {
|
|
6200
7085
|
if (delay) await new Promise((r) => setTimeout(r, delay));
|
|
6201
7086
|
const o = await Vio.checkout.getNexiOrder(checkoutId, sponsorId).catch(() => null);
|
|
6202
|
-
const
|
|
6203
|
-
if (!
|
|
7087
|
+
const current2 = this.confirmedOrder;
|
|
7088
|
+
if (!current2 || current2.providerRef?.value !== paymentId) return;
|
|
6204
7089
|
if (!o) continue;
|
|
6205
7090
|
const paidWith = o.payment_method ? o.card_last4 ? `${o.payment_method} \u2022\u2022\u2022\u2022 ${o.card_last4}` : o.payment_method : void 0;
|
|
6206
7091
|
this.confirmedOrder = {
|
|
6207
|
-
...
|
|
7092
|
+
...current2,
|
|
6208
7093
|
...paidWith ? { paidWith } : {},
|
|
6209
7094
|
...o.email ? { email: o.email } : {}
|
|
6210
7095
|
};
|
|
@@ -6232,15 +7117,15 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
6232
7117
|
const options = this.nexiShipping?.options ?? [];
|
|
6233
7118
|
if (options.length < 2) return "";
|
|
6234
7119
|
const currency = this.checkoutState?.currency || getGlobalCurrency();
|
|
6235
|
-
const
|
|
7120
|
+
const current2 = this.nexiShipping?.shipping_id;
|
|
6236
7121
|
return html`
|
|
6237
7122
|
<div class="ship-select nexi-ship" role="radiogroup" aria-label="Frakt">
|
|
6238
7123
|
${options.map(
|
|
6239
7124
|
(o) => html`
|
|
6240
7125
|
<button
|
|
6241
|
-
class="ship-opt ${
|
|
7126
|
+
class="ship-opt ${current2 === o.id ? "active" : ""}"
|
|
6242
7127
|
role="radio"
|
|
6243
|
-
aria-checked=${
|
|
7128
|
+
aria-checked=${current2 === o.id}
|
|
6244
7129
|
?disabled=${this.nexiPicking}
|
|
6245
7130
|
@click=${() => void this.onPickNexiShipping(o.id)}
|
|
6246
7131
|
>
|
|
@@ -7075,6 +7960,9 @@ __decorateClass([
|
|
|
7075
7960
|
__decorateClass([
|
|
7076
7961
|
state()
|
|
7077
7962
|
], _VioCheckout.prototype, "verifyingReturn", 2);
|
|
7963
|
+
__decorateClass([
|
|
7964
|
+
state()
|
|
7965
|
+
], _VioCheckout.prototype, "orderSponsor", 2);
|
|
7078
7966
|
__decorateClass([
|
|
7079
7967
|
state()
|
|
7080
7968
|
], _VioCheckout.prototype, "confirmedOrder", 2);
|
|
@@ -7211,5 +8099,5 @@ function registerVioElements() {
|
|
|
7211
8099
|
}
|
|
7212
8100
|
|
|
7213
8101
|
export { VioCart, VioCheckout, VioProduct, VioProductCarousel, VioProductDetail, VioTokens, applyVioTheme, registerVioElements };
|
|
7214
|
-
//# sourceMappingURL=chunk-
|
|
7215
|
-
//# sourceMappingURL=chunk-
|
|
8102
|
+
//# sourceMappingURL=chunk-Q2NALS7P.js.map
|
|
8103
|
+
//# sourceMappingURL=chunk-Q2NALS7P.js.map
|