@seatlayer/js 0.47.1 → 0.47.2
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/dist/index.cjs +296 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -1
- package/dist/index.d.ts +91 -1
- package/dist/index.js +293 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -577,6 +577,10 @@ var PubApi = class {
|
|
|
577
577
|
paymentOptions(key) {
|
|
578
578
|
return this.request(`/pub/events/${encodeURIComponent(key)}/payment-options`);
|
|
579
579
|
}
|
|
580
|
+
/** Server-resolved active ticket offers and category prices. */
|
|
581
|
+
availability(key, live = false) {
|
|
582
|
+
return this.request(`/pub/events/${encodeURIComponent(key)}/availability${live ? "?live=1" : ""}`);
|
|
583
|
+
}
|
|
580
584
|
/**
|
|
581
585
|
* Turn a live hold into an order and start a payment.
|
|
582
586
|
*
|
|
@@ -1073,7 +1077,7 @@ var SeatingChart = class {
|
|
|
1073
1077
|
this.tipEl.style.display = "none";
|
|
1074
1078
|
return;
|
|
1075
1079
|
}
|
|
1076
|
-
const
|
|
1080
|
+
const money2 = (() => {
|
|
1077
1081
|
try {
|
|
1078
1082
|
return new Intl.NumberFormat(void 0, { style: "currency", currency: details.currency }).format(details.price);
|
|
1079
1083
|
} catch {
|
|
@@ -1081,7 +1085,7 @@ var SeatingChart = class {
|
|
|
1081
1085
|
}
|
|
1082
1086
|
})();
|
|
1083
1087
|
const statusLine = details.status === "free" ? "" : `<div style="margin-top:5px;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:#fca5a5;font-weight:700">${details.status === "held" ? t("map.statusHeld") : t("map.statusTaken")}</div>`;
|
|
1084
|
-
this.tipEl.innerHTML = `<div style="font-weight:700;font-size:13px">${details.label}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px;color:#c7cddc"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span>${details.categoryLabel}</span><span style="margin-left:auto;font-weight:700;color:#fff">${
|
|
1088
|
+
this.tipEl.innerHTML = `<div style="font-weight:700;font-size:13px">${details.label}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px;color:#c7cddc"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span>${details.categoryLabel}</span><span style="margin-left:auto;font-weight:700;color:#fff">${money2}</span></div>` + statusLine;
|
|
1085
1089
|
this.tipEl.style.display = "block";
|
|
1086
1090
|
this.placeTooltip();
|
|
1087
1091
|
}
|
|
@@ -1989,6 +1993,136 @@ import {
|
|
|
1989
1993
|
planPanoramaDelivery,
|
|
1990
1994
|
schedulePanoramaUpgrade
|
|
1991
1995
|
} from "@seatlayer/core/view/panoramaDelivery";
|
|
1996
|
+
|
|
1997
|
+
// src/offerAvailability.ts
|
|
1998
|
+
var SALE_STATES = [
|
|
1999
|
+
"on-sale",
|
|
2000
|
+
"low",
|
|
2001
|
+
"sold-out",
|
|
2002
|
+
"presale",
|
|
2003
|
+
"closed"
|
|
2004
|
+
];
|
|
2005
|
+
function money(value) {
|
|
2006
|
+
if (value === null || value === void 0) return null;
|
|
2007
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2008
|
+
}
|
|
2009
|
+
function timestamp(value) {
|
|
2010
|
+
if (value === null || value === void 0) return null;
|
|
2011
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2012
|
+
}
|
|
2013
|
+
function parseSummary(value) {
|
|
2014
|
+
if (value == null) return null;
|
|
2015
|
+
if (typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2016
|
+
const source = value;
|
|
2017
|
+
const count = source.count;
|
|
2018
|
+
const index = source.index;
|
|
2019
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 1) return void 0;
|
|
2020
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count < index) return void 0;
|
|
2021
|
+
const remaining = source.remaining;
|
|
2022
|
+
if (remaining != null && (typeof remaining !== "number" || !Number.isInteger(remaining) || remaining < 0)) {
|
|
2023
|
+
return void 0;
|
|
2024
|
+
}
|
|
2025
|
+
const result = {
|
|
2026
|
+
index,
|
|
2027
|
+
count,
|
|
2028
|
+
remaining: remaining == null ? null : remaining
|
|
2029
|
+
};
|
|
2030
|
+
if (source.id !== void 0) {
|
|
2031
|
+
if (typeof source.id !== "string" || !source.id.trim()) return void 0;
|
|
2032
|
+
result.id = source.id.trim();
|
|
2033
|
+
}
|
|
2034
|
+
if (source.name !== void 0) {
|
|
2035
|
+
if (typeof source.name !== "string" || !source.name.trim()) return void 0;
|
|
2036
|
+
result.name = source.name.trim();
|
|
2037
|
+
}
|
|
2038
|
+
if (source.categoryKey !== void 0) {
|
|
2039
|
+
if (source.categoryKey !== null && (typeof source.categoryKey !== "string" || !source.categoryKey.trim())) {
|
|
2040
|
+
return void 0;
|
|
2041
|
+
}
|
|
2042
|
+
result.categoryKey = source.categoryKey == null ? null : source.categoryKey.trim();
|
|
2043
|
+
}
|
|
2044
|
+
for (const key of ["startsAt", "endsAt"]) {
|
|
2045
|
+
if (source[key] === void 0) continue;
|
|
2046
|
+
const parsed = timestamp(source[key]);
|
|
2047
|
+
if (parsed === void 0) return void 0;
|
|
2048
|
+
result[key] = parsed;
|
|
2049
|
+
}
|
|
2050
|
+
return result;
|
|
2051
|
+
}
|
|
2052
|
+
function parseTicketOfferAvailability(body) {
|
|
2053
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
2054
|
+
const raw = body;
|
|
2055
|
+
const state = SALE_STATES.find((candidate) => candidate === raw.state);
|
|
2056
|
+
if (!state) return null;
|
|
2057
|
+
const fromPrice = money(raw.fromPrice);
|
|
2058
|
+
const previousPrice = money(raw.previousPrice);
|
|
2059
|
+
if (fromPrice === void 0 || previousPrice === void 0) return null;
|
|
2060
|
+
const currency = raw.currency == null ? null : typeof raw.currency === "string" && raw.currency.trim() ? raw.currency.trim() : void 0;
|
|
2061
|
+
if (currency === void 0) return null;
|
|
2062
|
+
const release = parseSummary(raw.release);
|
|
2063
|
+
if (release === void 0) return null;
|
|
2064
|
+
const upcoming = raw.upcoming === void 0 ? null : parseSummary(raw.upcoming);
|
|
2065
|
+
if (upcoming === void 0) return null;
|
|
2066
|
+
let prices = [];
|
|
2067
|
+
if (raw.prices != null) {
|
|
2068
|
+
if (!Array.isArray(raw.prices)) return null;
|
|
2069
|
+
const parsed = [];
|
|
2070
|
+
for (const entry of raw.prices) {
|
|
2071
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
2072
|
+
const row = entry;
|
|
2073
|
+
const categoryKey = typeof row.categoryKey === "string" ? row.categoryKey.trim() : "";
|
|
2074
|
+
if (!categoryKey) return null;
|
|
2075
|
+
const price = money(row.price);
|
|
2076
|
+
const previous = money(row.previousPrice);
|
|
2077
|
+
if (price === void 0 || price === null || previous === void 0) return null;
|
|
2078
|
+
const item = { categoryKey, price, previousPrice: previous };
|
|
2079
|
+
if (row.offerId !== void 0) {
|
|
2080
|
+
if (typeof row.offerId !== "string" || !row.offerId.trim()) return null;
|
|
2081
|
+
item.offerId = row.offerId.trim();
|
|
2082
|
+
}
|
|
2083
|
+
if (row.offerName !== void 0) {
|
|
2084
|
+
if (typeof row.offerName !== "string" || !row.offerName.trim()) return null;
|
|
2085
|
+
item.offerName = row.offerName.trim();
|
|
2086
|
+
}
|
|
2087
|
+
if (row.remaining !== void 0) {
|
|
2088
|
+
if (row.remaining !== null && (typeof row.remaining !== "number" || !Number.isInteger(row.remaining) || row.remaining < 0)) return null;
|
|
2089
|
+
item.remaining = row.remaining == null ? null : row.remaining;
|
|
2090
|
+
}
|
|
2091
|
+
for (const key of ["startsAt", "endsAt"]) {
|
|
2092
|
+
if (row[key] === void 0) continue;
|
|
2093
|
+
const at = timestamp(row[key]);
|
|
2094
|
+
if (at === void 0) return null;
|
|
2095
|
+
item[key] = at;
|
|
2096
|
+
}
|
|
2097
|
+
parsed.push(item);
|
|
2098
|
+
}
|
|
2099
|
+
prices = parsed;
|
|
2100
|
+
}
|
|
2101
|
+
return { state, fromPrice, previousPrice, currency, release, upcoming, prices };
|
|
2102
|
+
}
|
|
2103
|
+
function nextOfferTransitionAt(availability, now) {
|
|
2104
|
+
if (!availability) return null;
|
|
2105
|
+
let next = null;
|
|
2106
|
+
const consider = (at) => {
|
|
2107
|
+
if (at != null && at > now && (next === null || at < next)) next = at;
|
|
2108
|
+
};
|
|
2109
|
+
for (const summary of [availability.release, availability.upcoming]) {
|
|
2110
|
+
consider(summary?.startsAt);
|
|
2111
|
+
consider(summary?.endsAt);
|
|
2112
|
+
}
|
|
2113
|
+
for (const price of availability.prices) {
|
|
2114
|
+
consider(price.startsAt);
|
|
2115
|
+
consider(price.endsAt);
|
|
2116
|
+
}
|
|
2117
|
+
return next;
|
|
2118
|
+
}
|
|
2119
|
+
function ticketOfferPrices(availability) {
|
|
2120
|
+
const map = {};
|
|
2121
|
+
for (const entry of availability?.prices ?? []) map[entry.categoryKey] = entry.price;
|
|
2122
|
+
return map;
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// src/SeatPicker.ts
|
|
1992
2126
|
var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
|
|
1993
2127
|
var DEFAULT_MAX_SELECTION2 = 10;
|
|
1994
2128
|
var EXTEND_PROMPT_MS = 6e4;
|
|
@@ -2209,6 +2343,15 @@ var CSS = (
|
|
|
2209
2343
|
.sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
2210
2344
|
|
|
2211
2345
|
/* price panel \u2014 one compact filter control replaces the wrapping price-chip row. */
|
|
2346
|
+
.sl-offer{display:none;margin:12px 14px 2px;padding:12px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));
|
|
2347
|
+
border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
|
|
2348
|
+
.sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
|
|
2349
|
+
.sl-offer-copy{min-width:0}.sl-offer-kicker{display:block;font-size:9px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-accent)}
|
|
2350
|
+
.sl-offer-name{display:block;margin-top:2px;font-size:13px;font-weight:800;line-height:1.3}.sl-offer-line{display:block;margin-top:3px;font-size:11px;line-height:1.35;color:var(--sl-muted)}
|
|
2351
|
+
.sl-offer-info{position:relative;flex:none}.sl-offer-info>summary{list-style:none;width:25px;height:25px;border:1px solid var(--sl-line);border-radius:999px;
|
|
2352
|
+
display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
|
|
2353
|
+
.sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent)}
|
|
2354
|
+
.sl-offer-detail{margin-top:10px;padding-top:9px;border-top:1px solid var(--sl-line);font-size:10.5px;line-height:1.45;color:var(--sl-muted)}
|
|
2212
2355
|
.sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
|
|
2213
2356
|
.sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
2214
2357
|
.sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
|
|
@@ -2219,6 +2362,7 @@ var CSS = (
|
|
|
2219
2362
|
padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
|
|
2220
2363
|
.sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
|
|
2221
2364
|
.sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
|
|
2365
|
+
.sl-price-was{margin-left:auto;color:var(--sl-muted);font-size:10px;text-decoration:line-through}.sl-price-offer{display:block;color:var(--sl-accent);font-size:9px;font-weight:750}
|
|
2222
2366
|
.sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
|
|
2223
2367
|
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
2224
2368
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
@@ -2944,10 +3088,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
2944
3088
|
this.ro = null;
|
|
2945
3089
|
this.holdTimer = null;
|
|
2946
3090
|
this.toastTimer = null;
|
|
3091
|
+
this.offerRefreshTimer = null;
|
|
3092
|
+
/** Armed only when the offer schedule has a known future transition (or as a
|
|
3093
|
+
* bounded retry after a failed read) — never a fixed-cadence poll. */
|
|
3094
|
+
this.offerBoundaryTimer = null;
|
|
3095
|
+
this.offerVisibilityHandler = null;
|
|
2947
3096
|
/** Short-lived UI motion timers; all are cancelled on destroy. */
|
|
2948
3097
|
this.motionTimers = /* @__PURE__ */ new Set();
|
|
2949
3098
|
// state
|
|
2950
3099
|
this.currency = "USD";
|
|
3100
|
+
this.eventTimezone = null;
|
|
3101
|
+
this.offerAvailability = null;
|
|
2951
3102
|
this.hold = null;
|
|
2952
3103
|
/** Latest server expiry for the open hold (moves on extend). */
|
|
2953
3104
|
this.holdExpiresAt = 0;
|
|
@@ -3061,6 +3212,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3061
3212
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
3062
3213
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
3063
3214
|
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
3215
|
+
this.hostPricing = options.pricing;
|
|
3064
3216
|
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
3065
3217
|
this.access = options.transport ? null : createBuyerAccessContext(options, {
|
|
3066
3218
|
onExpired: (event) => {
|
|
@@ -3102,6 +3254,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3102
3254
|
},
|
|
3103
3255
|
onStatusChange: () => {
|
|
3104
3256
|
this.syncPrices();
|
|
3257
|
+
this.scheduleOfferRefresh(true);
|
|
3105
3258
|
this.evictTakenSelections();
|
|
3106
3259
|
this.detectBooked();
|
|
3107
3260
|
this.refreshMinimap();
|
|
@@ -3523,6 +3676,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3523
3676
|
</div>
|
|
3524
3677
|
<div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
|
|
3525
3678
|
<div class="sl-filters" data-ref="filters"></div>
|
|
3679
|
+
<div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
|
|
3526
3680
|
<div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
|
|
3527
3681
|
<div class="sl-prices" data-ref="prices"></div>
|
|
3528
3682
|
<div class="sl-live" data-ref="live" role="status" aria-live="polite"><span class="dot" aria-hidden="true"></span><span data-ref="liveText">Live availability \u2014 seats update in real time</span></div>
|
|
@@ -3543,6 +3697,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
3543
3697
|
this.els[el.dataset.ref] = el;
|
|
3544
3698
|
});
|
|
3545
3699
|
this.mapHost = this.els.map;
|
|
3700
|
+
void this.refreshOfferAvailability(false);
|
|
3701
|
+
if (this.api.availability) {
|
|
3702
|
+
this.offerVisibilityHandler = () => {
|
|
3703
|
+
if (!document.hidden && !this.destroyed) void this.refreshOfferAvailability(false);
|
|
3704
|
+
};
|
|
3705
|
+
document.addEventListener("visibilitychange", this.offerVisibilityHandler);
|
|
3706
|
+
}
|
|
3546
3707
|
const applyLayout = () => {
|
|
3547
3708
|
const w = root.clientWidth;
|
|
3548
3709
|
if (w <= 0) return;
|
|
@@ -3662,6 +3823,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3662
3823
|
const chartTheme = this.controller.doc?.theme;
|
|
3663
3824
|
Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
3664
3825
|
this.currency = info.currency ?? this.opts.currency ?? "USD";
|
|
3826
|
+
this.eventTimezone = info.timezone ?? null;
|
|
3665
3827
|
const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl;
|
|
3666
3828
|
if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
|
|
3667
3829
|
else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
|
|
@@ -5002,6 +5164,119 @@ var SeatPicker = class _SeatPicker {
|
|
|
5002
5164
|
return `${n} ${this.currency}`;
|
|
5003
5165
|
}
|
|
5004
5166
|
}
|
|
5167
|
+
/**
|
|
5168
|
+
* Sleep until the offer schedule's next known transition, then re-read.
|
|
5169
|
+
*
|
|
5170
|
+
* A far-away boundary is capped: the wake re-reads, learns the (unchanged)
|
|
5171
|
+
* schedule, and re-arms — so a picker left open for days still tracks an
|
|
5172
|
+
* organizer's schedule edits at a cost of one request every few hours. No
|
|
5173
|
+
* future transition means no timer at all; an event with no releases does
|
|
5174
|
+
* zero background traffic. A wake in a hidden tab fetches nothing — the
|
|
5175
|
+
* visibilitychange handler owns catching that tab up.
|
|
5176
|
+
*/
|
|
5177
|
+
scheduleOfferBoundary(availability) {
|
|
5178
|
+
if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
|
|
5179
|
+
this.offerBoundaryTimer = null;
|
|
5180
|
+
if (!this.api.availability || this.destroyed) return;
|
|
5181
|
+
const now = Date.now();
|
|
5182
|
+
const boundary = nextOfferTransitionAt(availability, now);
|
|
5183
|
+
if (boundary == null) return;
|
|
5184
|
+
const MAX_SLEEP_MS = 6 * 36e5;
|
|
5185
|
+
const delay = Math.min(Math.max(boundary - now + 1e3, 1e3), MAX_SLEEP_MS);
|
|
5186
|
+
this.offerBoundaryTimer = setTimeout(() => {
|
|
5187
|
+
this.offerBoundaryTimer = null;
|
|
5188
|
+
if (document.hidden) return;
|
|
5189
|
+
void this.refreshOfferAvailability(false);
|
|
5190
|
+
}, delay);
|
|
5191
|
+
}
|
|
5192
|
+
/** Debounce the no-store offer read behind a burst of seat-status frames. */
|
|
5193
|
+
scheduleOfferRefresh(live) {
|
|
5194
|
+
if (!this.api.availability || this.destroyed) return;
|
|
5195
|
+
if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
|
|
5196
|
+
this.offerRefreshTimer = setTimeout(() => {
|
|
5197
|
+
this.offerRefreshTimer = null;
|
|
5198
|
+
void this.refreshOfferAvailability(live);
|
|
5199
|
+
}, live ? 180 : 0);
|
|
5200
|
+
}
|
|
5201
|
+
/**
|
|
5202
|
+
* Pull the server's resolved answer. A failed refresh keeps the last truthful
|
|
5203
|
+
* answer: flashing back to a chart price while checkout still charges an
|
|
5204
|
+
* offer is worse than a temporarily stale remaining count.
|
|
5205
|
+
*/
|
|
5206
|
+
async refreshOfferAvailability(live) {
|
|
5207
|
+
if (!this.api.availability || this.destroyed) return;
|
|
5208
|
+
try {
|
|
5209
|
+
const body = await this.api.availability(this.opts.event, live);
|
|
5210
|
+
if (this.destroyed) return;
|
|
5211
|
+
const availability = parseTicketOfferAvailability(body);
|
|
5212
|
+
if (!availability) return;
|
|
5213
|
+
this.offerAvailability = availability;
|
|
5214
|
+
this.scheduleOfferBoundary(availability);
|
|
5215
|
+
const server = ticketOfferPrices(availability);
|
|
5216
|
+
const merged = { ...this.hostPricing?.prices ?? {}, ...server };
|
|
5217
|
+
const pricing = Object.keys(merged).length > 0 || this.hostPricing?.formatter ? { prices: merged, ...this.hostPricing?.formatter ? { formatter: this.hostPricing.formatter } : {} } : void 0;
|
|
5218
|
+
this.setPricing(pricing);
|
|
5219
|
+
this.syncOffer();
|
|
5220
|
+
this.opts.onOfferAvailabilityChange?.(availability);
|
|
5221
|
+
} catch {
|
|
5222
|
+
if (!this.destroyed && !this.offerBoundaryTimer && !document.hidden) {
|
|
5223
|
+
this.offerBoundaryTimer = setTimeout(() => {
|
|
5224
|
+
this.offerBoundaryTimer = null;
|
|
5225
|
+
if (document.hidden) return;
|
|
5226
|
+
void this.refreshOfferAvailability(false);
|
|
5227
|
+
}, 3e4);
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
offerPrice(categoryKey) {
|
|
5232
|
+
if (!categoryKey) return null;
|
|
5233
|
+
return this.offerAvailability?.prices.find((entry) => entry.categoryKey === categoryKey) ?? null;
|
|
5234
|
+
}
|
|
5235
|
+
/** The compact current/upcoming offer card above Ticket prices. */
|
|
5236
|
+
syncOffer() {
|
|
5237
|
+
const host = this.els.offer;
|
|
5238
|
+
if (!host) return;
|
|
5239
|
+
const availability = this.offerAvailability;
|
|
5240
|
+
const active = availability?.release ?? null;
|
|
5241
|
+
const upcoming = !active ? availability?.upcoming ?? null : null;
|
|
5242
|
+
if (!availability || availability.state === "closed" || availability.state === "sold-out" || !active && !upcoming) {
|
|
5243
|
+
host.classList.remove("has");
|
|
5244
|
+
host.replaceChildren();
|
|
5245
|
+
return;
|
|
5246
|
+
}
|
|
5247
|
+
const offer = active ?? upcoming;
|
|
5248
|
+
const main = document.createElement("div");
|
|
5249
|
+
main.className = "sl-offer-main";
|
|
5250
|
+
const copy = document.createElement("div");
|
|
5251
|
+
copy.className = "sl-offer-copy";
|
|
5252
|
+
const kicker = document.createElement("span");
|
|
5253
|
+
kicker.className = "sl-offer-kicker";
|
|
5254
|
+
kicker.textContent = active ? "Current ticket offer" : "Upcoming ticket offer";
|
|
5255
|
+
const name = document.createElement("strong");
|
|
5256
|
+
name.className = "sl-offer-name";
|
|
5257
|
+
name.textContent = offer.name || (active ? "Current offer" : "Scheduled offer");
|
|
5258
|
+
const line = document.createElement("span");
|
|
5259
|
+
line.className = "sl-offer-line";
|
|
5260
|
+
const facts = [];
|
|
5261
|
+
if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
|
|
5262
|
+
if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
|
|
5263
|
+
if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
|
|
5264
|
+
if (upcoming?.startsAt != null) facts.push(`starts ${formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale)}`);
|
|
5265
|
+
line.textContent = facts.join(" \xB7 ");
|
|
5266
|
+
copy.append(kicker, name, line);
|
|
5267
|
+
const info = document.createElement("details");
|
|
5268
|
+
info.className = "sl-offer-info";
|
|
5269
|
+
const summary = document.createElement("summary");
|
|
5270
|
+
summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
|
|
5271
|
+
summary.textContent = "i";
|
|
5272
|
+
const detail = document.createElement("div");
|
|
5273
|
+
detail.className = "sl-offer-detail";
|
|
5274
|
+
detail.textContent = active ? `This price applies automatically to eligible seats. Tickets in active carts temporarily reduce the available quantity; released or expired holds return it. When the offer ends, the next matching offer or normal ticket price takes over.` : `Tickets are available at their normal price now. This scheduled offer will apply automatically to eligible seats when it starts.`;
|
|
5275
|
+
info.append(summary, detail);
|
|
5276
|
+
main.append(copy, info);
|
|
5277
|
+
host.replaceChildren(main);
|
|
5278
|
+
host.classList.add("has");
|
|
5279
|
+
}
|
|
5005
5280
|
/**
|
|
5006
5281
|
* The price the buyer will actually pay for a category (+tier): the host's
|
|
5007
5282
|
* `pricing` override when present, else the chart's stored price. Every
|
|
@@ -5028,9 +5303,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
5028
5303
|
this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
|
|
5029
5304
|
this.els.prices.innerHTML = shown.map((c) => {
|
|
5030
5305
|
const price = this.catPrice(c);
|
|
5306
|
+
const offer = this.offerPrice(c.key);
|
|
5307
|
+
const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
|
|
5031
5308
|
const active = this.focusedCatKey === c.key;
|
|
5032
5309
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
5033
|
-
return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${c.key}" role="button" tabindex="0" aria-pressed="${active}" title="${active ? "Show all seats" : `Show ${c.label} seats on the map`}"><span class="sl-dot" style="background:${c.color}"></span><span class="sl-price-label">${c.label}</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
5310
|
+
return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${escapeOption(c.key)}" role="button" tabindex="0" aria-pressed="${active}" title="${escapeOption(active ? "Show all seats" : `Show ${c.label} seats on the map`)}"><span class="sl-dot" style="background:${escapeOption(c.color)}"></span><span class="sl-price-label">${escapeOption(c.label)}` + (offer?.offerName ? `<small class="sl-price-offer">${escapeOption(offer.offerName)}</small>` : "") + `</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
5034
5311
|
}).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? `Show all ${doc.categories.length} ticket types` : "Show fewer") + `</button>` : "") + `<div class="sl-status-key" aria-label="Seat status legend"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>Temporarily held</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>Sold</span></div>`;
|
|
5035
5312
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
5036
5313
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
@@ -5686,6 +5963,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5686
5963
|
}
|
|
5687
5964
|
emitHoldChange() {
|
|
5688
5965
|
const hold = this.hold;
|
|
5966
|
+
this.scheduleOfferRefresh(true);
|
|
5689
5967
|
this.opts.onHoldChange?.(
|
|
5690
5968
|
hold,
|
|
5691
5969
|
hold?.seats ?? [],
|
|
@@ -6415,6 +6693,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
6415
6693
|
flashOnLiveChange: true,
|
|
6416
6694
|
onStatusChange: () => {
|
|
6417
6695
|
this.syncPrices();
|
|
6696
|
+
this.scheduleOfferRefresh(true);
|
|
6418
6697
|
this.detectBooked();
|
|
6419
6698
|
this.refreshMinimap();
|
|
6420
6699
|
this.pushAvailabilityTo3d();
|
|
@@ -6550,6 +6829,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
6550
6829
|
this.stopHoldTimer();
|
|
6551
6830
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
6552
6831
|
if (this.liveTimer) clearTimeout(this.liveTimer);
|
|
6832
|
+
if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
|
|
6833
|
+
if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
|
|
6834
|
+
this.offerRefreshTimer = null;
|
|
6835
|
+
this.offerBoundaryTimer = null;
|
|
6836
|
+
if (this.offerVisibilityHandler) {
|
|
6837
|
+
document.removeEventListener("visibilitychange", this.offerVisibilityHandler);
|
|
6838
|
+
this.offerVisibilityHandler = null;
|
|
6839
|
+
}
|
|
6553
6840
|
for (const timer of this.motionTimers) clearTimeout(timer);
|
|
6554
6841
|
this.motionTimers.clear();
|
|
6555
6842
|
this.ro?.disconnect();
|
|
@@ -6690,10 +6977,12 @@ export {
|
|
|
6690
6977
|
markerOf,
|
|
6691
6978
|
mutationCount,
|
|
6692
6979
|
needsMoveConfirmation,
|
|
6980
|
+
parseTicketOfferAvailability,
|
|
6693
6981
|
planAssignment,
|
|
6694
6982
|
retryAfterCopy,
|
|
6695
6983
|
selectionSources,
|
|
6696
6984
|
stateBadge,
|
|
6697
|
-
suggestMarker
|
|
6985
|
+
suggestMarker,
|
|
6986
|
+
ticketOfferPrices
|
|
6698
6987
|
};
|
|
6699
6988
|
//# sourceMappingURL=index.js.map
|