@vio-live/web-sdk 0.27.0 → 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 +116 -0
- package/dist/{chunk-HJRIPCWB.js → chunk-AFJ5M53F.js} +233 -29
- package/dist/chunk-AFJ5M53F.js.map +1 -0
- package/dist/{chunk-GLAKUYQA.js → chunk-Q2NALS7P.js} +832 -179
- package/dist/chunk-Q2NALS7P.js.map +1 -0
- package/dist/core/index.d.ts +108 -17
- package/dist/core/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{tokens-CihuCU2l.d.ts → tokens-y0KzN7bP.d.ts} +103 -9
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +3 -2
- package/dist/chunk-GLAKUYQA.js.map +0 -1
- package/dist/chunk-HJRIPCWB.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
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-
|
|
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
2
|
import { LitElement, css, html, nothing } from 'lit';
|
|
3
3
|
import { property, state, query } from 'lit/decorators.js';
|
|
4
4
|
|
|
@@ -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;
|
|
@@ -1244,6 +1275,102 @@ __decorateClass([
|
|
|
1244
1275
|
state()
|
|
1245
1276
|
], VioProductCarousel.prototype, "fetchError", 2);
|
|
1246
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" });
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1247
1374
|
// src/core/checkout/method-taxonomy.ts
|
|
1248
1375
|
function normalizeMethodName(name) {
|
|
1249
1376
|
return String(name ?? "").toLowerCase().replace(/[^a-z]/g, "");
|
|
@@ -1451,6 +1578,207 @@ function isWithin(node, ancestor) {
|
|
|
1451
1578
|
return false;
|
|
1452
1579
|
}
|
|
1453
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
|
+
|
|
1454
1782
|
// src/ui/components/vio-product-detail.ts
|
|
1455
1783
|
var STRIPE_LOGO_SRC = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
1456
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>'
|
|
@@ -1485,6 +1813,10 @@ var VioProductDetail = class extends LitElement {
|
|
|
1485
1813
|
this.selectedOptions = {};
|
|
1486
1814
|
this.quantity = 1;
|
|
1487
1815
|
this.activeImageIndex = 0;
|
|
1816
|
+
this.thumbsOverflow = false;
|
|
1817
|
+
this.thumbsAtStart = true;
|
|
1818
|
+
this.thumbsAtEnd = false;
|
|
1819
|
+
this.logoSquare = false;
|
|
1488
1820
|
this.adding = false;
|
|
1489
1821
|
this.applePayOk = false;
|
|
1490
1822
|
this.availableMethods = null;
|
|
@@ -1502,6 +1834,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
1502
1834
|
container: () => this.renderRoot.querySelector(".modal"),
|
|
1503
1835
|
close: () => this.close()
|
|
1504
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
|
+
});
|
|
1505
1845
|
this.boundProductClick = (e) => {
|
|
1506
1846
|
if (!this.autoOpen) return;
|
|
1507
1847
|
const ev = e;
|
|
@@ -1529,6 +1869,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
1529
1869
|
this.show();
|
|
1530
1870
|
void this.fetchProduct();
|
|
1531
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
|
+
};
|
|
1532
1880
|
}
|
|
1533
1881
|
/** The card's context applies to the product it opened — not to one the host opened by hand later. */
|
|
1534
1882
|
get fromCard() {
|
|
@@ -1616,7 +1964,10 @@ var VioProductDetail = class extends LitElement {
|
|
|
1616
1964
|
height: 92vh;
|
|
1617
1965
|
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
1618
1966
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
1967
|
+
overscroll-behavior: contain;
|
|
1619
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; }
|
|
1620
1971
|
.handle {
|
|
1621
1972
|
display: flex;
|
|
1622
1973
|
justify-content: center;
|
|
@@ -1637,8 +1988,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1637
1988
|
@media (min-width: 601px) {
|
|
1638
1989
|
.modal {
|
|
1639
1990
|
inset: 0 0 0 auto;
|
|
1640
|
-
width: min(440px, 100%);
|
|
1641
|
-
max-width: 440px;
|
|
1991
|
+
width: min(var(--vio-detail-width, 440px), 100%);
|
|
1992
|
+
max-width: var(--vio-detail-width, 440px);
|
|
1642
1993
|
border-radius: 0;
|
|
1643
1994
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.1);
|
|
1644
1995
|
transform: translateX(100%);
|
|
@@ -1652,29 +2003,59 @@ var VioProductDetail = class extends LitElement {
|
|
|
1652
2003
|
max-width: none;
|
|
1653
2004
|
grid-template-columns: 1fr;
|
|
1654
2005
|
gap: 20px;
|
|
1655
|
-
padding:
|
|
2006
|
+
padding: var(--vio-detail-padding-top, 0px) var(--vio-detail-padding-x, var(--vio-space-lg, 24px)) 40px;
|
|
1656
2007
|
}
|
|
1657
|
-
.modal .name { font-size: 26px; line-height: 1.2; }
|
|
1658
|
-
.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; }
|
|
1659
2010
|
}
|
|
1660
2011
|
|
|
1661
2012
|
.topbar {
|
|
1662
2013
|
display: flex;
|
|
1663
2014
|
justify-content: space-between;
|
|
1664
2015
|
align-items: center;
|
|
1665
|
-
|
|
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));
|
|
1666
2020
|
position: sticky;
|
|
1667
2021
|
top: 0;
|
|
1668
2022
|
background: var(--vio-color-surface, #fff);
|
|
1669
2023
|
border-bottom: 1px solid var(--vio-color-border, #e5e5e5);
|
|
1670
2024
|
z-index: 1;
|
|
1671
2025
|
}
|
|
1672
|
-
.
|
|
1673
|
-
|
|
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);
|
|
1674
2052
|
text-transform: uppercase;
|
|
1675
2053
|
letter-spacing: var(--vio-tracking-label, 0.1em);
|
|
1676
2054
|
color: var(--vio-color-text-secondary, #666);
|
|
1677
|
-
font-weight:
|
|
2055
|
+
font-weight: 600;
|
|
2056
|
+
white-space: nowrap;
|
|
2057
|
+
overflow: hidden;
|
|
2058
|
+
text-overflow: ellipsis;
|
|
1678
2059
|
}
|
|
1679
2060
|
.close {
|
|
1680
2061
|
background: none;
|
|
@@ -1700,74 +2081,110 @@ var VioProductDetail = class extends LitElement {
|
|
|
1700
2081
|
gap: 64px;
|
|
1701
2082
|
}
|
|
1702
2083
|
@media (max-width: 800px) {
|
|
1703
|
-
.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
|
+
}
|
|
1704
2089
|
}
|
|
1705
2090
|
|
|
1706
|
-
/* Gallery
|
|
1707
|
-
|
|
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; }
|
|
1708
2098
|
.gallery-main {
|
|
1709
|
-
|
|
1710
|
-
|
|
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);
|
|
1711
2108
|
display: flex;
|
|
1712
2109
|
align-items: center;
|
|
1713
2110
|
justify-content: center;
|
|
1714
2111
|
overflow: hidden;
|
|
1715
2112
|
}
|
|
1716
2113
|
.gallery-main img {
|
|
2114
|
+
display: block;
|
|
1717
2115
|
width: 100%;
|
|
1718
2116
|
height: 100%;
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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; }
|
|
1723
2125
|
.gallery-thumbs {
|
|
1724
2126
|
display: flex;
|
|
1725
|
-
|
|
1726
|
-
|
|
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;
|
|
1727
2134
|
}
|
|
2135
|
+
.gallery-thumbs::-webkit-scrollbar { display: none; }
|
|
1728
2136
|
.gallery-thumb {
|
|
1729
|
-
|
|
1730
|
-
|
|
2137
|
+
flex: 0 0 auto;
|
|
2138
|
+
width: var(--vio-detail-thumb-size, 64px);
|
|
2139
|
+
height: var(--vio-detail-thumb-size, 64px);
|
|
1731
2140
|
background: var(--vio-color-surface-muted, #f2f2f2);
|
|
1732
|
-
border: 1px solid transparent;
|
|
2141
|
+
border: var(--vio-detail-thumb-border-width, 1px) solid transparent;
|
|
2142
|
+
border-radius: var(--vio-detail-thumb-radius, 0);
|
|
1733
2143
|
cursor: pointer;
|
|
1734
2144
|
padding: 0;
|
|
1735
2145
|
overflow: hidden;
|
|
2146
|
+
scroll-snap-align: start;
|
|
2147
|
+
box-sizing: border-box;
|
|
2148
|
+
opacity: var(--vio-detail-thumb-opacity, 1);
|
|
1736
2149
|
}
|
|
1737
2150
|
.gallery-thumb img {
|
|
2151
|
+
display: block;
|
|
1738
2152
|
width: 100%;
|
|
1739
2153
|
height: 100%;
|
|
1740
|
-
object-fit:
|
|
1741
|
-
padding: 6px;
|
|
1742
|
-
box-sizing: border-box;
|
|
2154
|
+
object-fit: var(--vio-detail-thumb-fit, cover);
|
|
1743
2155
|
}
|
|
1744
|
-
.gallery-thumb.active {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
.sponsor-header {
|
|
1748
|
-
grid-column: 1 / -1;
|
|
1749
|
-
display: flex;
|
|
1750
|
-
align-items: center;
|
|
1751
|
-
min-height: 24px;
|
|
2156
|
+
.gallery-thumb.active {
|
|
2157
|
+
border-color: var(--vio-detail-thumb-active-color, var(--vio-color-text, #0a0a0a));
|
|
2158
|
+
opacity: 1;
|
|
1752
2159
|
}
|
|
1753
|
-
.
|
|
1754
|
-
display:
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
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;
|
|
1760
2178
|
}
|
|
1761
|
-
.
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
font-weight: 600;
|
|
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; }
|
|
1767
2184
|
}
|
|
1768
2185
|
|
|
1769
2186
|
/* Info column */
|
|
1770
|
-
.info { display: flex; flex-direction: column; gap:
|
|
2187
|
+
.info { display: flex; flex-direction: column; gap: var(--vio-detail-info-gap, 20px); min-width: 0; }
|
|
1771
2188
|
.brand {
|
|
1772
2189
|
font-size: 12px;
|
|
1773
2190
|
text-transform: uppercase;
|
|
@@ -1777,8 +2194,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1777
2194
|
}
|
|
1778
2195
|
.name {
|
|
1779
2196
|
font-family: var(--vio-font-serif, Georgia, serif);
|
|
1780
|
-
font-size: clamp(28px, 4vw, 40px);
|
|
1781
|
-
font-weight: 400;
|
|
2197
|
+
font-size: var(--vio-detail-title-size, clamp(28px, 4vw, 40px));
|
|
2198
|
+
font-weight: var(--vio-detail-title-weight, 400);
|
|
1782
2199
|
line-height: 1.15;
|
|
1783
2200
|
margin: 0;
|
|
1784
2201
|
letter-spacing: -0.01em;
|
|
@@ -1789,8 +2206,8 @@ var VioProductDetail = class extends LitElement {
|
|
|
1789
2206
|
gap: 16px;
|
|
1790
2207
|
}
|
|
1791
2208
|
.price {
|
|
1792
|
-
font-size: 24px;
|
|
1793
|
-
font-weight: 700;
|
|
2209
|
+
font-size: var(--vio-detail-price-size, 24px);
|
|
2210
|
+
font-weight: var(--vio-detail-price-weight, 700);
|
|
1794
2211
|
}
|
|
1795
2212
|
.compare-at {
|
|
1796
2213
|
font-size: 16px;
|
|
@@ -1808,7 +2225,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
1808
2225
|
.stock.out-of-stock { color: var(--vio-color-text-tertiary, #999); }
|
|
1809
2226
|
|
|
1810
2227
|
/* Option pickers */
|
|
1811
|
-
.option-group { display: flex; flex-direction: column; gap:
|
|
2228
|
+
.option-group { display: flex; flex-direction: column; gap: 6px; }
|
|
1812
2229
|
.option-label {
|
|
1813
2230
|
font-size: 11px;
|
|
1814
2231
|
text-transform: uppercase;
|
|
@@ -1816,14 +2233,28 @@ var VioProductDetail = class extends LitElement {
|
|
|
1816
2233
|
color: var(--vio-color-text-secondary, #666);
|
|
1817
2234
|
font-weight: 500;
|
|
1818
2235
|
}
|
|
1819
|
-
|
|
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; }
|
|
1820
2246
|
.option-pill {
|
|
1821
|
-
|
|
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);
|
|
1822
2253
|
background: #fff;
|
|
1823
2254
|
border: 1px solid var(--vio-color-border, #e5e5e5);
|
|
1824
2255
|
cursor: pointer;
|
|
1825
2256
|
font-family: inherit;
|
|
1826
|
-
font-size: 13px;
|
|
2257
|
+
font-size: var(--vio-detail-option-font-size, 13px);
|
|
1827
2258
|
font-weight: 500;
|
|
1828
2259
|
color: inherit;
|
|
1829
2260
|
transition: border-color 0.15s, background 0.15s;
|
|
@@ -1842,6 +2273,34 @@ var VioProductDetail = class extends LitElement {
|
|
|
1842
2273
|
}
|
|
1843
2274
|
.option-pill.unavailable:hover { border-color: var(--vio-color-border, #e5e5e5); }
|
|
1844
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
|
+
|
|
1845
2304
|
/* Qty + CTA row */
|
|
1846
2305
|
.qty-cta-row {
|
|
1847
2306
|
display: grid;
|
|
@@ -1856,7 +2315,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
1856
2315
|
}
|
|
1857
2316
|
.qty-btn {
|
|
1858
2317
|
width: 44px;
|
|
1859
|
-
height:
|
|
2318
|
+
height: var(--vio-detail-button-height, 52px);
|
|
1860
2319
|
background: #fff;
|
|
1861
2320
|
border: none;
|
|
1862
2321
|
cursor: pointer;
|
|
@@ -1874,7 +2333,9 @@ var VioProductDetail = class extends LitElement {
|
|
|
1874
2333
|
background: var(--vio-color-text, #0a0a0a);
|
|
1875
2334
|
color: var(--vio-color-text-on-primary, #fff);
|
|
1876
2335
|
border: none;
|
|
1877
|
-
|
|
2336
|
+
min-height: var(--vio-detail-button-height, 52px);
|
|
2337
|
+
border-radius: var(--vio-detail-button-radius, 0);
|
|
2338
|
+
padding: 0 24px;
|
|
1878
2339
|
font-size: 12px;
|
|
1879
2340
|
text-transform: uppercase;
|
|
1880
2341
|
letter-spacing: var(--vio-tracking-label, 0.1em);
|
|
@@ -1984,19 +2445,49 @@ var VioProductDetail = class extends LitElement {
|
|
|
1984
2445
|
.buy-applepay:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1985
2446
|
|
|
1986
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); }
|
|
1987
2474
|
.description {
|
|
1988
2475
|
font-size: 14px;
|
|
1989
2476
|
line-height: 1.6;
|
|
1990
2477
|
color: var(--vio-color-text-secondary, #444);
|
|
1991
|
-
|
|
2478
|
+
padding-bottom: 14px;
|
|
2479
|
+
white-space: pre-line;
|
|
1992
2480
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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
|
+
}
|
|
2000
2491
|
}
|
|
2001
2492
|
|
|
2002
2493
|
/* Skeleton loader: shimmer placeholders mirroring the product layout
|
|
@@ -2052,14 +2543,47 @@ var VioProductDetail = class extends LitElement {
|
|
|
2052
2543
|
disconnectedCallback() {
|
|
2053
2544
|
document.removeEventListener("vio:product-click", this.boundProductClick);
|
|
2054
2545
|
this.slotRegistration.clear();
|
|
2546
|
+
this.bottomSheet.disconnect();
|
|
2055
2547
|
super.disconnectedCallback();
|
|
2056
2548
|
}
|
|
2057
2549
|
updated(changed) {
|
|
2058
|
-
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();
|
|
2059
2557
|
if (changed.has("vioSlot") || changed.has("vioSlotName") || changed.has("vioScope") || changed.has("vioScopeName") || changed.has("vioCampaign")) {
|
|
2060
2558
|
this.slotRegistration.sync(this.slotAttributes());
|
|
2061
2559
|
}
|
|
2062
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
|
+
}
|
|
2063
2587
|
show() {
|
|
2064
2588
|
this.open = true;
|
|
2065
2589
|
}
|
|
@@ -2546,20 +3070,26 @@ var VioProductDetail = class extends LitElement {
|
|
|
2546
3070
|
}
|
|
2547
3071
|
buyWithVipps() {
|
|
2548
3072
|
if (!this.product || this.availableQuantity <= 0 || this.adding) return;
|
|
3073
|
+
const redirect = beginPaymentRedirect({ method: "vipps", sponsorId: this.sponsorId, source: "detail" });
|
|
2549
3074
|
this.addCurrentToCart();
|
|
2550
3075
|
this.close();
|
|
2551
|
-
void this.startVippsExpressOrCheckout();
|
|
3076
|
+
void this.startVippsExpressOrCheckout(redirect);
|
|
2552
3077
|
}
|
|
2553
|
-
async startVippsExpressOrCheckout() {
|
|
3078
|
+
async startVippsExpressOrCheckout(redirect) {
|
|
2554
3079
|
let fallback = true;
|
|
2555
3080
|
try {
|
|
2556
|
-
if (!await Vio.checkout.getVippsExpressEnabled(this.sponsorId))
|
|
2557
|
-
|
|
2558
|
-
|
|
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
|
+
}
|
|
2559
3088
|
} catch (err) {
|
|
2560
3089
|
if (typeof console !== "undefined") {
|
|
2561
3090
|
console.warn("[VioProductDetail] Vipps Express not started:", err);
|
|
2562
3091
|
}
|
|
3092
|
+
redirect.fail(err instanceof Error ? err.message : String(err));
|
|
2563
3093
|
}
|
|
2564
3094
|
if (!fallback) return;
|
|
2565
3095
|
this.dispatchEvent(
|
|
@@ -2620,28 +3150,67 @@ var VioProductDetail = class extends LitElement {
|
|
|
2620
3150
|
this.sellerSettled = true;
|
|
2621
3151
|
}
|
|
2622
3152
|
/**
|
|
2623
|
-
* Who sells the product
|
|
2624
|
-
*
|
|
2625
|
-
*
|
|
2626
|
-
*
|
|
2627
|
-
*
|
|
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.
|
|
2628
3158
|
*/
|
|
2629
|
-
|
|
3159
|
+
renderTopbarSponsor() {
|
|
2630
3160
|
const identity = resolveSponsorIdentity(this.sponsorId);
|
|
2631
|
-
|
|
2632
|
-
if (
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
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 });
|
|
2636
3205
|
}
|
|
2637
3206
|
render() {
|
|
2638
3207
|
return html`
|
|
2639
3208
|
<div class="backdrop" @click=${this.close}></div>
|
|
2640
3209
|
<div class="modal" role="dialog" aria-modal="true" aria-label="Produkt" tabindex="-1">
|
|
2641
3210
|
<div class="handle" @click=${this.close} aria-hidden="true"></div>
|
|
2642
|
-
<div class="topbar">
|
|
2643
|
-
<
|
|
2644
|
-
<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>
|
|
2645
3214
|
</div>
|
|
2646
3215
|
|
|
2647
3216
|
${this.renderBody()}
|
|
@@ -2673,45 +3242,90 @@ var VioProductDetail = class extends LitElement {
|
|
|
2673
3242
|
const activeImage = this.activeImage;
|
|
2674
3243
|
return html`
|
|
2675
3244
|
<div class="body">
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
<div class="gallery-main">
|
|
3245
|
+
<div class="gallery" part="gallery">
|
|
3246
|
+
<div class="gallery-main" part="image">
|
|
2679
3247
|
${activeImage?.url ? html`<img src=${activeImage.url} alt=${p.title} />` : ""}
|
|
2680
3248
|
</div>
|
|
2681
3249
|
${images.length > 1 ? html`
|
|
2682
|
-
<div class="
|
|
2683
|
-
|
|
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(
|
|
2684
3260
|
(img, idx) => html`
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
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
|
+
`
|
|
2693
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>
|
|
2694
3280
|
</div>
|
|
2695
3281
|
` : ""}
|
|
2696
3282
|
</div>
|
|
2697
3283
|
|
|
2698
|
-
<div class="info">
|
|
2699
|
-
${p.brand ? html`<div class="brand">${p.brand}</div>` : ""}
|
|
2700
|
-
<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>
|
|
2701
3287
|
|
|
2702
3288
|
<div class="price-row">
|
|
2703
|
-
<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>
|
|
2704
3290
|
${showCompare ? html`<span class="compare-at">${formatPrice(
|
|
2705
3291
|
compare,
|
|
2706
3292
|
p.price.currency_code
|
|
2707
3293
|
)}</span>` : ""}
|
|
2708
3294
|
</div>
|
|
2709
3295
|
|
|
2710
|
-
<div class="stock ${stock.cls}">${stock.text}</div>
|
|
3296
|
+
<div class="stock ${stock.cls}" part="stock">${stock.text}</div>
|
|
2711
3297
|
|
|
2712
3298
|
${this.renderOptionPickers(p.options)}
|
|
2713
3299
|
|
|
2714
|
-
<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">
|
|
2715
3329
|
<div class="qty-stepper">
|
|
2716
3330
|
<button
|
|
2717
3331
|
class="qty-btn"
|
|
@@ -2732,6 +3346,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2732
3346
|
</div>
|
|
2733
3347
|
<button
|
|
2734
3348
|
class="add-to-cart"
|
|
3349
|
+
part="add-to-cart"
|
|
2735
3350
|
@click=${this.addToCart}
|
|
2736
3351
|
?disabled=${this.availableQuantity <= 0 || this.adding}
|
|
2737
3352
|
>
|
|
@@ -2793,36 +3408,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
2793
3408
|
</button>
|
|
2794
3409
|
` : ""}
|
|
2795
3410
|
|
|
2796
|
-
${this.
|
|
2797
|
-
|
|
2798
|
-
class="buy-vipps-official"
|
|
2799
|
-
brand="vipps"
|
|
2800
|
-
language="no"
|
|
2801
|
-
verb="buy"
|
|
2802
|
-
variant="primary"
|
|
2803
|
-
rounded="true"
|
|
2804
|
-
stretched="true"
|
|
2805
|
-
role="button"
|
|
2806
|
-
aria-label="Kjøp nå med Vipps"
|
|
2807
|
-
@click=${this.buyWithVipps}
|
|
2808
|
-
></vipps-mobilepay-button>
|
|
2809
|
-
` : html`
|
|
2810
|
-
<button
|
|
2811
|
-
class="buy-vipps"
|
|
2812
|
-
@click=${this.buyWithVipps}
|
|
2813
|
-
?disabled=${this.adding}
|
|
2814
|
-
aria-label="Kjøp nå med Vipps"
|
|
2815
|
-
>
|
|
2816
|
-
<span>Kjøp nå med</span>
|
|
2817
|
-
<span class="vipps-badge">vipps</span>
|
|
2818
|
-
</button>
|
|
2819
|
-
` : ""}
|
|
3411
|
+
${this.renderSellerNote()}
|
|
3412
|
+
</div>
|
|
2820
3413
|
|
|
2821
3414
|
${p.description ? html`
|
|
2822
|
-
<
|
|
2823
|
-
<
|
|
3415
|
+
<details class="description-block" part="description">
|
|
3416
|
+
<summary>Beskrivelse</summary>
|
|
2824
3417
|
<div class="description">${this.stripHtml(p.description)}</div>
|
|
2825
|
-
</
|
|
3418
|
+
</details>
|
|
2826
3419
|
` : ""}
|
|
2827
3420
|
</div>
|
|
2828
3421
|
</div>
|
|
@@ -2834,9 +3427,9 @@ var VioProductDetail = class extends LitElement {
|
|
|
2834
3427
|
const values = normalizeValues(opt.values);
|
|
2835
3428
|
if (values.length === 0) return "";
|
|
2836
3429
|
return html`
|
|
2837
|
-
<div class="option-group">
|
|
2838
|
-
<div class="option-label">${opt.name}</div>
|
|
2839
|
-
<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">
|
|
2840
3433
|
${values.map((val) => {
|
|
2841
3434
|
const available = this.isValueAvailable(opt.name, val);
|
|
2842
3435
|
const currentVal = this.selectedOptions[opt.name] ?? "";
|
|
@@ -2844,6 +3437,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
2844
3437
|
return html`
|
|
2845
3438
|
<button
|
|
2846
3439
|
class="option-pill ${available ? "" : "unavailable"}"
|
|
3440
|
+
part=${isSelected ? "option option-selected" : "option"}
|
|
2847
3441
|
@click=${() => this.selectOption(opt.name, val)}
|
|
2848
3442
|
aria-pressed=${isSelected}
|
|
2849
3443
|
?disabled=${!available}
|
|
@@ -2923,6 +3517,18 @@ __decorateClass([
|
|
|
2923
3517
|
__decorateClass([
|
|
2924
3518
|
state()
|
|
2925
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);
|
|
2926
3532
|
__decorateClass([
|
|
2927
3533
|
state()
|
|
2928
3534
|
], VioProductDetail.prototype, "adding", 2);
|
|
@@ -2974,6 +3580,14 @@ var VioCart = class extends LitElement {
|
|
|
2974
3580
|
container: () => this.renderRoot.querySelector(".drawer"),
|
|
2975
3581
|
close: () => this.close()
|
|
2976
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
|
+
});
|
|
2977
3591
|
this.boundOnCartChange = (e) => {
|
|
2978
3592
|
const detail = e.detail;
|
|
2979
3593
|
this.carts = detail.cartsBySponsor;
|
|
@@ -3004,7 +3618,7 @@ var VioCart = class extends LitElement {
|
|
|
3004
3618
|
top: 0;
|
|
3005
3619
|
right: 0;
|
|
3006
3620
|
bottom: 0;
|
|
3007
|
-
width: min(440px, 100%);
|
|
3621
|
+
width: min(var(--vio-cart-width, 440px), 100%);
|
|
3008
3622
|
background: var(--vio-color-surface, #fff);
|
|
3009
3623
|
transform: translateX(100%);
|
|
3010
3624
|
transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
|
@@ -3033,8 +3647,11 @@ var VioCart = class extends LitElement {
|
|
|
3033
3647
|
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
3034
3648
|
transform: translateY(100%);
|
|
3035
3649
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
3650
|
+
overscroll-behavior: contain;
|
|
3036
3651
|
}
|
|
3037
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; }
|
|
3038
3655
|
.handle {
|
|
3039
3656
|
display: flex;
|
|
3040
3657
|
justify-content: center;
|
|
@@ -3354,6 +3971,7 @@ var VioCart = class extends LitElement {
|
|
|
3354
3971
|
}
|
|
3355
3972
|
disconnectedCallback() {
|
|
3356
3973
|
Vio.cart.removeEventListener("change", this.boundOnCartChange);
|
|
3974
|
+
this.bottomSheet.disconnect();
|
|
3357
3975
|
super.disconnectedCallback();
|
|
3358
3976
|
}
|
|
3359
3977
|
close() {
|
|
@@ -3400,7 +4018,10 @@ var VioCart = class extends LitElement {
|
|
|
3400
4018
|
return isMethodEnabled(this.availableMethods, ...names);
|
|
3401
4019
|
}
|
|
3402
4020
|
updated(changed) {
|
|
3403
|
-
if (changed.has("open"))
|
|
4021
|
+
if (changed.has("open")) {
|
|
4022
|
+
this.dialogFocus.update(this.open);
|
|
4023
|
+
this.bottomSheet.update(this.open);
|
|
4024
|
+
}
|
|
3404
4025
|
if (changed.has("open") && this.open) {
|
|
3405
4026
|
this.confirmedOrder = null;
|
|
3406
4027
|
this.preparedTotal = -1;
|
|
@@ -3519,23 +4140,29 @@ var VioCart = class extends LitElement {
|
|
|
3519
4140
|
if (this.itemCount === 0) return;
|
|
3520
4141
|
const firstSponsorId = [...this.carts.keys()][0];
|
|
3521
4142
|
if (firstSponsorId === void 0) return;
|
|
3522
|
-
|
|
4143
|
+
const redirect = beginPaymentRedirect({ method: "vipps", sponsorId: firstSponsorId, source: "cart" });
|
|
4144
|
+
void this.startVippsExpressOrCheckout(firstSponsorId, redirect);
|
|
3523
4145
|
}
|
|
3524
4146
|
/** Vipps' own button when its script answers; our orange one otherwise. */
|
|
3525
4147
|
async loadVippsButton() {
|
|
3526
4148
|
this.vippsOfficial = await ensureVippsButton();
|
|
3527
4149
|
}
|
|
3528
|
-
async startVippsExpressOrCheckout(sponsorId) {
|
|
4150
|
+
async startVippsExpressOrCheckout(sponsorId, redirect) {
|
|
3529
4151
|
let fallback = true;
|
|
3530
4152
|
try {
|
|
3531
4153
|
await Vio.cart.waitForCartMutations(sponsorId);
|
|
3532
|
-
if (!await Vio.checkout.getVippsExpressEnabled(sponsorId))
|
|
3533
|
-
|
|
3534
|
-
|
|
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
|
+
}
|
|
3535
4161
|
} catch (err) {
|
|
3536
4162
|
if (typeof console !== "undefined") {
|
|
3537
4163
|
console.warn("[VioCart] Vipps Express not started:", err);
|
|
3538
4164
|
}
|
|
4165
|
+
redirect.fail(err instanceof Error ? err.message : String(err));
|
|
3539
4166
|
}
|
|
3540
4167
|
if (!fallback) return;
|
|
3541
4168
|
this.dispatchEvent(
|
|
@@ -3941,6 +4568,15 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3941
4568
|
close: () => this.close(),
|
|
3942
4569
|
canClose: () => !this.paymentInFlight
|
|
3943
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
|
+
});
|
|
3944
4580
|
this.paymentMethodsResolved = false;
|
|
3945
4581
|
/** Guards `autoSelectSoleMethod` to ONE attempt per opening — see there. */
|
|
3946
4582
|
this.autoSelectAttempted = false;
|
|
@@ -4093,8 +4729,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4093
4729
|
from the right instead of the full-screen overlay. */
|
|
4094
4730
|
.modal.as-side {
|
|
4095
4731
|
inset: 0 0 0 auto;
|
|
4096
|
-
width: min(440px, 100%);
|
|
4097
|
-
max-width: 440px;
|
|
4732
|
+
width: min(var(--vio-checkout-width, 440px), 100%);
|
|
4733
|
+
max-width: var(--vio-checkout-width, 440px);
|
|
4098
4734
|
transform: translateX(100%);
|
|
4099
4735
|
border-radius: 0;
|
|
4100
4736
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.1);
|
|
@@ -4109,8 +4745,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4109
4745
|
@media (min-width: 601px) {
|
|
4110
4746
|
.modal {
|
|
4111
4747
|
inset: 0 0 0 auto;
|
|
4112
|
-
width: min(440px, 100%);
|
|
4113
|
-
max-width: 440px;
|
|
4748
|
+
width: min(var(--vio-checkout-width, 440px), 100%);
|
|
4749
|
+
max-width: var(--vio-checkout-width, 440px);
|
|
4114
4750
|
transform: translateX(100%);
|
|
4115
4751
|
border-radius: 0;
|
|
4116
4752
|
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.1);
|
|
@@ -4147,7 +4783,10 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4147
4783
|
height: 92vh;
|
|
4148
4784
|
border-radius: var(--vio-radius-xl, 16px) var(--vio-radius-xl, 16px) 0 0;
|
|
4149
4785
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
|
4786
|
+
overscroll-behavior: contain;
|
|
4150
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; }
|
|
4151
4790
|
.handle {
|
|
4152
4791
|
display: flex;
|
|
4153
4792
|
justify-content: center;
|
|
@@ -4522,7 +5161,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4522
5161
|
width: 48px;
|
|
4523
5162
|
height: 48px;
|
|
4524
5163
|
border-radius: 50%;
|
|
4525
|
-
background: #2e7d32;
|
|
5164
|
+
background: var(--vio-confirm-check-bg, #2e7d32);
|
|
4526
5165
|
color: #fff;
|
|
4527
5166
|
display: flex;
|
|
4528
5167
|
align-items: center;
|
|
@@ -4532,8 +5171,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4532
5171
|
}
|
|
4533
5172
|
.confirm-title {
|
|
4534
5173
|
font-family: var(--vio-font-serif, Georgia, serif);
|
|
4535
|
-
font-size: 24px;
|
|
4536
|
-
font-weight: 400;
|
|
5174
|
+
font-size: var(--vio-confirm-title-size, 24px);
|
|
5175
|
+
font-weight: var(--vio-confirm-title-weight, 400);
|
|
4537
5176
|
margin: 0;
|
|
4538
5177
|
}
|
|
4539
5178
|
/* The brand the order is with: its logo over the thanks, its name in it. */
|
|
@@ -4541,13 +5180,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4541
5180
|
.confirm-brand-logo {
|
|
4542
5181
|
display: block;
|
|
4543
5182
|
width: auto;
|
|
4544
|
-
height:
|
|
4545
|
-
max-
|
|
4546
|
-
max-width: 160px;
|
|
5183
|
+
height: var(--vio-confirm-logo-height, 40px);
|
|
5184
|
+
max-width: var(--vio-confirm-logo-max-width, 220px);
|
|
4547
5185
|
object-fit: contain;
|
|
4548
5186
|
}
|
|
4549
5187
|
.confirm-sponsor {
|
|
4550
|
-
font-size: 14px;
|
|
5188
|
+
font-size: var(--vio-confirm-text-size, 14px);
|
|
4551
5189
|
line-height: 1.4;
|
|
4552
5190
|
color: var(--vio-color-text, #0a0a0a);
|
|
4553
5191
|
margin: 4px 0 0;
|
|
@@ -4771,6 +5409,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4771
5409
|
} else if (outcome === "failed") {
|
|
4772
5410
|
this.paymentError = "Betalingen ble avbrutt eller feilet. Vennligst pr\xF8v igjen.";
|
|
4773
5411
|
this.open = true;
|
|
5412
|
+
returnCancelled({ method: vioMethod, sponsorId, source: "checkout", checkoutId });
|
|
4774
5413
|
} else if (sawStatus) {
|
|
4775
5414
|
this.paymentNotice = "Betalingen behandles fortsatt. Du f\xE5r ordrebekreftelse p\xE5 e-post n\xE5r den er gjennomf\xF8rt. Handlekurven er uendret.";
|
|
4776
5415
|
this.open = true;
|
|
@@ -5113,10 +5752,14 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5113
5752
|
this.unmountNexi();
|
|
5114
5753
|
this.unmountAdyen();
|
|
5115
5754
|
for (const settle of [...this.pendingSuccessEmits]) settle();
|
|
5755
|
+
this.bottomSheet.disconnect();
|
|
5116
5756
|
super.disconnectedCallback();
|
|
5117
5757
|
}
|
|
5118
5758
|
updated(changed) {
|
|
5119
|
-
if (changed.has("open"))
|
|
5759
|
+
if (changed.has("open")) {
|
|
5760
|
+
this.dialogFocus.update(this.open);
|
|
5761
|
+
this.bottomSheet.update(this.open);
|
|
5762
|
+
}
|
|
5120
5763
|
if (changed.has("open") && this.open && this.items.length > 0 && this.availableShippingsList.length === 0) {
|
|
5121
5764
|
void this.loadAvailableShippings();
|
|
5122
5765
|
}
|
|
@@ -5569,10 +6212,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5569
6212
|
}
|
|
5570
6213
|
if (method === "vipps") {
|
|
5571
6214
|
this.vippsLoading = true;
|
|
6215
|
+
const redirect = beginPaymentRedirect({ method: "vipps", sponsorId: this.checkoutState?.sponsorId, source: "checkout" });
|
|
5572
6216
|
void this.startVippsFromCheckout().catch((err) => {
|
|
5573
6217
|
if (typeof console !== "undefined") {
|
|
5574
6218
|
console.warn("[VioCheckout] Vipps payment could not start:", err);
|
|
5575
6219
|
}
|
|
6220
|
+
redirect.fail(errorText(err));
|
|
5576
6221
|
this.paymentError = friendlyPaymentError(errorText(err));
|
|
5577
6222
|
}).finally(() => {
|
|
5578
6223
|
this.vippsLoading = false;
|
|
@@ -5657,12 +6302,14 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5657
6302
|
* `visibilitychange`) or the element leaves, the event goes out at once
|
|
5658
6303
|
* without the reference. Exactly one event per call. `afterwards` runs
|
|
5659
6304
|
* right after the event, so a path keeps the order it always had (the cart
|
|
5660
|
-
* 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.
|
|
5661
6308
|
*/
|
|
5662
6309
|
async emitPaymentSuccess(detail, checkoutId, sponsorId, afterwards) {
|
|
5663
6310
|
let done = false;
|
|
5664
6311
|
const sponsorAtStart = this.sponsorOfOrder(sponsorId, checkoutId);
|
|
5665
|
-
const finish = (
|
|
6312
|
+
const finish = (found = {}) => {
|
|
5666
6313
|
if (done) return;
|
|
5667
6314
|
done = true;
|
|
5668
6315
|
const named = this.sponsorOfOrder(sponsorId, checkoutId);
|
|
@@ -5671,7 +6318,11 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5671
6318
|
const event = new CustomEvent("vio:payment-success", {
|
|
5672
6319
|
bubbles: true,
|
|
5673
6320
|
composed: true,
|
|
5674
|
-
detail: buyerRef ? {
|
|
6321
|
+
detail: found.buyerRef || found.orderNumber ? {
|
|
6322
|
+
...withSponsor,
|
|
6323
|
+
...found.buyerRef ? { buyerRef: found.buyerRef } : {},
|
|
6324
|
+
...found.orderNumber ? { orderNumber: found.orderNumber } : {}
|
|
6325
|
+
} : withSponsor
|
|
5675
6326
|
});
|
|
5676
6327
|
this.dispatchEvent(event);
|
|
5677
6328
|
if (!this.isConnected && typeof window !== "undefined") {
|
|
@@ -5684,7 +6335,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5684
6335
|
};
|
|
5685
6336
|
const known = buyerRefOf(detail);
|
|
5686
6337
|
if (known || !checkoutId || !this.shouldAskBuyerRef(detail)) {
|
|
5687
|
-
finish(known);
|
|
6338
|
+
finish({ buyerRef: known });
|
|
5688
6339
|
return;
|
|
5689
6340
|
}
|
|
5690
6341
|
const settleEarly = () => finish();
|
|
@@ -5716,8 +6367,9 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5716
6367
|
* failed or has neither the reference nor an order yet (the webhook has not
|
|
5717
6368
|
* written the checkout), again at 1.2 s — all within 2.5 s. An order
|
|
5718
6369
|
* without a reference is a settled answer: the backend has none, asking
|
|
5719
|
-
* again cannot help.
|
|
5720
|
-
*
|
|
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.
|
|
5721
6373
|
*/
|
|
5722
6374
|
async askBuyerRef(checkoutId, sponsorId, stopped) {
|
|
5723
6375
|
const started = Date.now();
|
|
@@ -5730,19 +6382,20 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5730
6382
|
for (const at of [0, _VioCheckout.BUYER_REF_RETRY_AT_MS]) {
|
|
5731
6383
|
const wait = Math.min(at - (Date.now() - started), remaining());
|
|
5732
6384
|
if (wait > 0) await Promise.race([new Promise((resolve) => setTimeout(resolve, wait)), timeUp]);
|
|
5733
|
-
if (stopped() || remaining() <= 0) return
|
|
6385
|
+
if (stopped() || remaining() <= 0) return {};
|
|
5734
6386
|
try {
|
|
5735
6387
|
const reference = await Promise.race([
|
|
5736
6388
|
Vio.checkout.getOrderReference(checkoutId, sponsorId || void 0),
|
|
5737
6389
|
timeUp
|
|
5738
6390
|
]);
|
|
5739
|
-
if (reference === null) return
|
|
5740
|
-
|
|
5741
|
-
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;
|
|
5742
6395
|
} catch {
|
|
5743
6396
|
}
|
|
5744
6397
|
}
|
|
5745
|
-
return
|
|
6398
|
+
return {};
|
|
5746
6399
|
} finally {
|
|
5747
6400
|
clearTimeout(deadline);
|
|
5748
6401
|
}
|
|
@@ -6006,22 +6659,22 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
6006
6659
|
if (email) details.push(["Kvittering til", email]);
|
|
6007
6660
|
const sponsor = this.checkoutSponsorIdentity();
|
|
6008
6661
|
return html`
|
|
6009
|
-
<section class="confirmation">
|
|
6662
|
+
<section class="confirmation" part="confirmation">
|
|
6010
6663
|
${usableLogo(sponsor) ? html`<div class="confirm-brand" part="sponsor">
|
|
6011
6664
|
${renderSponsorMark(this, sponsor, { logo: "confirm-brand-logo", name: "confirm-brand-name" })}
|
|
6012
6665
|
</div>` : ""}
|
|
6013
6666
|
<header class="confirm-head">
|
|
6014
6667
|
<div class="confirm-check" aria-hidden="true">✓</div>
|
|
6015
6668
|
<div>
|
|
6016
|
-
<h3 class="confirm-title">Takk for bestillingen!</h3>
|
|
6017
|
-
${sponsor?.name ? html`<p class="confirm-sponsor">Du har handlet hos <strong>${sponsor.name}</strong>.</p>` : ""}
|
|
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>` : ""}
|
|
6018
6671
|
<p class="confirm-text">
|
|
6019
6672
|
Betalingen er bekreftet.${email ? html` Du får en ordrebekreftelse på e-post.` : ""}
|
|
6020
6673
|
</p>
|
|
6021
6674
|
</div>
|
|
6022
6675
|
</header>
|
|
6023
6676
|
|
|
6024
|
-
<dl class="confirm-details">
|
|
6677
|
+
<dl class="confirm-details" part="confirm-details">
|
|
6025
6678
|
${details.map(
|
|
6026
6679
|
([k, v]) => html`<div><dt>${k}</dt><dd class=${k === o?.providerRef?.label ? "ref" : ""}>${v}</dd></div>`
|
|
6027
6680
|
)}
|
|
@@ -6431,12 +7084,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
6431
7084
|
for (const delay of _VioCheckout.NEXI_DETAILS_DELAYS_MS) {
|
|
6432
7085
|
if (delay) await new Promise((r) => setTimeout(r, delay));
|
|
6433
7086
|
const o = await Vio.checkout.getNexiOrder(checkoutId, sponsorId).catch(() => null);
|
|
6434
|
-
const
|
|
6435
|
-
if (!
|
|
7087
|
+
const current2 = this.confirmedOrder;
|
|
7088
|
+
if (!current2 || current2.providerRef?.value !== paymentId) return;
|
|
6436
7089
|
if (!o) continue;
|
|
6437
7090
|
const paidWith = o.payment_method ? o.card_last4 ? `${o.payment_method} \u2022\u2022\u2022\u2022 ${o.card_last4}` : o.payment_method : void 0;
|
|
6438
7091
|
this.confirmedOrder = {
|
|
6439
|
-
...
|
|
7092
|
+
...current2,
|
|
6440
7093
|
...paidWith ? { paidWith } : {},
|
|
6441
7094
|
...o.email ? { email: o.email } : {}
|
|
6442
7095
|
};
|
|
@@ -6464,15 +7117,15 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
6464
7117
|
const options = this.nexiShipping?.options ?? [];
|
|
6465
7118
|
if (options.length < 2) return "";
|
|
6466
7119
|
const currency = this.checkoutState?.currency || getGlobalCurrency();
|
|
6467
|
-
const
|
|
7120
|
+
const current2 = this.nexiShipping?.shipping_id;
|
|
6468
7121
|
return html`
|
|
6469
7122
|
<div class="ship-select nexi-ship" role="radiogroup" aria-label="Frakt">
|
|
6470
7123
|
${options.map(
|
|
6471
7124
|
(o) => html`
|
|
6472
7125
|
<button
|
|
6473
|
-
class="ship-opt ${
|
|
7126
|
+
class="ship-opt ${current2 === o.id ? "active" : ""}"
|
|
6474
7127
|
role="radio"
|
|
6475
|
-
aria-checked=${
|
|
7128
|
+
aria-checked=${current2 === o.id}
|
|
6476
7129
|
?disabled=${this.nexiPicking}
|
|
6477
7130
|
@click=${() => void this.onPickNexiShipping(o.id)}
|
|
6478
7131
|
>
|
|
@@ -7446,5 +8099,5 @@ function registerVioElements() {
|
|
|
7446
8099
|
}
|
|
7447
8100
|
|
|
7448
8101
|
export { VioCart, VioCheckout, VioProduct, VioProductCarousel, VioProductDetail, VioTokens, applyVioTheme, registerVioElements };
|
|
7449
|
-
//# sourceMappingURL=chunk-
|
|
7450
|
-
//# sourceMappingURL=chunk-
|
|
8102
|
+
//# sourceMappingURL=chunk-Q2NALS7P.js.map
|
|
8103
|
+
//# sourceMappingURL=chunk-Q2NALS7P.js.map
|