@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.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 money = (() => {
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">${money}</span></div>` + statusLine;
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