@seatlayer/js 0.47.1 → 0.47.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3957,11 +3957,13 @@ __export(index_exports, {
3957
3957
  markerOf: () => markerOf,
3958
3958
  mutationCount: () => mutationCount,
3959
3959
  needsMoveConfirmation: () => needsMoveConfirmation,
3960
+ parseTicketOfferAvailability: () => parseTicketOfferAvailability,
3960
3961
  planAssignment: () => planAssignment,
3961
3962
  retryAfterCopy: () => retryAfterCopy,
3962
3963
  selectionSources: () => selectionSources,
3963
3964
  stateBadge: () => stateBadge,
3964
- suggestMarker: () => suggestMarker
3965
+ suggestMarker: () => suggestMarker,
3966
+ ticketOfferPrices: () => ticketOfferPrices
3965
3967
  });
3966
3968
  module.exports = __toCommonJS(index_exports);
3967
3969
 
@@ -4497,6 +4499,10 @@ var PubApi = class {
4497
4499
  paymentOptions(key) {
4498
4500
  return this.request(`/pub/events/${encodeURIComponent(key)}/payment-options`);
4499
4501
  }
4502
+ /** Server-resolved active ticket offers and category prices. */
4503
+ availability(key, live = false) {
4504
+ return this.request(`/pub/events/${encodeURIComponent(key)}/availability${live ? "?live=1" : ""}`);
4505
+ }
4500
4506
  /**
4501
4507
  * Turn a live hold into an order and start a payment.
4502
4508
  *
@@ -4993,7 +4999,7 @@ var SeatingChart = class {
4993
4999
  this.tipEl.style.display = "none";
4994
5000
  return;
4995
5001
  }
4996
- const money = (() => {
5002
+ const money2 = (() => {
4997
5003
  try {
4998
5004
  return new Intl.NumberFormat(void 0, { style: "currency", currency: details.currency }).format(details.price);
4999
5005
  } catch {
@@ -5001,7 +5007,7 @@ var SeatingChart = class {
5001
5007
  }
5002
5008
  })();
5003
5009
  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" ? (0, import_core.t)("map.statusHeld") : (0, import_core.t)("map.statusTaken")}</div>`;
5004
- 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;
5010
+ 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;
5005
5011
  this.tipEl.style.display = "block";
5006
5012
  this.placeTooltip();
5007
5013
  }
@@ -5895,6 +5901,136 @@ var EmbeddedDesigner = class {
5895
5901
  var import_core2 = require("@seatlayer/core");
5896
5902
  var import_panorama = require("@seatlayer/core/view3d/crossfade/panorama");
5897
5903
  var import_panoramaDelivery = require("@seatlayer/core/view/panoramaDelivery");
5904
+
5905
+ // src/offerAvailability.ts
5906
+ var SALE_STATES = [
5907
+ "on-sale",
5908
+ "low",
5909
+ "sold-out",
5910
+ "presale",
5911
+ "closed"
5912
+ ];
5913
+ function money(value) {
5914
+ if (value === null || value === void 0) return null;
5915
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
5916
+ }
5917
+ function timestamp(value) {
5918
+ if (value === null || value === void 0) return null;
5919
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
5920
+ }
5921
+ function parseSummary(value) {
5922
+ if (value == null) return null;
5923
+ if (typeof value !== "object" || Array.isArray(value)) return void 0;
5924
+ const source = value;
5925
+ const count = source.count;
5926
+ const index = source.index;
5927
+ if (typeof index !== "number" || !Number.isInteger(index) || index < 1) return void 0;
5928
+ if (typeof count !== "number" || !Number.isInteger(count) || count < index) return void 0;
5929
+ const remaining = source.remaining;
5930
+ if (remaining != null && (typeof remaining !== "number" || !Number.isInteger(remaining) || remaining < 0)) {
5931
+ return void 0;
5932
+ }
5933
+ const result = {
5934
+ index,
5935
+ count,
5936
+ remaining: remaining == null ? null : remaining
5937
+ };
5938
+ if (source.id !== void 0) {
5939
+ if (typeof source.id !== "string" || !source.id.trim()) return void 0;
5940
+ result.id = source.id.trim();
5941
+ }
5942
+ if (source.name !== void 0) {
5943
+ if (typeof source.name !== "string" || !source.name.trim()) return void 0;
5944
+ result.name = source.name.trim();
5945
+ }
5946
+ if (source.categoryKey !== void 0) {
5947
+ if (source.categoryKey !== null && (typeof source.categoryKey !== "string" || !source.categoryKey.trim())) {
5948
+ return void 0;
5949
+ }
5950
+ result.categoryKey = source.categoryKey == null ? null : source.categoryKey.trim();
5951
+ }
5952
+ for (const key of ["startsAt", "endsAt"]) {
5953
+ if (source[key] === void 0) continue;
5954
+ const parsed = timestamp(source[key]);
5955
+ if (parsed === void 0) return void 0;
5956
+ result[key] = parsed;
5957
+ }
5958
+ return result;
5959
+ }
5960
+ function parseTicketOfferAvailability(body) {
5961
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
5962
+ const raw = body;
5963
+ const state = SALE_STATES.find((candidate) => candidate === raw.state);
5964
+ if (!state) return null;
5965
+ const fromPrice = money(raw.fromPrice);
5966
+ const previousPrice = money(raw.previousPrice);
5967
+ if (fromPrice === void 0 || previousPrice === void 0) return null;
5968
+ const currency = raw.currency == null ? null : typeof raw.currency === "string" && raw.currency.trim() ? raw.currency.trim() : void 0;
5969
+ if (currency === void 0) return null;
5970
+ const release = parseSummary(raw.release);
5971
+ if (release === void 0) return null;
5972
+ const upcoming = raw.upcoming === void 0 ? null : parseSummary(raw.upcoming);
5973
+ if (upcoming === void 0) return null;
5974
+ let prices = [];
5975
+ if (raw.prices != null) {
5976
+ if (!Array.isArray(raw.prices)) return null;
5977
+ const parsed = [];
5978
+ for (const entry of raw.prices) {
5979
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
5980
+ const row = entry;
5981
+ const categoryKey = typeof row.categoryKey === "string" ? row.categoryKey.trim() : "";
5982
+ if (!categoryKey) return null;
5983
+ const price = money(row.price);
5984
+ const previous = money(row.previousPrice);
5985
+ if (price === void 0 || price === null || previous === void 0) return null;
5986
+ const item = { categoryKey, price, previousPrice: previous };
5987
+ if (row.offerId !== void 0) {
5988
+ if (typeof row.offerId !== "string" || !row.offerId.trim()) return null;
5989
+ item.offerId = row.offerId.trim();
5990
+ }
5991
+ if (row.offerName !== void 0) {
5992
+ if (typeof row.offerName !== "string" || !row.offerName.trim()) return null;
5993
+ item.offerName = row.offerName.trim();
5994
+ }
5995
+ if (row.remaining !== void 0) {
5996
+ if (row.remaining !== null && (typeof row.remaining !== "number" || !Number.isInteger(row.remaining) || row.remaining < 0)) return null;
5997
+ item.remaining = row.remaining == null ? null : row.remaining;
5998
+ }
5999
+ for (const key of ["startsAt", "endsAt"]) {
6000
+ if (row[key] === void 0) continue;
6001
+ const at = timestamp(row[key]);
6002
+ if (at === void 0) return null;
6003
+ item[key] = at;
6004
+ }
6005
+ parsed.push(item);
6006
+ }
6007
+ prices = parsed;
6008
+ }
6009
+ return { state, fromPrice, previousPrice, currency, release, upcoming, prices };
6010
+ }
6011
+ function nextOfferTransitionAt(availability, now) {
6012
+ if (!availability) return null;
6013
+ let next = null;
6014
+ const consider = (at) => {
6015
+ if (at != null && at > now && (next === null || at < next)) next = at;
6016
+ };
6017
+ for (const summary of [availability.release, availability.upcoming]) {
6018
+ consider(summary?.startsAt);
6019
+ consider(summary?.endsAt);
6020
+ }
6021
+ for (const price of availability.prices) {
6022
+ consider(price.startsAt);
6023
+ consider(price.endsAt);
6024
+ }
6025
+ return next;
6026
+ }
6027
+ function ticketOfferPrices(availability) {
6028
+ const map = {};
6029
+ for (const entry of availability?.prices ?? []) map[entry.categoryKey] = entry.price;
6030
+ return map;
6031
+ }
6032
+
6033
+ // src/SeatPicker.ts
5898
6034
  var import_meta = {};
5899
6035
  var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
5900
6036
  var DEFAULT_MAX_SELECTION2 = 10;
@@ -6116,6 +6252,15 @@ var CSS2 = (
6116
6252
  .sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
6117
6253
 
6118
6254
  /* price panel \u2014 one compact filter control replaces the wrapping price-chip row. */
6255
+ .sl-offer{display:none;margin:12px 14px 2px;padding:12px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));
6256
+ border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
6257
+ .sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
6258
+ .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)}
6259
+ .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)}
6260
+ .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;
6261
+ display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
6262
+ .sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent)}
6263
+ .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)}
6119
6264
  .sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
