@seatlayer/js 0.47.0 → 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 +330 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -2
- package/dist/index.d.ts +91 -2
- package/dist/index.js +327 -16
- 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
|
*
|
|
@@ -945,7 +949,6 @@ var SeatingChart = class {
|
|
|
945
949
|
this.mount = null;
|
|
946
950
|
this.hostEl = null;
|
|
947
951
|
this.rendered = false;
|
|
948
|
-
this.retryBtn = null;
|
|
949
952
|
this.mode_ = null;
|
|
950
953
|
this.tipEl = null;
|
|
951
954
|
this.tipPos = { x: 0, y: 0 };
|
|
@@ -1074,7 +1077,7 @@ var SeatingChart = class {
|
|
|
1074
1077
|
this.tipEl.style.display = "none";
|
|
1075
1078
|
return;
|
|
1076
1079
|
}
|
|
1077
|
-
const
|
|
1080
|
+
const money2 = (() => {
|
|
1078
1081
|
try {
|
|
1079
1082
|
return new Intl.NumberFormat(void 0, { style: "currency", currency: details.currency }).format(details.price);
|
|
1080
1083
|
} catch {
|
|
@@ -1082,7 +1085,7 @@ var SeatingChart = class {
|
|
|
1082
1085
|
}
|
|
1083
1086
|
})();
|
|
1084
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>`;
|
|
1085
|
-
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;
|
|
1086
1089
|
this.tipEl.style.display = "block";
|
|
1087
1090
|
this.placeTooltip();
|
|
1088
1091
|
}
|
|
@@ -1320,14 +1323,12 @@ var SeatingChart = class {
|
|
|
1320
1323
|
void this.render().catch((err) => this.opts.onError?.(err));
|
|
1321
1324
|
});
|
|
1322
1325
|
box.appendChild(btn);
|
|
1323
|
-
this.retryBtn = btn;
|
|
1324
1326
|
host.appendChild(box);
|
|
1325
1327
|
}
|
|
1326
1328
|
destroy() {
|
|
1327
1329
|
this.realtime?.stop();
|
|
1328
1330
|
this.realtime = null;
|
|
1329
1331
|
this.access?.clear();
|
|
1330
|
-
this.retryBtn = null;
|
|
1331
1332
|
if (this.hostEl && this.onTipMove) this.hostEl.removeEventListener("mousemove", this.onTipMove);
|
|
1332
1333
|
this.tipEl = null;
|
|
1333
1334
|
this.onTipMove = null;
|
|
@@ -1992,6 +1993,136 @@ import {
|
|
|
1992
1993
|
planPanoramaDelivery,
|
|
1993
1994
|
schedulePanoramaUpgrade
|
|
1994
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
|
|
1995
2126
|
var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
|
|
1996
2127
|
var DEFAULT_MAX_SELECTION2 = 10;
|
|
1997
2128
|
var EXTEND_PROMPT_MS = 6e4;
|
|
@@ -2212,6 +2343,15 @@ var CSS = (
|
|
|
2212
2343
|
.sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
2213
2344
|
|
|
2214
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)}
|
|
2215
2355
|
.sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
|
|
2216
2356
|
.sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
2217
2357
|
.sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
|
|
@@ -2222,6 +2362,7 @@ var CSS = (
|
|
|
2222
2362
|
padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
|
|
2223
2363
|
.sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
|
|
2224
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}
|
|
2225
2366
|
.sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
|
|
2226
2367
|
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
2227
2368
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
@@ -2663,7 +2804,21 @@ var CSS = (
|
|
|
2663
2804
|
/* Venue navigation inside 3D: levels (isolate a floor) and areas (fly to a zone).
|
|
2664
2805
|
Bottom-LEFT, clear of the module's own chips (Overview bottom-right, 360
|
|
2665
2806
|
bottom-centre) and of the bottom-sheeted confirm card. Horizontally scrollable
|
|
2666
|
-
so a venue with many zones never pushes the rail off a phone screen.
|
|
2807
|
+
so a venue with many zones never pushes the rail off a phone screen.
|
|
2808
|
+
|
|
2809
|
+
EVERY WIDTH HERE IS RELATIVE TO THE RAIL, NEVER TO THE WINDOW. These three
|
|
2810
|
+
rules used to size off 100vw, which contradicts the one contract this widget
|
|
2811
|
+
states about itself: it adapts to a full-screen takeover, an inline div in a
|
|
2812
|
+
content page, or a popup, and its breakpoints key off the CONTAINER, never the
|
|
2813
|
+
viewport. A 390 px picker embedded in a 1400 px page asked for
|
|
2814
|
+
calc(100vw - 180px) = 1220 inside a 390 px rail.
|
|
2815
|
+
|
|
2816
|
+
MEASURED, IT DID NOT OVERFLOW: the scroll parent is a flex container, so the
|
|
2817
|
+
over-large declaration was shrunk back to the rail and both the old and new
|
|
2818
|
+
rules resolve to 364 px in situ. So this is a latent correctness fix, not a
|
|
2819
|
+
visible bug -- the numbers were wrong and were being covered for by a
|
|
2820
|
+
flex-shrink one level up. Left as 100% because the next person to change that
|
|
2821
|
+
parent's display should not inherit a rule that only works by accident. */
|
|
2667
2822
|
.sl-view3d-nav{position:absolute;left:12px;bottom:16px;z-index:3;display:flex;flex-direction:column;gap:6px;
|
|
2668
2823
|
max-width:calc(100% - 150px);pointer-events:none}
|
|
2669
2824
|
.sl-view3d-nav > div{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;pointer-events:auto;
|
|
@@ -2675,15 +2830,15 @@ var CSS = (
|
|
|
2675
2830
|
.sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
|
|
2676
2831
|
.sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
2677
2832
|
.sl-view3d-nav button:disabled{opacity:.45;cursor:not-allowed}
|
|
2678
|
-
.sl-view3d-nav select{min-height:38px;max-width:
|
|
2833
|
+
.sl-view3d-nav select{min-height:38px;max-width:100%;padding:7px 34px 7px 12px;
|
|
2679
2834
|
border-radius:999px;font:700 11.5px/1 inherit;color:#eef1f8;background:rgba(12,18,32,.86);
|
|
2680
2835
|
border:1px solid rgba(150,165,205,.45);backdrop-filter:blur(6px);cursor:pointer}
|
|
2681
2836
|
.sl-view3d-nav select:focus-visible{outline:2px solid var(--sl-accent);outline-offset:2px}
|
|
2682
2837
|
.sl-view3d-nav .sl-view3d-locator{display:grid;grid-template-columns:minmax(150px,1.25fr) minmax(110px,.8fr) minmax(105px,.7fr) auto;
|
|
2683
|
-
width:min(720px,
|
|
2838
|
+
width:min(720px,100%);overflow:visible}
|
|
2684
2839
|
.sl-view3d-nav.is-seat-focused .sl-view3d-locator{display:none}
|
|
2685
2840
|
.sl-view3d-locator select{width:100%;min-width:0}
|
|
2686
|
-
.sl-picker[data-layout="narrow"] .sl-view3d-nav .sl-view3d-locator{display:flex;width:
|
|
2841
|
+
.sl-picker[data-layout="narrow"] .sl-view3d-nav .sl-view3d-locator{display:flex;width:100%;overflow-x:auto}
|
|
2687
2842
|
.sl-picker[data-layout="narrow"] .sl-view3d-locator select{flex:0 0 155px}
|
|
2688
2843
|
/* On phones the library owns the bottom edge for seat/panorama/overview actions.
|
|
2689
2844
|
Keep venue navigation in a separate top rail so those control families never
|
|
@@ -2933,10 +3088,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
2933
3088
|
this.ro = null;
|
|
2934
3089
|
this.holdTimer = null;
|
|
2935
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;
|
|
2936
3096
|
/** Short-lived UI motion timers; all are cancelled on destroy. */
|
|
2937
3097
|
this.motionTimers = /* @__PURE__ */ new Set();
|
|
2938
3098
|
// state
|
|
2939
3099
|
this.currency = "USD";
|
|
3100
|
+
this.eventTimezone = null;
|
|
3101
|
+
this.offerAvailability = null;
|
|
2940
3102
|
this.hold = null;
|
|
2941
3103
|
/** Latest server expiry for the open hold (moves on extend). */
|
|
2942
3104
|
this.holdExpiresAt = 0;
|
|
@@ -3050,6 +3212,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3050
3212
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
3051
3213
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
3052
3214
|
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
3215
|
+
this.hostPricing = options.pricing;
|
|
3053
3216
|
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
3054
3217
|
this.access = options.transport ? null : createBuyerAccessContext(options, {
|
|
3055
3218
|
onExpired: (event) => {
|
|
@@ -3091,6 +3254,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3091
3254
|
},
|
|
3092
3255
|
onStatusChange: () => {
|
|
3093
3256
|
this.syncPrices();
|
|
3257
|
+
this.scheduleOfferRefresh(true);
|
|
3094
3258
|
this.evictTakenSelections();
|
|
3095
3259
|
this.detectBooked();
|
|
3096
3260
|
this.refreshMinimap();
|
|
@@ -3512,6 +3676,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3512
3676
|
</div>
|
|
3513
3677
|
<div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
|
|
3514
3678
|
<div class="sl-filters" data-ref="filters"></div>
|
|
3679
|
+
<div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
|
|
3515
3680
|
<div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
|
|
3516
3681
|
<div class="sl-prices" data-ref="prices"></div>
|
|
3517
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>
|
|
@@ -3532,6 +3697,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
3532
3697
|
this.els[el.dataset.ref] = el;
|
|
3533
3698
|
});
|
|
3534
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
|
+
}
|
|
3535
3707
|
const applyLayout = () => {
|
|
3536
3708
|
const w = root.clientWidth;
|
|
3537
3709
|
if (w <= 0) return;
|
|
@@ -3569,10 +3741,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
3569
3741
|
e.stopPropagation();
|
|
3570
3742
|
setSheet(root.dataset.sheet !== "open");
|
|
3571
3743
|
});
|
|
3744
|
+
this.els.peek?.addEventListener("click", (e) => {
|
|
3745
|
+
const go = e.target.closest(".sl-sheet-go");
|
|
3746
|
+
if (!go) return;
|
|
3747
|
+
e.stopPropagation();
|
|
3748
|
+
if (go.dataset.act === "checkout") void this.handleCta();
|
|
3749
|
+
else setSheet(true);
|
|
3750
|
+
});
|
|
3572
3751
|
let startY = 0;
|
|
3573
3752
|
let swiped = false;
|
|
3574
3753
|
let tracking = false;
|
|
3575
3754
|
head.addEventListener("pointerdown", (e) => {
|
|
3755
|
+
if (e.target.closest?.(".sl-seccard,.sl-sheet-toggle,.sl-sheet-go")) {
|
|
3756
|
+
tracking = false;
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3576
3759
|
tracking = true;
|
|
3577
3760
|
swiped = false;
|
|
3578
3761
|
startY = e.clientY;
|
|
@@ -3591,7 +3774,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3591
3774
|
});
|
|
3592
3775
|
head.addEventListener("pointerup", (e) => {
|
|
3593
3776
|
if (tracking && !swiped && Math.abs(e.clientY - startY) < 6) {
|
|
3594
|
-
|
|
3777
|
+
setSheet(root.dataset.sheet !== "open");
|
|
3595
3778
|
}
|
|
3596
3779
|
tracking = false;
|
|
3597
3780
|
head.releasePointerCapture?.(e.pointerId);
|
|
@@ -3640,6 +3823,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3640
3823
|
const chartTheme = this.controller.doc?.theme;
|
|
3641
3824
|
Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
3642
3825
|
this.currency = info.currency ?? this.opts.currency ?? "USD";
|
|
3826
|
+
this.eventTimezone = info.timezone ?? null;
|
|
3643
3827
|
const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl;
|
|
3644
3828
|
if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
|
|
3645
3829
|
else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
|
|
@@ -3785,12 +3969,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3785
3969
|
const narrow = this.root?.dataset.layout === "narrow";
|
|
3786
3970
|
const filters = this.els.filters;
|
|
3787
3971
|
if (filters) {
|
|
3972
|
+
if (this.cbEl) this.els.zoom?.appendChild(this.cbEl);
|
|
3788
3973
|
if (narrow) {
|
|
3789
3974
|
if (this.a11yChipsEl) filters.appendChild(this.a11yChipsEl);
|
|
3790
|
-
if (this.cbEl) filters.appendChild(this.cbEl);
|
|
3791
3975
|
} else {
|
|
3792
3976
|
if (this.a11yChipsEl) this.regions["top-left"]?.appendChild(this.a11yChipsEl);
|
|
3793
|
-
if (this.cbEl) this.els.zoom?.appendChild(this.cbEl);
|
|
3794
3977
|
}
|
|
3795
3978
|
const has = narrow && filters.children.length > 0;
|
|
3796
3979
|
filters.classList.toggle("has", has);
|
|
@@ -4981,6 +5164,119 @@ var SeatPicker = class _SeatPicker {
|
|
|
4981
5164
|
return `${n} ${this.currency}`;
|
|
4982
5165
|
}
|
|
4983
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
|
+
}
|
|
4984
5280
|
/**
|
|
4985
5281
|
* The price the buyer will actually pay for a category (+tier): the host's
|
|
4986
5282
|
* `pricing` override when present, else the chart's stored price. Every
|
|
@@ -5007,9 +5303,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
5007
5303
|
this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
|
|
5008
5304
|
this.els.prices.innerHTML = shown.map((c) => {
|
|
5009
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;
|
|
5010
5308
|
const active = this.focusedCatKey === c.key;
|
|
5011
5309
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
5012
|
-
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>`;
|
|
5013
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>`;
|
|
5014
5312
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
5015
5313
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
@@ -5321,10 +5619,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
5321
5619
|
if (previousCount === 0 && count > 0) this.animateOnce(this.els.cta, "sl-ready", 520);
|
|
5322
5620
|
if (this.els.peek) {
|
|
5323
5621
|
if (count) {
|
|
5324
|
-
|
|
5622
|
+
const holding = !!this.hold && !pendingCount;
|
|
5623
|
+
this.els.peek.innerHTML = `<span>${count} ${count === 1 ? "ticket" : "tickets"} \xB7 ${this.money(total)}</span><button type="button" class="go sl-sheet-go" data-act="${holding ? "checkout" : "open"}">${this.hold ? pendingCount ? "Secure more" : "Continue" : "Review"}</button>`;
|
|
5325
5624
|
} else {
|
|
5326
5625
|
const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
|
|
5327
|
-
this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<
|
|
5626
|
+
this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<button type="button" class="go sl-sheet-go" data-act="open">\u2726 Best seats</button>`;
|
|
5328
5627
|
}
|
|
5329
5628
|
}
|
|
5330
5629
|
this.lastTrayCount = count;
|
|
@@ -5664,6 +5963,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5664
5963
|
}
|
|
5665
5964
|
emitHoldChange() {
|
|
5666
5965
|
const hold = this.hold;
|
|
5966
|
+
this.scheduleOfferRefresh(true);
|
|
5667
5967
|
this.opts.onHoldChange?.(
|
|
5668
5968
|
hold,
|
|
5669
5969
|
hold?.seats ?? [],
|
|
@@ -6393,6 +6693,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
6393
6693
|
flashOnLiveChange: true,
|
|
6394
6694
|
onStatusChange: () => {
|
|
6395
6695
|
this.syncPrices();
|
|
6696
|
+
this.scheduleOfferRefresh(true);
|
|
6396
6697
|
this.detectBooked();
|
|
6397
6698
|
this.refreshMinimap();
|
|
6398
6699
|
this.pushAvailabilityTo3d();
|
|
@@ -6528,6 +6829,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
6528
6829
|
this.stopHoldTimer();
|
|
6529
6830
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
6530
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
|
+
}
|
|
6531
6840
|
for (const timer of this.motionTimers) clearTimeout(timer);
|
|
6532
6841
|
this.motionTimers.clear();
|
|
6533
6842
|
this.ro?.disconnect();
|
|
@@ -6668,10 +6977,12 @@ export {
|
|
|
6668
6977
|
markerOf,
|
|
6669
6978
|
mutationCount,
|
|
6670
6979
|
needsMoveConfirmation,
|
|
6980
|
+
parseTicketOfferAvailability,
|
|
6671
6981
|
planAssignment,
|
|
6672
6982
|
retryAfterCopy,
|
|
6673
6983
|
selectionSources,
|
|
6674
6984
|
stateBadge,
|
|
6675
|
-
suggestMarker
|
|
6985
|
+
suggestMarker,
|
|
6986
|
+
ticketOfferPrices
|
|
6676
6987
|
};
|
|
6677
6988
|
//# sourceMappingURL=index.js.map
|