@seatlayer/js 0.47.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/{channelsMode-33ZLORVF.js → channelsMode-NCH5GYHN.js} +3 -3
- package/dist/{chunk-X2R4AQSZ.js → chunk-H4OJF6LE.js} +176 -7
- package/dist/chunk-H4OJF6LE.js.map +1 -0
- package/dist/{chunk-DMEFXZIL.js → chunk-KNMZQZXR.js} +2 -2
- package/dist/{chunk-CF5MS7UR.js → chunk-Q5QT3YLA.js} +186 -59
- package/dist/chunk-Q5QT3YLA.js.map +1 -0
- package/dist/index.cjs +851 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -5
- package/dist/index.d.ts +108 -5
- package/dist/index.js +493 -36
- package/dist/index.js.map +1 -1
- package/dist/{manager-CAIPkUYl.d.cts → manager-DEjbKiqJ.d.cts} +200 -17
- package/dist/{manager-CAIPkUYl.d.ts → manager-DEjbKiqJ.d.ts} +200 -17
- package/dist/manager.cjs +358 -62
- package/dist/manager.cjs.map +1 -1
- package/dist/manager.d.cts +1 -1
- package/dist/manager.d.ts +1 -1
- package/dist/manager.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-CF5MS7UR.js.map +0 -1
- package/dist/chunk-X2R4AQSZ.js.map +0 -1
- /package/dist/{channelsMode-33ZLORVF.js.map → channelsMode-NCH5GYHN.js.map} +0 -0
- /package/dist/{chunk-DMEFXZIL.js.map → chunk-KNMZQZXR.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SeatManager
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Q5QT3YLA.js";
|
|
4
4
|
import {
|
|
5
5
|
ACCESS_LINK_DEFAULTS,
|
|
6
6
|
ChannelsMode,
|
|
@@ -28,11 +28,11 @@ import {
|
|
|
28
28
|
selectionSources,
|
|
29
29
|
stateBadge,
|
|
30
30
|
suggestMarker
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-KNMZQZXR.js";
|
|
32
32
|
import {
|
|
33
33
|
ManageApi,
|
|
34
34
|
ManageApiError
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-H4OJF6LE.js";
|
|
36
36
|
import {
|
|
37
37
|
__privateAdd,
|
|
38
38
|
__privateGet,
|
|
@@ -524,9 +524,54 @@ var PubApi = class {
|
|
|
524
524
|
}
|
|
525
525
|
return data;
|
|
526
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Binary counterpart to `request`. Buyer media needs the same in-memory
|
|
529
|
+
* bearer/refresh rules as JSON, but returns bytes that the picker turns into
|
|
530
|
+
* a blob URL. The bearer stays in the Authorization header and is never
|
|
531
|
+
* appended to `path`.
|
|
532
|
+
*/
|
|
533
|
+
async requestBlob(path, retried = {}) {
|
|
534
|
+
const headers = {};
|
|
535
|
+
const authorization = await this.access?.authorization(retried.auth ? "unauthorized" : "initial");
|
|
536
|
+
if (authorization) headers.Authorization = authorization;
|
|
537
|
+
const res = await fetch(`${this.base}${path}`, { method: "GET", headers, credentials: "omit" });
|
|
538
|
+
if (res.ok) return res.blob();
|
|
539
|
+
const isJson = (res.headers.get("content-type") ?? "").includes("application/json");
|
|
540
|
+
const data = isJson ? await res.json().catch(() => null) : null;
|
|
541
|
+
const code = data?.code ?? data?.error;
|
|
542
|
+
if (this.access?.configured && (res.status === 401 || res.status === 403 || res.status === 422)) {
|
|
543
|
+
const refreshed = await this.access.handleFailure(res.status, code);
|
|
544
|
+
if (refreshed && !retried.auth) return this.requestBlob(path, { ...retried, auth: true });
|
|
545
|
+
}
|
|
546
|
+
let retryAfterS;
|
|
547
|
+
if (res.status === 429) {
|
|
548
|
+
retryAfterS = parseRetryAfter(res.headers.get("Retry-After"), data?.retryAfterSeconds) ?? DEFAULT_RATE_LIMIT_WAIT_S;
|
|
549
|
+
if (!retried.rateLimit && retryAfterS <= MAX_RATE_LIMIT_WAIT_S) {
|
|
550
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfterS * 1e3));
|
|
551
|
+
return this.requestBlob(path, { ...retried, rateLimit: true });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
throw new ApiError(
|
|
555
|
+
res.status,
|
|
556
|
+
data?.error ?? `request_failed_${res.status}`,
|
|
557
|
+
code,
|
|
558
|
+
void 0,
|
|
559
|
+
void 0,
|
|
560
|
+
retryAfterS
|
|
561
|
+
);
|
|
562
|
+
}
|
|
527
563
|
chart(key) {
|
|
528
564
|
return this.request(`/pub/events/${encodeURIComponent(key)}/chart`);
|
|
529
565
|
}
|
|
566
|
+
/** Authenticated bytes for an Event-scoped authored view image. */
|
|
567
|
+
asset(key, asset) {
|
|
568
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(asset)) {
|
|
569
|
+
return Promise.reject(new ApiError(404, "not_found", "not_found"));
|
|
570
|
+
}
|
|
571
|
+
return this.requestBlob(
|
|
572
|
+
`/pub/events/${encodeURIComponent(key)}/assets/${encodeURIComponent(asset)}`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
530
575
|
objects(key) {
|
|
531
576
|
return this.request(`/pub/events/${encodeURIComponent(key)}/objects?compact=1`);
|
|
532
577
|
}
|
|
@@ -577,6 +622,10 @@ var PubApi = class {
|
|
|
577
622
|
paymentOptions(key) {
|
|
578
623
|
return this.request(`/pub/events/${encodeURIComponent(key)}/payment-options`);
|
|
579
624
|
}
|
|
625
|
+
/** Server-resolved active ticket offers and category prices. */
|
|
626
|
+
availability(key, live = false) {
|
|
627
|
+
return this.request(`/pub/events/${encodeURIComponent(key)}/availability${live ? "?live=1" : ""}`);
|
|
628
|
+
}
|
|
580
629
|
/**
|
|
581
630
|
* Turn a live hold into an order and start a payment.
|
|
582
631
|
*
|
|
@@ -625,12 +674,11 @@ var PubApi = class {
|
|
|
625
674
|
/**
|
|
626
675
|
* What PickerController opens its own socket with.
|
|
627
676
|
*
|
|
628
|
-
* Empty for an access-scoped client: a
|
|
677
|
+
* Empty for an access-scoped client: a scoped audience authenticates with a
|
|
629
678
|
* subprotocol ticket, which a URL-only constructor cannot carry, so the SDK's
|
|
630
679
|
* BuyerRealtimeClient owns that socket instead and the controller skips its
|
|
631
680
|
* own (an empty URL is its documented "no live feed" contract). A tokenless
|
|
632
|
-
* public client returns exactly the URL it always has
|
|
633
|
-
* public picker's realtime path changes.
|
|
681
|
+
* Managed public client returns exactly the URL it always has.
|
|
634
682
|
*/
|
|
635
683
|
socketUrl(key) {
|
|
636
684
|
return this.accessScoped ? "" : this.subscribeUrl(key);
|
|
@@ -1073,7 +1121,7 @@ var SeatingChart = class {
|
|
|
1073
1121
|
this.tipEl.style.display = "none";
|
|
1074
1122
|
return;
|
|
1075
1123
|
}
|
|
1076
|
-
const
|
|
1124
|
+
const money2 = (() => {
|
|
1077
1125
|
try {
|
|
1078
1126
|
return new Intl.NumberFormat(void 0, { style: "currency", currency: details.currency }).format(details.price);
|
|
1079
1127
|
} catch {
|
|
@@ -1081,7 +1129,7 @@ var SeatingChart = class {
|
|
|
1081
1129
|
}
|
|
1082
1130
|
})();
|
|
1083
1131
|
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">${
|
|
1132
|
+
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
1133
|
this.tipEl.style.display = "block";
|
|
1086
1134
|
this.placeTooltip();
|
|
1087
1135
|
}
|
|
@@ -1989,6 +2037,211 @@ import {
|
|
|
1989
2037
|
planPanoramaDelivery,
|
|
1990
2038
|
schedulePanoramaUpgrade
|
|
1991
2039
|
} from "@seatlayer/core/view/panoramaDelivery";
|
|
2040
|
+
|
|
2041
|
+
// src/buyerAssets.ts
|
|
2042
|
+
var SAFE_ASSET = /^[a-zA-Z0-9._-]+$/;
|
|
2043
|
+
function buyerEventAssetReference(value) {
|
|
2044
|
+
let url;
|
|
2045
|
+
try {
|
|
2046
|
+
url = new URL(value, "https://seatlayer.invalid");
|
|
2047
|
+
} catch {
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
if (url.search || url.hash) return null;
|
|
2051
|
+
const match = /^\/pub\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
|
|
2052
|
+
if (!match) return null;
|
|
2053
|
+
try {
|
|
2054
|
+
const eventKey = decodeURIComponent(match[1]);
|
|
2055
|
+
const asset = decodeURIComponent(match[2]);
|
|
2056
|
+
if (!eventKey || !SAFE_ASSET.test(asset)) return null;
|
|
2057
|
+
return { eventKey, asset };
|
|
2058
|
+
} catch {
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
function looksLikeBuyerAsset(value) {
|
|
2063
|
+
try {
|
|
2064
|
+
return /^\/pub\/events\/[^/]+\/assets(?:\/|$)/.test(
|
|
2065
|
+
new URL(value, "https://seatlayer.invalid").pathname
|
|
2066
|
+
);
|
|
2067
|
+
} catch {
|
|
2068
|
+
return false;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
var BuyerAssetObjectUrls = class {
|
|
2072
|
+
constructor(eventKey, load) {
|
|
2073
|
+
this.eventKey = eventKey;
|
|
2074
|
+
this.load = load;
|
|
2075
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
2076
|
+
this.created = /* @__PURE__ */ new Set();
|
|
2077
|
+
this.disposed = false;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* External organizer/CDN URLs pass through unchanged. SeatLayer event assets
|
|
2081
|
+
* never do: they require the transport, and a reference for another Event is
|
|
2082
|
+
* refused instead of being loaded anonymously.
|
|
2083
|
+
*/
|
|
2084
|
+
resolve(reference) {
|
|
2085
|
+
const parsed = buyerEventAssetReference(reference);
|
|
2086
|
+
if (!parsed) {
|
|
2087
|
+
return Promise.resolve(looksLikeBuyerAsset(reference) ? null : reference);
|
|
2088
|
+
}
|
|
2089
|
+
if (parsed.eventKey !== this.eventKey || !this.load || this.disposed) return Promise.resolve(null);
|
|
2090
|
+
const existing = this.pending.get(reference);
|
|
2091
|
+
if (existing) return existing;
|
|
2092
|
+
const task = this.load(this.eventKey, parsed.asset).then((blob) => {
|
|
2093
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
2094
|
+
if (this.disposed) {
|
|
2095
|
+
URL.revokeObjectURL(objectUrl);
|
|
2096
|
+
return null;
|
|
2097
|
+
}
|
|
2098
|
+
this.created.add(objectUrl);
|
|
2099
|
+
return objectUrl;
|
|
2100
|
+
}).catch((error) => {
|
|
2101
|
+
this.pending.delete(reference);
|
|
2102
|
+
throw error;
|
|
2103
|
+
});
|
|
2104
|
+
this.pending.set(reference, task);
|
|
2105
|
+
return task;
|
|
2106
|
+
}
|
|
2107
|
+
dispose() {
|
|
2108
|
+
if (this.disposed) return;
|
|
2109
|
+
this.disposed = true;
|
|
2110
|
+
for (const url of this.created) URL.revokeObjectURL(url);
|
|
2111
|
+
this.created.clear();
|
|
2112
|
+
this.pending.clear();
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
|
|
2116
|
+
// src/offerAvailability.ts
|
|
2117
|
+
var SALE_STATES = [
|
|
2118
|
+
"on-sale",
|
|
2119
|
+
"low",
|
|
2120
|
+
"sold-out",
|
|
2121
|
+
"presale",
|
|
2122
|
+
"closed"
|
|
2123
|
+
];
|
|
2124
|
+
function money(value) {
|
|
2125
|
+
if (value === null || value === void 0) return null;
|
|
2126
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2127
|
+
}
|
|
2128
|
+
function timestamp(value) {
|
|
2129
|
+
if (value === null || value === void 0) return null;
|
|
2130
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2131
|
+
}
|
|
2132
|
+
function parseSummary(value) {
|
|
2133
|
+
if (value == null) return null;
|
|
2134
|
+
if (typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2135
|
+
const source = value;
|
|
2136
|
+
const count = source.count;
|
|
2137
|
+
const index = source.index;
|
|
2138
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 1) return void 0;
|
|
2139
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count < index) return void 0;
|
|
2140
|
+
const remaining = source.remaining;
|
|
2141
|
+
if (remaining != null && (typeof remaining !== "number" || !Number.isInteger(remaining) || remaining < 0)) {
|
|
2142
|
+
return void 0;
|
|
2143
|
+
}
|
|
2144
|
+
const result = {
|
|
2145
|
+
index,
|
|
2146
|
+
count,
|
|
2147
|
+
remaining: remaining == null ? null : remaining
|
|
2148
|
+
};
|
|
2149
|
+
if (source.id !== void 0) {
|
|
2150
|
+
if (typeof source.id !== "string" || !source.id.trim()) return void 0;
|
|
2151
|
+
result.id = source.id.trim();
|
|
2152
|
+
}
|
|
2153
|
+
if (source.name !== void 0) {
|
|
2154
|
+
if (typeof source.name !== "string" || !source.name.trim()) return void 0;
|
|
2155
|
+
result.name = source.name.trim();
|
|
2156
|
+
}
|
|
2157
|
+
if (source.categoryKey !== void 0) {
|
|
2158
|
+
if (source.categoryKey !== null && (typeof source.categoryKey !== "string" || !source.categoryKey.trim())) {
|
|
2159
|
+
return void 0;
|
|
2160
|
+
}
|
|
2161
|
+
result.categoryKey = source.categoryKey == null ? null : source.categoryKey.trim();
|
|
2162
|
+
}
|
|
2163
|
+
for (const key of ["startsAt", "endsAt"]) {
|
|
2164
|
+
if (source[key] === void 0) continue;
|
|
2165
|
+
const parsed = timestamp(source[key]);
|
|
2166
|
+
if (parsed === void 0) return void 0;
|
|
2167
|
+
result[key] = parsed;
|
|
2168
|
+
}
|
|
2169
|
+
return result;
|
|
2170
|
+
}
|
|
2171
|
+
function parseTicketOfferAvailability(body) {
|
|
2172
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
2173
|
+
const raw = body;
|
|
2174
|
+
const state = SALE_STATES.find((candidate) => candidate === raw.state);
|
|
2175
|
+
if (!state) return null;
|
|
2176
|
+
const fromPrice = money(raw.fromPrice);
|
|
2177
|
+
const previousPrice = money(raw.previousPrice);
|
|
2178
|
+
if (fromPrice === void 0 || previousPrice === void 0) return null;
|
|
2179
|
+
const currency = raw.currency == null ? null : typeof raw.currency === "string" && raw.currency.trim() ? raw.currency.trim() : void 0;
|
|
2180
|
+
if (currency === void 0) return null;
|
|
2181
|
+
const release = parseSummary(raw.release);
|
|
2182
|
+
if (release === void 0) return null;
|
|
2183
|
+
const upcoming = raw.upcoming === void 0 ? null : parseSummary(raw.upcoming);
|
|
2184
|
+
if (upcoming === void 0) return null;
|
|
2185
|
+
let prices = [];
|
|
2186
|
+
if (raw.prices != null) {
|
|
2187
|
+
if (!Array.isArray(raw.prices)) return null;
|
|
2188
|
+
const parsed = [];
|
|
2189
|
+
for (const entry of raw.prices) {
|
|
2190
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
2191
|
+
const row = entry;
|
|
2192
|
+
const categoryKey = typeof row.categoryKey === "string" ? row.categoryKey.trim() : "";
|
|
2193
|
+
if (!categoryKey) return null;
|
|
2194
|
+
const price = money(row.price);
|
|
2195
|
+
const previous = money(row.previousPrice);
|
|
2196
|
+
if (price === void 0 || price === null || previous === void 0) return null;
|
|
2197
|
+
const item = { categoryKey, price, previousPrice: previous };
|
|
2198
|
+
if (row.offerId !== void 0) {
|
|
2199
|
+
if (typeof row.offerId !== "string" || !row.offerId.trim()) return null;
|
|
2200
|
+
item.offerId = row.offerId.trim();
|
|
2201
|
+
}
|
|
2202
|
+
if (row.offerName !== void 0) {
|
|
2203
|
+
if (typeof row.offerName !== "string" || !row.offerName.trim()) return null;
|
|
2204
|
+
item.offerName = row.offerName.trim();
|
|
2205
|
+
}
|
|
2206
|
+
if (row.remaining !== void 0) {
|
|
2207
|
+
if (row.remaining !== null && (typeof row.remaining !== "number" || !Number.isInteger(row.remaining) || row.remaining < 0)) return null;
|
|
2208
|
+
item.remaining = row.remaining == null ? null : row.remaining;
|
|
2209
|
+
}
|
|
2210
|
+
for (const key of ["startsAt", "endsAt"]) {
|
|
2211
|
+
if (row[key] === void 0) continue;
|
|
2212
|
+
const at = timestamp(row[key]);
|
|
2213
|
+
if (at === void 0) return null;
|
|
2214
|
+
item[key] = at;
|
|
2215
|
+
}
|
|
2216
|
+
parsed.push(item);
|
|
2217
|
+
}
|
|
2218
|
+
prices = parsed;
|
|
2219
|
+
}
|
|
2220
|
+
return { state, fromPrice, previousPrice, currency, release, upcoming, prices };
|
|
2221
|
+
}
|
|
2222
|
+
function nextOfferTransitionAt(availability, now) {
|
|
2223
|
+
if (!availability) return null;
|
|
2224
|
+
let next = null;
|
|
2225
|
+
const consider = (at) => {
|
|
2226
|
+
if (at != null && at > now && (next === null || at < next)) next = at;
|
|
2227
|
+
};
|
|
2228
|
+
for (const summary of [availability.release, availability.upcoming]) {
|
|
2229
|
+
consider(summary?.startsAt);
|
|
2230
|
+
consider(summary?.endsAt);
|
|
2231
|
+
}
|
|
2232
|
+
for (const price of availability.prices) {
|
|
2233
|
+
consider(price.startsAt);
|
|
2234
|
+
consider(price.endsAt);
|
|
2235
|
+
}
|
|
2236
|
+
return next;
|
|
2237
|
+
}
|
|
2238
|
+
function ticketOfferPrices(availability) {
|
|
2239
|
+
const map = {};
|
|
2240
|
+
for (const entry of availability?.prices ?? []) map[entry.categoryKey] = entry.price;
|
|
2241
|
+
return map;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// src/SeatPicker.ts
|
|
1992
2245
|
var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
|
|
1993
2246
|
var DEFAULT_MAX_SELECTION2 = 10;
|
|
1994
2247
|
var EXTEND_PROMPT_MS = 6e4;
|
|
@@ -2209,6 +2462,15 @@ var CSS = (
|
|
|
2209
2462
|
.sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
2210
2463
|
|
|
2211
2464
|
/* price panel \u2014 one compact filter control replaces the wrapping price-chip row. */
|
|
2465
|
+
.sl-offer{display:none;margin:12px 14px 2px;padding:12px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));
|
|
2466
|
+
border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
|
|
2467
|
+
.sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
|
|
2468
|
+
.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)}
|
|
2469
|
+
.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)}
|
|
2470
|
+
.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;
|
|
2471
|
+
display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
|
|
2472
|
+
.sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent)}
|
|
2473
|
+
.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
2474
|
.sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
|
|
2213
2475
|
.sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
2214
2476
|
.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 +2481,7 @@ var CSS = (
|
|
|
2219
2481
|
padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
|
|
2220
2482
|
.sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
|
|
2221
2483
|
.sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
|
|
2484
|
+
.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
2485
|
.sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
|
|
2223
2486
|
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
2224
2487
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
@@ -2944,10 +3207,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
2944
3207
|
this.ro = null;
|
|
2945
3208
|
this.holdTimer = null;
|
|
2946
3209
|
this.toastTimer = null;
|
|
3210
|
+
this.offerRefreshTimer = null;
|
|
3211
|
+
/** Armed only when the offer schedule has a known future transition (or as a
|
|
3212
|
+
* bounded retry after a failed read) — never a fixed-cadence poll. */
|
|
3213
|
+
this.offerBoundaryTimer = null;
|
|
3214
|
+
this.offerVisibilityHandler = null;
|
|
2947
3215
|
/** Short-lived UI motion timers; all are cancelled on destroy. */
|
|
2948
3216
|
this.motionTimers = /* @__PURE__ */ new Set();
|
|
2949
3217
|
// state
|
|
2950
3218
|
this.currency = "USD";
|
|
3219
|
+
this.eventTimezone = null;
|
|
3220
|
+
this.offerAvailability = null;
|
|
2951
3221
|
this.hold = null;
|
|
2952
3222
|
/** Latest server expiry for the open hold (moves on extend). */
|
|
2953
3223
|
this.holdExpiresAt = 0;
|
|
@@ -3006,6 +3276,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
3006
3276
|
this.secCardEl = null;
|
|
3007
3277
|
this.viewEl = null;
|
|
3008
3278
|
this.viewCleanup = null;
|
|
3279
|
+
/** Supersedes an older authored-view byte request when another seat is opened. */
|
|
3280
|
+
this.seatViewGen = 0;
|
|
3009
3281
|
this.allSeatsCache = null;
|
|
3010
3282
|
// F3 minimap
|
|
3011
3283
|
this.miniCanvas = null;
|
|
@@ -3061,6 +3333,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3061
3333
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
3062
3334
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
3063
3335
|
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
3336
|
+
this.hostPricing = options.pricing;
|
|
3064
3337
|
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
3065
3338
|
this.access = options.transport ? null : createBuyerAccessContext(options, {
|
|
3066
3339
|
onExpired: (event) => {
|
|
@@ -3077,6 +3350,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
3077
3350
|
onObjectUnavailable: (event) => this.opts.onSelectedObjectUnavailable?.(event)
|
|
3078
3351
|
});
|
|
3079
3352
|
this.api = options.transport ?? this.pubApi;
|
|
3353
|
+
this.buyerAssetUrls = new BuyerAssetObjectUrls(
|
|
3354
|
+
options.event,
|
|
3355
|
+
this.api.asset ? (key, asset) => this.api.asset(key, asset) : void 0
|
|
3356
|
+
);
|
|
3080
3357
|
if (options.checkout === "hosted" && !this.pubApi) {
|
|
3081
3358
|
console.warn(
|
|
3082
3359
|
'seatlayer: checkout: "hosted" needs the widget\'s own transport \u2014 a custom `transport` owns its backend, so the picker is staying on onCheckout for this mount.'
|
|
@@ -3102,6 +3379,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3102
3379
|
},
|
|
3103
3380
|
onStatusChange: () => {
|
|
3104
3381
|
this.syncPrices();
|
|
3382
|
+
this.scheduleOfferRefresh(true);
|
|
3105
3383
|
this.evictTakenSelections();
|
|
3106
3384
|
this.detectBooked();
|
|
3107
3385
|
this.refreshMinimap();
|
|
@@ -3204,7 +3482,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3204
3482
|
}
|
|
3205
3483
|
}
|
|
3206
3484
|
const sightHtml = hasStage && distance != null ? `<div class="sl-confirm-sight">${t2("picker.sightline", { m: distance })}</div>` : "";
|
|
3207
|
-
const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb"
|
|
3485
|
+
const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button>` : `<button type="button" class="sl-confirm-view sl-confirm-viewbtn" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><span aria-hidden="true">\u{1F52D}</span><span>${this.tf("picker.viewFromHere", "View from here")}</span></button>`;
|
|
3208
3486
|
return viewBtn + sightHtml;
|
|
3209
3487
|
}
|
|
3210
3488
|
/** "See it in 3D" (2D) / "View from this seat" (already in 3D) action for the
|
|
@@ -3523,6 +3801,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3523
3801
|
</div>
|
|
3524
3802
|
<div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
|
|
3525
3803
|
<div class="sl-filters" data-ref="filters"></div>
|
|
3804
|
+
<div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
|
|
3526
3805
|
<div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
|
|
3527
3806
|
<div class="sl-prices" data-ref="prices"></div>
|
|
3528
3807
|
<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 +3822,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
3543
3822
|
this.els[el.dataset.ref] = el;
|
|
3544
3823
|
});
|
|
3545
3824
|
this.mapHost = this.els.map;
|
|
3825
|
+
void this.refreshOfferAvailability(false);
|
|
3826
|
+
if (this.api.availability) {
|
|
3827
|
+
this.offerVisibilityHandler = () => {
|
|
3828
|
+
if (!document.hidden && !this.destroyed) void this.refreshOfferAvailability(false);
|
|
3829
|
+
};
|
|
3830
|
+
document.addEventListener("visibilitychange", this.offerVisibilityHandler);
|
|
3831
|
+
}
|
|
3546
3832
|
const applyLayout = () => {
|
|
3547
3833
|
const w = root.clientWidth;
|
|
3548
3834
|
if (w <= 0) return;
|
|
@@ -3647,7 +3933,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3647
3933
|
}
|
|
3648
3934
|
this.els.boot.remove();
|
|
3649
3935
|
this.startRealtime();
|
|
3650
|
-
this.salesClosed = !!info.salesClosed;
|
|
3936
|
+
this.salesClosed = !!info.salesClosed || !!this.opts.readOnly;
|
|
3651
3937
|
this.controller.setViewMode(this.normalizeInitialView(this.opts.initialView));
|
|
3652
3938
|
this.buildRegions();
|
|
3653
3939
|
this.regions["bottom-right"].appendChild(this.els.zoom);
|
|
@@ -3662,6 +3948,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3662
3948
|
const chartTheme = this.controller.doc?.theme;
|
|
3663
3949
|
Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
3664
3950
|
this.currency = info.currency ?? this.opts.currency ?? "USD";
|
|
3951
|
+
this.eventTimezone = info.timezone ?? null;
|
|
3665
3952
|
const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl;
|
|
3666
3953
|
if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
|
|
3667
3954
|
else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
|
|
@@ -3893,8 +4180,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
3893
4180
|
* idempotent DOM apply used at load and on transition.
|
|
3894
4181
|
*/
|
|
3895
4182
|
setSalesClosed(closed) {
|
|
3896
|
-
|
|
3897
|
-
this.salesClosed
|
|
4183
|
+
const next = closed || !!this.opts.readOnly;
|
|
4184
|
+
if (this.salesClosed === next) return;
|
|
4185
|
+
this.salesClosed = next;
|
|
3898
4186
|
this.applySalesClosed();
|
|
3899
4187
|
}
|
|
3900
4188
|
applySalesClosed() {
|
|
@@ -4778,6 +5066,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
4778
5066
|
el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + this.see3dConfirmHtml() + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
|
|
4779
5067
|
this.els.map.appendChild(el);
|
|
4780
5068
|
this.confirmEl = el;
|
|
5069
|
+
const thumb = el.querySelector(".sl-confirm-thumb");
|
|
5070
|
+
if (thumb && seat.viewUrl) {
|
|
5071
|
+
const thumbReference = seat.viewMeta?.previewUrl ?? seat.viewUrl;
|
|
5072
|
+
void this.buyerAssetUrls.resolve(thumbReference).then((url) => {
|
|
5073
|
+
if (url && el.isConnected && this.confirmEl === el) thumb.src = url;
|
|
5074
|
+
}).catch((error) => this.opts.onError?.(error));
|
|
5075
|
+
}
|
|
4781
5076
|
this.reanchorConfirm();
|
|
4782
5077
|
el.querySelector(".sl-confirm-view")?.addEventListener("click", () => void this.openSeatView(seat));
|
|
4783
5078
|
el.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
|
|
@@ -4858,6 +5153,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4858
5153
|
*/
|
|
4859
5154
|
async openSeatView(seat) {
|
|
4860
5155
|
if (!this.root || !this.seatViewEnabled()) return;
|
|
5156
|
+
const generation = ++this.seatViewGen;
|
|
4861
5157
|
const doc = this.controller.doc;
|
|
4862
5158
|
const activeId = this.controller.getActiveFloorId();
|
|
4863
5159
|
const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
@@ -4865,9 +5161,24 @@ var SeatPicker = class _SeatPicker {
|
|
|
4865
5161
|
let caption;
|
|
4866
5162
|
let real = false;
|
|
4867
5163
|
if (seat.viewUrl) {
|
|
5164
|
+
let resolvedUrl;
|
|
5165
|
+
let resolvedPreviewUrl = null;
|
|
5166
|
+
try {
|
|
5167
|
+
const previewReference = seat.viewMeta?.previewUrl;
|
|
5168
|
+
if (previewReference && previewReference !== seat.viewUrl) {
|
|
5169
|
+
resolvedPreviewUrl = await this.buyerAssetUrls.resolve(previewReference);
|
|
5170
|
+
resolvedUrl = seat.viewUrl;
|
|
5171
|
+
} else {
|
|
5172
|
+
resolvedUrl = await this.buyerAssetUrls.resolve(seat.viewUrl);
|
|
5173
|
+
}
|
|
5174
|
+
} catch (error) {
|
|
5175
|
+
if (generation === this.seatViewGen) this.opts.onError?.(error);
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5178
|
+
if (generation !== this.seatViewGen || !resolvedUrl || seat.viewMeta?.previewUrl && seat.viewMeta.previewUrl !== seat.viewUrl && !resolvedPreviewUrl || !this.root || !this.seatViewEnabled()) return;
|
|
4868
5179
|
const view = {
|
|
4869
|
-
url:
|
|
4870
|
-
...
|
|
5180
|
+
url: resolvedUrl,
|
|
5181
|
+
...resolvedPreviewUrl ? { previewUrl: resolvedPreviewUrl } : {},
|
|
4871
5182
|
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
4872
5183
|
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
4873
5184
|
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
@@ -4885,14 +5196,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
4885
5196
|
const { generateSeatPanorama } = await loadPanorama();
|
|
4886
5197
|
pano2 = generateSeatPanorama(seat, focal, this.allSeats());
|
|
4887
5198
|
} catch (err) {
|
|
4888
|
-
this.opts.onError?.(err);
|
|
5199
|
+
if (generation === this.seatViewGen) this.opts.onError?.(err);
|
|
4889
5200
|
return;
|
|
4890
5201
|
}
|
|
4891
|
-
if (!this.root || !this.seatViewEnabled()) return;
|
|
5202
|
+
if (generation !== this.seatViewGen || !this.root || !this.seatViewEnabled()) return;
|
|
4892
5203
|
panoSource = { url: pano2.url, generated: true };
|
|
4893
5204
|
caption = t2("picker.illustrationCaption", { m: pano2.distanceM });
|
|
4894
5205
|
}
|
|
4895
|
-
this.closeSeatView();
|
|
5206
|
+
this.closeSeatView(false);
|
|
4896
5207
|
const el = document.createElement("div");
|
|
4897
5208
|
el.className = "sl-view";
|
|
4898
5209
|
el.setAttribute("role", "dialog");
|
|
@@ -4908,9 +5219,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
4908
5219
|
};
|
|
4909
5220
|
if (delivery.upgradeUrl) {
|
|
4910
5221
|
cancelUpgrade = schedulePanoramaUpgrade(() => {
|
|
4911
|
-
void
|
|
4912
|
-
if (!
|
|
4913
|
-
|
|
5222
|
+
void this.buyerAssetUrls.resolve(delivery.upgradeUrl).then((url) => {
|
|
5223
|
+
if (!url || loadAbort.signal.aborted) return null;
|
|
5224
|
+
return loadPanoramaImage(url, loadAbort.signal).then(() => url);
|
|
5225
|
+
}).then((url) => {
|
|
5226
|
+
if (!url || !el.isConnected || loadAbort.signal.aborted) return;
|
|
5227
|
+
pano.style.backgroundImage = `url("${url}")`;
|
|
4914
5228
|
}).catch(() => {
|
|
4915
5229
|
});
|
|
4916
5230
|
});
|
|
@@ -4986,7 +5300,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
4986
5300
|
el.removeEventListener("keydown", onKey);
|
|
4987
5301
|
};
|
|
4988
5302
|
}
|
|
4989
|
-
closeSeatView() {
|
|
5303
|
+
closeSeatView(cancelPending = true) {
|
|
5304
|
+
if (cancelPending) this.seatViewGen += 1;
|
|
4990
5305
|
this.viewCleanup?.();
|
|
4991
5306
|
this.viewCleanup = null;
|
|
4992
5307
|
this.viewEl?.remove();
|
|
@@ -5002,6 +5317,119 @@ var SeatPicker = class _SeatPicker {
|
|
|
5002
5317
|
return `${n} ${this.currency}`;
|
|
5003
5318
|
}
|
|
5004
5319
|
}
|
|
5320
|
+
/**
|
|
5321
|
+
* Sleep until the offer schedule's next known transition, then re-read.
|
|
5322
|
+
*
|
|
5323
|
+
* A far-away boundary is capped: the wake re-reads, learns the (unchanged)
|
|
5324
|
+
* schedule, and re-arms — so a picker left open for days still tracks an
|
|
5325
|
+
* organizer's schedule edits at a cost of one request every few hours. No
|
|
5326
|
+
* future transition means no timer at all; an event with no releases does
|
|
5327
|
+
* zero background traffic. A wake in a hidden tab fetches nothing — the
|
|
5328
|
+
* visibilitychange handler owns catching that tab up.
|
|
5329
|
+
*/
|
|
5330
|
+
scheduleOfferBoundary(availability) {
|
|
5331
|
+
if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
|
|
5332
|
+
this.offerBoundaryTimer = null;
|
|
5333
|
+
if (!this.api.availability || this.destroyed) return;
|
|
5334
|
+
const now = Date.now();
|
|
5335
|
+
const boundary = nextOfferTransitionAt(availability, now);
|
|
5336
|
+
if (boundary == null) return;
|
|
5337
|
+
const MAX_SLEEP_MS = 6 * 36e5;
|
|
5338
|
+
const delay = Math.min(Math.max(boundary - now + 1e3, 1e3), MAX_SLEEP_MS);
|
|
5339
|
+
this.offerBoundaryTimer = setTimeout(() => {
|
|
5340
|
+
this.offerBoundaryTimer = null;
|
|
5341
|
+
if (document.hidden) return;
|
|
5342
|
+
void this.refreshOfferAvailability(false);
|
|
5343
|
+
}, delay);
|
|
5344
|
+
}
|
|
5345
|
+
/** Debounce the no-store offer read behind a burst of seat-status frames. */
|
|
5346
|
+
scheduleOfferRefresh(live) {
|
|
5347
|
+
if (!this.api.availability || this.destroyed) return;
|
|
5348
|
+
if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
|
|
5349
|
+
this.offerRefreshTimer = setTimeout(() => {
|
|
5350
|
+
this.offerRefreshTimer = null;
|
|
5351
|
+
void this.refreshOfferAvailability(live);
|
|
5352
|
+
}, live ? 180 : 0);
|
|
5353
|
+
}
|
|
5354
|
+
/**
|
|
5355
|
+
* Pull the server's resolved answer. A failed refresh keeps the last truthful
|
|
5356
|
+
* answer: flashing back to a chart price while checkout still charges an
|
|
5357
|
+
* offer is worse than a temporarily stale remaining count.
|
|
5358
|
+
*/
|
|
5359
|
+
async refreshOfferAvailability(live) {
|
|
5360
|
+
if (!this.api.availability || this.destroyed) return;
|
|
5361
|
+
try {
|
|
5362
|
+
const body = await this.api.availability(this.opts.event, live);
|
|
5363
|
+
if (this.destroyed) return;
|
|
5364
|
+
const availability = parseTicketOfferAvailability(body);
|
|
5365
|
+
if (!availability) return;
|
|
5366
|
+
this.offerAvailability = availability;
|
|
5367
|
+
this.scheduleOfferBoundary(availability);
|
|
5368
|
+
const server = ticketOfferPrices(availability);
|
|
5369
|
+
const merged = { ...this.hostPricing?.prices ?? {}, ...server };
|
|
5370
|
+
const pricing = Object.keys(merged).length > 0 || this.hostPricing?.formatter ? { prices: merged, ...this.hostPricing?.formatter ? { formatter: this.hostPricing.formatter } : {} } : void 0;
|
|
5371
|
+
this.setPricing(pricing);
|
|
5372
|
+
this.syncOffer();
|
|
5373
|
+
this.opts.onOfferAvailabilityChange?.(availability);
|
|
5374
|
+
} catch {
|
|
5375
|
+
if (!this.destroyed && !this.offerBoundaryTimer && !document.hidden) {
|
|
5376
|
+
this.offerBoundaryTimer = setTimeout(() => {
|
|
5377
|
+
this.offerBoundaryTimer = null;
|
|
5378
|
+
if (document.hidden) return;
|
|
5379
|
+
void this.refreshOfferAvailability(false);
|
|
5380
|
+
}, 3e4);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
offerPrice(categoryKey) {
|
|
5385
|
+
if (!categoryKey) return null;
|
|
5386
|
+
return this.offerAvailability?.prices.find((entry) => entry.categoryKey === categoryKey) ?? null;
|
|
5387
|
+
}
|
|
5388
|
+
/** The compact current/upcoming offer card above Ticket prices. */
|
|
5389
|
+
syncOffer() {
|
|
5390
|
+
const host = this.els.offer;
|
|
5391
|
+
if (!host) return;
|
|
5392
|
+
const availability = this.offerAvailability;
|
|
5393
|
+
const active = availability?.release ?? null;
|
|
5394
|
+
const upcoming = !active ? availability?.upcoming ?? null : null;
|
|
5395
|
+
if (!availability || availability.state === "closed" || availability.state === "sold-out" || !active && !upcoming) {
|
|
5396
|
+
host.classList.remove("has");
|
|
5397
|
+
host.replaceChildren();
|
|
5398
|
+
return;
|
|
5399
|
+
}
|
|
5400
|
+
const offer = active ?? upcoming;
|
|
5401
|
+
const main = document.createElement("div");
|
|
5402
|
+
main.className = "sl-offer-main";
|
|
5403
|
+
const copy = document.createElement("div");
|
|
5404
|
+
copy.className = "sl-offer-copy";
|
|
5405
|
+
const kicker = document.createElement("span");
|
|
5406
|
+
kicker.className = "sl-offer-kicker";
|
|
5407
|
+
kicker.textContent = active ? "Current ticket offer" : "Upcoming ticket offer";
|
|
5408
|
+
const name = document.createElement("strong");
|
|
5409
|
+
name.className = "sl-offer-name";
|
|
5410
|
+
name.textContent = offer.name || (active ? "Current offer" : "Scheduled offer");
|
|
5411
|
+
const line = document.createElement("span");
|
|
5412
|
+
line.className = "sl-offer-line";
|
|
5413
|
+
const facts = [];
|
|
5414
|
+
if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
|
|
5415
|
+
if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
|
|
5416
|
+
if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
|
|
5417
|
+
if (upcoming?.startsAt != null) facts.push(`starts ${formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale)}`);
|
|
5418
|
+
line.textContent = facts.join(" \xB7 ");
|
|
5419
|
+
copy.append(kicker, name, line);
|
|
5420
|
+
const info = document.createElement("details");
|
|
5421
|
+
info.className = "sl-offer-info";
|
|
5422
|
+
const summary = document.createElement("summary");
|
|
5423
|
+
summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
|
|
5424
|
+
summary.textContent = "i";
|
|
5425
|
+
const detail = document.createElement("div");
|
|
5426
|
+
detail.className = "sl-offer-detail";
|
|
5427
|
+
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.`;
|
|
5428
|
+
info.append(summary, detail);
|
|
5429
|
+
main.append(copy, info);
|
|
5430
|
+
host.replaceChildren(main);
|
|
5431
|
+
host.classList.add("has");
|
|
5432
|
+
}
|
|
5005
5433
|
/**
|
|
5006
5434
|
* The price the buyer will actually pay for a category (+tier): the host's
|
|
5007
5435
|
* `pricing` override when present, else the chart's stored price. Every
|
|
@@ -5028,9 +5456,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
5028
5456
|
this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
|
|
5029
5457
|
this.els.prices.innerHTML = shown.map((c) => {
|
|
5030
5458
|
const price = this.catPrice(c);
|
|
5459
|
+
const offer = this.offerPrice(c.key);
|
|
5460
|
+
const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
|
|
5031
5461
|
const active = this.focusedCatKey === c.key;
|
|
5032
5462
|
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>`;
|
|
5463
|
+
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
5464
|
}).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
5465
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
5036
5466
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
@@ -5686,6 +6116,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5686
6116
|
}
|
|
5687
6117
|
emitHoldChange() {
|
|
5688
6118
|
const hold = this.hold;
|
|
6119
|
+
this.scheduleOfferRefresh(true);
|
|
5689
6120
|
this.opts.onHoldChange?.(
|
|
5690
6121
|
hold,
|
|
5691
6122
|
hold?.seats ?? [],
|
|
@@ -5953,19 +6384,33 @@ var SeatPicker = class _SeatPicker {
|
|
|
5953
6384
|
async seatViewFor3d(seatId) {
|
|
5954
6385
|
const seat = this.allSeats().find((s) => s.id === seatId);
|
|
5955
6386
|
if (!seat) return null;
|
|
5956
|
-
if (seat.viewUrl)
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
6387
|
+
if (seat.viewUrl) {
|
|
6388
|
+
try {
|
|
6389
|
+
const previewReference = seat.viewMeta?.previewUrl;
|
|
6390
|
+
const progressive = !!previewReference && previewReference !== seat.viewUrl;
|
|
6391
|
+
const previewUrl = progressive ? await this.buyerAssetUrls.resolve(previewReference) : null;
|
|
6392
|
+
const url = progressive ? seat.viewUrl : await this.buyerAssetUrls.resolve(seat.viewUrl);
|
|
6393
|
+
if (!url) return null;
|
|
6394
|
+
if (progressive && !previewUrl) return null;
|
|
6395
|
+
return {
|
|
6396
|
+
url,
|
|
6397
|
+
...previewUrl ? { previewUrl } : {},
|
|
6398
|
+
...progressive ? { resolveUrl: (reference) => this.buyerAssetUrls.resolve(reference) } : {},
|
|
6399
|
+
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
6400
|
+
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
6401
|
+
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
6402
|
+
...seat.viewMeta?.previewHeight !== void 0 ? { previewHeight: seat.viewMeta.previewHeight } : {},
|
|
6403
|
+
...seat.viewMeta?.initialBearingDeg !== void 0 ? { initialBearingDeg: seat.viewMeta.initialBearingDeg } : {},
|
|
6404
|
+
...seat.viewMeta?.initialPitchDeg !== void 0 ? { initialPitchDeg: seat.viewMeta.initialPitchDeg } : {},
|
|
6405
|
+
...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
|
|
6406
|
+
...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
|
|
6407
|
+
...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
|
|
6408
|
+
};
|
|
6409
|
+
} catch (error) {
|
|
6410
|
+
this.opts.onError?.(error);
|
|
6411
|
+
return null;
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
5969
6414
|
const doc = this.controller.doc;
|
|
5970
6415
|
if (!doc) return null;
|
|
5971
6416
|
const activeId = this.controller.getActiveFloorId();
|
|
@@ -6415,6 +6860,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
6415
6860
|
flashOnLiveChange: true,
|
|
6416
6861
|
onStatusChange: () => {
|
|
6417
6862
|
this.syncPrices();
|
|
6863
|
+
this.scheduleOfferRefresh(true);
|
|
6418
6864
|
this.detectBooked();
|
|
6419
6865
|
this.refreshMinimap();
|
|
6420
6866
|
this.pushAvailabilityTo3d();
|
|
@@ -6547,9 +6993,18 @@ var SeatPicker = class _SeatPicker {
|
|
|
6547
6993
|
this.closeSeatView();
|
|
6548
6994
|
this.closeCheckoutPanel();
|
|
6549
6995
|
this.exit3d();
|
|
6996
|
+
this.buyerAssetUrls.dispose();
|
|
6550
6997
|
this.stopHoldTimer();
|
|
6551
6998
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
6552
6999
|
if (this.liveTimer) clearTimeout(this.liveTimer);
|
|
7000
|
+
if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
|
|
7001
|
+
if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
|
|
7002
|
+
this.offerRefreshTimer = null;
|
|
7003
|
+
this.offerBoundaryTimer = null;
|
|
7004
|
+
if (this.offerVisibilityHandler) {
|
|
7005
|
+
document.removeEventListener("visibilitychange", this.offerVisibilityHandler);
|
|
7006
|
+
this.offerVisibilityHandler = null;
|
|
7007
|
+
}
|
|
6553
7008
|
for (const timer of this.motionTimers) clearTimeout(timer);
|
|
6554
7009
|
this.motionTimers.clear();
|
|
6555
7010
|
this.ro?.disconnect();
|
|
@@ -6690,10 +7145,12 @@ export {
|
|
|
6690
7145
|
markerOf,
|
|
6691
7146
|
mutationCount,
|
|
6692
7147
|
needsMoveConfirmation,
|
|
7148
|
+
parseTicketOfferAvailability,
|
|
6693
7149
|
planAssignment,
|
|
6694
7150
|
retryAfterCopy,
|
|
6695
7151
|
selectionSources,
|
|
6696
7152
|
stateBadge,
|
|
6697
|
-
suggestMarker
|
|
7153
|
+
suggestMarker,
|
|
7154
|
+
ticketOfferPrices
|
|
6698
7155
|
};
|
|
6699
7156
|
//# sourceMappingURL=index.js.map
|