6120
6265
  .sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
6121
6266
  .sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
@@ -6126,6 +6271,7 @@ var CSS2 = (
6126
6271
  padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
6127
6272
  .sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
6128
6273
  .sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
6274
+ .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}
6129
6275
  .sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
6130
6276
  .sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
6131
6277
  .sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
@@ -6851,10 +6997,17 @@ var SeatPicker = class _SeatPicker {
6851
6997
  this.ro = null;
6852
6998
  this.holdTimer = null;
6853
6999
  this.toastTimer = null;
7000
+ this.offerRefreshTimer = null;
7001
+ /** Armed only when the offer schedule has a known future transition (or as a
7002
+ * bounded retry after a failed read) — never a fixed-cadence poll. */
7003
+ this.offerBoundaryTimer = null;
7004
+ this.offerVisibilityHandler = null;
6854
7005
  /** Short-lived UI motion timers; all are cancelled on destroy. */
6855
7006
  this.motionTimers = /* @__PURE__ */ new Set();
6856
7007
  // state
6857
7008
  this.currency = "USD";
7009
+ this.eventTimezone = null;
7010
+ this.offerAvailability = null;
6858
7011
  this.hold = null;
6859
7012
  /** Latest server expiry for the open hold (moves on extend). */
6860
7013
  this.holdExpiresAt = 0;
@@ -6968,6 +7121,7 @@ var SeatPicker = class _SeatPicker {
6968
7121
  if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
6969
7122
  if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
6970
7123
  this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
7124
+ this.hostPricing = options.pricing;
6971
7125
  this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
6972
7126
  this.access = options.transport ? null : createBuyerAccessContext(options, {
6973
7127
  onExpired: (event) => {
@@ -7009,6 +7163,7 @@ var SeatPicker = class _SeatPicker {
7009
7163
  },
7010
7164
  onStatusChange: () => {
7011
7165
  this.syncPrices();
7166
+ this.scheduleOfferRefresh(true);
7012
7167
  this.evictTakenSelections();
7013
7168
  this.detectBooked();
7014
7169
  this.refreshMinimap();
@@ -7430,6 +7585,7 @@ var SeatPicker = class _SeatPicker {
7430
7585
  </div>
7431
7586
  <div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
7432
7587
  <div class="sl-filters" data-ref="filters"></div>
7588
+ <div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
7433
7589
  <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
7434
7590
  <div class="sl-prices" data-ref="prices"></div>
7435
7591
  <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>
@@ -7450,6 +7606,13 @@ var SeatPicker = class _SeatPicker {
7450
7606
  this.els[el2.dataset.ref] = el2;
7451
7607
  });
7452
7608
  this.mapHost = this.els.map;
7609
+ void this.refreshOfferAvailability(false);
7610
+ if (this.api.availability) {
7611
+ this.offerVisibilityHandler = () => {
7612
+ if (!document.hidden && !this.destroyed) void this.refreshOfferAvailability(false);
7613
+ };
7614
+ document.addEventListener("visibilitychange", this.offerVisibilityHandler);
7615
+ }
7453
7616
  const applyLayout = () => {
7454
7617
  const w = root.clientWidth;
7455
7618
  if (w <= 0) return;
@@ -7569,6 +7732,7 @@ var SeatPicker = class _SeatPicker {
7569
7732
  const chartTheme = this.controller.doc?.theme;
7570
7733
  Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
7571
7734
  this.currency = info.currency ?? this.opts.currency ?? "USD";
7735
+ this.eventTimezone = info.timezone ?? null;
7572
7736
  const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl;
7573
7737
  if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
7574
7738
  else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
@@ -8909,6 +9073,119 @@ var SeatPicker = class _SeatPicker {
8909
9073
  return `${n} ${this.currency}`;
8910
9074
  }
8911
9075
  }
9076
+ /**
9077
+ * Sleep until the offer schedule's next known transition, then re-read.
9078
+ *
9079
+ * A far-away boundary is capped: the wake re-reads, learns the (unchanged)
9080
+ * schedule, and re-arms — so a picker left open for days still tracks an
9081
+ * organizer's schedule edits at a cost of one request every few hours. No
9082
+ * future transition means no timer at all; an event with no releases does
9083
+ * zero background traffic. A wake in a hidden tab fetches nothing — the
9084
+ * visibilitychange handler owns catching that tab up.
9085
+ */
9086
+ scheduleOfferBoundary(availability) {
9087
+ if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
9088
+ this.offerBoundaryTimer = null;
9089
+ if (!this.api.availability || this.destroyed) return;
9090
+ const now = Date.now();
9091
+ const boundary = nextOfferTransitionAt(availability, now);
9092
+ if (boundary == null) return;
9093
+ const MAX_SLEEP_MS = 6 * 36e5;
9094
+ const delay = Math.min(Math.max(boundary - now + 1e3, 1e3), MAX_SLEEP_MS);
9095
+ this.offerBoundaryTimer = setTimeout(() => {
9096
+ this.offerBoundaryTimer = null;
9097
+ if (document.hidden) return;
9098
+ void this.refreshOfferAvailability(false);
9099
+ }, delay);
9100
+ }
9101
+ /** Debounce the no-store offer read behind a burst of seat-status frames. */
9102
+ scheduleOfferRefresh(live) {
9103
+ if (!this.api.availability || this.destroyed) return;
9104
+ if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
9105
+ this.offerRefreshTimer = setTimeout(() => {
9106
+ this.offerRefreshTimer = null;
9107
+ void this.refreshOfferAvailability(live);
9108
+ }, live ? 180 : 0);
9109
+ }
9110
+ /**
9111
+ * Pull the server's resolved answer. A failed refresh keeps the last truthful
9112
+ * answer: flashing back to a chart price while checkout still charges an
9113
+ * offer is worse than a temporarily stale remaining count.
9114
+ */
9115
+ async refreshOfferAvailability(live) {
9116
+ if (!this.api.availability || this.destroyed) return;
9117
+ try {
9118
+ const body = await this.api.availability(this.opts.event, live);
9119
+ if (this.destroyed) return;
9120
+ const availability = parseTicketOfferAvailability(body);
9121
+ if (!availability) return;
9122
+ this.offerAvailability = availability;
9123
+ this.scheduleOfferBoundary(availability);
9124
+ const server = ticketOfferPrices(availability);
9125
+ const merged = { ...this.hostPricing?.prices ?? {}, ...server };
9126
+ const pricing = Object.keys(merged).length > 0 || this.hostPricing?.formatter ? { prices: merged, ...this.hostPricing?.formatter ? { formatter: this.hostPricing.formatter } : {} } : void 0;
9127
+ this.setPricing(pricing);
9128
+ this.syncOffer();
9129
+ this.opts.onOfferAvailabilityChange?.(availability);
9130
+ } catch {
9131
+ if (!this.destroyed && !this.offerBoundaryTimer && !document.hidden) {
9132
+ this.offerBoundaryTimer = setTimeout(() => {
9133
+ this.offerBoundaryTimer = null;
9134
+ if (document.hidden) return;
9135
+ void this.refreshOfferAvailability(false);
9136
+ }, 3e4);
9137
+ }
9138
+ }
9139
+ }
9140
+ offerPrice(categoryKey) {
9141
+ if (!categoryKey) return null;
9142
+ return this.offerAvailability?.prices.find((entry) => entry.categoryKey === categoryKey) ?? null;
9143
+ }
9144
+ /** The compact current/upcoming offer card above Ticket prices. */
9145
+ syncOffer() {
9146
+ const host = this.els.offer;
9147
+ if (!host) return;
9148
+ const availability = this.offerAvailability;
9149
+ const active = availability?.release ?? null;
9150
+ const upcoming = !active ? availability?.upcoming ?? null : null;
9151
+ if (!availability || availability.state === "closed" || availability.state === "sold-out" || !active && !upcoming) {
9152
+ host.classList.remove("has");
9153
+ host.replaceChildren();
9154
+ return;
9155
+ }
9156
+ const offer = active ?? upcoming;
9157
+ const main = document.createElement("div");
9158
+ main.className = "sl-offer-main";
9159
+ const copy = document.createElement("div");
9160
+ copy.className = "sl-offer-copy";
9161
+ const kicker = document.createElement("span");
9162
+ kicker.className = "sl-offer-kicker";
9163
+ kicker.textContent = active ? "Current ticket offer" : "Upcoming ticket offer";
9164
+ const name = document.createElement("strong");
9165
+ name.className = "sl-offer-name";
9166
+ name.textContent = offer.name || (active ? "Current offer" : "Scheduled offer");
9167
+ const line = document.createElement("span");
9168
+ line.className = "sl-offer-line";
9169
+ const facts = [];
9170
+ if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
9171
+ if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
9172
+ if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
9173
+ if (upcoming?.startsAt != null) facts.push(`starts ${formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale)}`);
9174
+ line.textContent = facts.join(" \xB7 ");
9175
+ copy.append(kicker, name, line);
9176
+ const info = document.createElement("details");
9177
+ info.className = "sl-offer-info";
9178
+ const summary = document.createElement("summary");
9179
+ summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
9180
+ summary.textContent = "i";
9181
+ const detail = document.createElement("div");
9182
+ detail.className = "sl-offer-detail";
9183
+ 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.`;
9184
+ info.append(summary, detail);
9185
+ main.append(copy, info);
9186
+ host.replaceChildren(main);
9187
+ host.classList.add("has");
9188
+ }
8912
9189
  /**
8913
9190
  * The price the buyer will actually pay for a category (+tier): the host's
8914
9191
  * `pricing` override when present, else the chart's stored price. Every
@@ -8935,9 +9212,11 @@ var SeatPicker = class _SeatPicker {
8935
9212
  this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
8936
9213
  this.els.prices.innerHTML = shown.map((c) => {
8937
9214
  const price = this.catPrice(c);
9215
+ const offer = this.offerPrice(c.key);
9216
+ const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
8938
9217
  const active = this.focusedCatKey === c.key;
8939
9218
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
8940
- 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>`;
9219
+ 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>`;
8941
9220
  }).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>`;
8942
9221
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
8943
9222
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
@@ -9593,6 +9872,7 @@ var SeatPicker = class _SeatPicker {
9593
9872
  }
9594
9873
  emitHoldChange() {
9595
9874
  const hold = this.hold;
9875
+ this.scheduleOfferRefresh(true);
9596
9876
  this.opts.onHoldChange?.(
9597
9877
  hold,
9598
9878
  hold?.seats ?? [],
@@ -10322,6 +10602,7 @@ var SeatPicker = class _SeatPicker {
10322
10602
  flashOnLiveChange: true,
10323
10603
  onStatusChange: () => {
10324
10604
  this.syncPrices();
10605
+ this.scheduleOfferRefresh(true);
10325
10606
  this.detectBooked();
10326
10607
  this.refreshMinimap();
10327
10608
  this.pushAvailabilityTo3d();
@@ -10457,6 +10738,14 @@ var SeatPicker = class _SeatPicker {
10457
10738
  this.stopHoldTimer();
10458
10739
  if (this.toastTimer) clearTimeout(this.toastTimer);
10459
10740
  if (this.liveTimer) clearTimeout(this.liveTimer);
10741
+ if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
10742
+ if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
10743
+ this.offerRefreshTimer = null;
10744
+ this.offerBoundaryTimer = null;
10745
+ if (this.offerVisibilityHandler) {
10746
+ document.removeEventListener("visibilitychange", this.offerVisibilityHandler);
10747
+ this.offerVisibilityHandler = null;
10748
+ }
10460
10749
  for (const timer of this.motionTimers) clearTimeout(timer);
10461
10750
  this.motionTimers.clear();
10462
10751
  this.ro?.disconnect();
@@ -13131,10 +13420,12 @@ init_manageApi();
13131
13420
  markerOf,
13132
13421
  mutationCount,
13133
13422
  needsMoveConfirmation,
13423
+ parseTicketOfferAvailability,
13134
13424
  planAssignment,
13135
13425
  retryAfterCopy,
13136
13426
  selectionSources,
13137
13427
  stateBadge,
13138
- suggestMarker
13428
+ suggestMarker,
13429
+ ticketOfferPrices
13139
13430
  });
13140
13431
  //# sourceMappingURL=index.cjs.map