@seatlayer/js 0.24.0 → 0.25.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/dist/index.cjs CHANGED
@@ -1462,6 +1462,17 @@ var CSS = `
1462
1462
  .sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
1463
1463
  .sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
1464
1464
  .sl-ba-copy .narrow{display:none}
1465
+ /* "\u2605 Best seats" premium quick-pick \u2014 gold accent echoing the \u2605 Premium pill on
1466
+ the confirm popover; deliberately distinct from the accent-toned qty/go. */
1467
+ .sl-ba-premium{position:relative;z-index:1;grid-column:1/-1;justify-self:start;display:inline-flex;align-items:center;gap:6px;
1468
+ padding:6px 12px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;cursor:pointer;
1469
+ color:#c9a24b;background:color-mix(in srgb,#e8c15a 10%,var(--sl-surface));
1470
+ border:1px solid color-mix(in srgb,#e8c15a 34%,var(--sl-line));transition:filter .15s,background .15s,color .15s}
1471
+ .sl-ba-premium .star{font-size:12px;line-height:1;color:#e8c15a}
1472
+ .sl-ba-premium:hover{filter:brightness(1.05)}
1473
+ .sl-ba-premium.on{color:#1c1608;background:linear-gradient(135deg,#f0cf6b,#e0b23f);border-color:transparent;
1474
+ box-shadow:0 6px 16px color-mix(in srgb,#e8c15a 26%,transparent)}
1475
+ .sl-ba-premium.on .star{color:#5a4410}
1465
1476
  .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
1466
1477
  font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
1467
1478
  .sl-ba-qty{display:flex;align-items:center;gap:7px;padding:3px;border:1px solid var(--sl-line);border-radius:9px;background:var(--sl-surface)}
@@ -1559,6 +1570,25 @@ var CSS = `
1559
1570
  .sl-confirm-view:hover{border-color:var(--sl-muted)}
1560
1571
  .sl-confirm-view svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
1561
1572
 
1573
+ /* commercial seat flags \u2014 limited-view caution + premium tag. Amber tone,
1574
+ deliberately distinct from the red taken/held state; shown on the confirm
1575
+ card, echoed as a small \u25D0 marker on cart chips and the hover tip. */
1576
+ .sl-cx{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}
1577
+ .sl-cx-warn{display:flex;align-items:flex-start;gap:7px;padding:8px 10px;border-radius:9px;
1578
+ background:color-mix(in srgb,#f4b740 13%,var(--sl-surface));border:1px solid color-mix(in srgb,#f4b740 40%,var(--sl-line));
1579
+ animation:slNoticeIn .28s ease both}
1580
+ .sl-cx-glyph{flex:none;font-size:14px;line-height:1.2;color:#f4b740}
1581
+ .sl-cx-txt{min-width:0;display:flex;flex-direction:column;gap:2px}
1582
+ .sl-cx-txt b{font-size:12px;font-weight:800;color:var(--sl-text)}
1583
+ .sl-cx-note{font-size:11px;line-height:1.4;color:var(--sl-muted)}
1584
+ .sl-cx-premium{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;padding:4px 10px;border-radius:999px;
1585
+ font-size:11px;font-weight:800;letter-spacing:.02em;color:#c9a24b;
1586
+ background:color-mix(in srgb,#e8c15a 13%,var(--sl-surface));border:1px solid color-mix(in srgb,#e8c15a 38%,var(--sl-line))}
1587
+ .sl-cx-star{font-size:12px;line-height:1;color:#e8c15a}
1588
+ .sl-cx-mark{flex:none;font-size:12px;line-height:1;color:#f4b740;cursor:help}
1589
+ .sl-tip-cx{display:flex;align-items:center;gap:6px;padding:5px 10px 7px;font-size:10.5px;font-weight:700;color:#e8b24a}
1590
+ .sl-tip-cx .g{font-size:12px}
1591
+
1562
1592
  /* 360\xB0 seat-view modal (fills the widget; drag-to-look-around equirectangular) */
1563
1593
  .sl-view{position:absolute;inset:0;z-index:12;display:flex;flex-direction:column;background:var(--sl-bg)}
1564
1594
  .sl-view-head{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
@@ -1687,6 +1717,8 @@ var SeatPicker = class _SeatPicker {
1687
1717
  this.srEl = null;
1688
1718
  this.baQty = 2;
1689
1719
  this.baCat = "";
1720
+ /** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
1721
+ this.baPremium = false;
1690
1722
  this.bestAvailableConfirm = false;
1691
1723
  this.releasingHold = false;
1692
1724
  /** Event sales window is closed (read-only load state / live close). */
@@ -1843,6 +1875,50 @@ var SeatPicker = class _SeatPicker {
1843
1875
  const sight = distance != null ? (0, import_core2.t)("picker.sightline", { m: distance }) : this.tf("picker.sightlineClear", "Clear sightline");
1844
1876
  return `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" src="${url}" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button><div class="sl-confirm-sight"><span aria-hidden="true">\u2713</span>${sight}</div>`;
1845
1877
  }
1878
+ /** Minimal HTML/attribute escaper for buyer-authored commercial text (notes). */
1879
+ escCx(value) {
1880
+ return String(value ?? "").replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch]);
1881
+ }
1882
+ /** Localized "Restricted view" / "Obstructed view" label for a seat's flags,
1883
+ * or '' when neither is set. Restricted takes precedence when both are on. */
1884
+ limitedViewLabel(c) {
1885
+ if (c?.restrictedView) return this.tf("picker.restrictedView", "Restricted view");
1886
+ if (c?.obstructedView) return this.tf("picker.obstructedView", "Obstructed view");
1887
+ return "";
1888
+ }
1889
+ /**
1890
+ * Commercial flags block for the confirm/detail surface: a subtle ★ Premium
1891
+ * tag plus an amber ◐ limited-view caution (with the organizer's note when
1892
+ * present). '' when the seat carries no surfaced commercial flag.
1893
+ */
1894
+ commercialConfirmHtml(c) {
1895
+ if (!c) return "";
1896
+ const rows = [];
1897
+ if (c.premium) {
1898
+ rows.push(
1899
+ `<div class="sl-cx-premium"><span class="sl-cx-star" aria-hidden="true">\u2605</span>${this.tf("picker.premiumSeat", "Premium seat")}</div>`
1900
+ );
1901
+ }
1902
+ const limited = this.limitedViewLabel(c);
1903
+ if (limited) {
1904
+ rows.push(
1905
+ `<div class="sl-cx-warn"><span class="sl-cx-glyph" aria-hidden="true">\u25D0</span><span class="sl-cx-txt"><b>${limited}</b>${c.note ? `<span class="sl-cx-note">${this.escCx(c.note)}</span>` : ""}</span></div>`
1906
+ );
1907
+ } else if (c.note) {
1908
+ rows.push(
1909
+ `<div class="sl-cx-warn"><span class="sl-cx-glyph" aria-hidden="true">\u2139</span><span class="sl-cx-txt"><span class="sl-cx-note">${this.escCx(c.note)}</span></span></div>`
1910
+ );
1911
+ }
1912
+ return rows.length ? `<div class="sl-cx">${rows.join("")}</div>` : "";
1913
+ }
1914
+ /** Small ◐ limited-view marker for a cart chip; title/aria uses the seat's
1915
+ * note when present, else the generic view label. '' for a clear-view seat. */
1916
+ commercialChipMarker(c) {
1917
+ const limited = this.limitedViewLabel(c);
1918
+ if (!limited) return "";
1919
+ const title = this.escCx(c?.note ? c.note : limited);
1920
+ return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
1921
+ }
1846
1922
  /** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
1847
1923
  isFramed() {
1848
1924
  return typeof window !== "undefined" && window.parent !== window;
@@ -2215,45 +2291,71 @@ var SeatPicker = class _SeatPicker {
2215
2291
  this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
2216
2292
  this.buildBadge(chartTheme);
2217
2293
  const present = /* @__PURE__ */ new Set();
2294
+ let hasLimitedView = false;
2218
2295
  if (this.controller.doc) {
2219
2296
  for (const seat of (0, import_core2.expandChart)(this.controller.doc)) {
2220
2297
  for (const type of seat.accessibility ?? []) present.add(type);
2221
2298
  if (seat.accessible && !seat.accessibility?.length) present.add("wheelchair");
2299
+ if (seat.commercial?.restrictedView || seat.commercial?.obstructedView) hasLimitedView = true;
2222
2300
  }
2223
2301
  }
2224
- if (present.size) {
2302
+ const focusSeatsForFilter = () => {
2303
+ if (this.rungsEl && this.controller.getRung() !== "seats") {
2304
+ this.controller.setRung("seats");
2305
+ this.collapseSectionCard();
2306
+ this.syncRung();
2307
+ }
2308
+ };
2309
+ if (present.size || hasLimitedView) {
2225
2310
  const chips = document.createElement("div");
2226
2311
  chips.className = "sl-chips";
2227
- const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
2228
- const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-f="${key}">${label}</button>`;
2229
- chips.innerHTML = mk("all", "All seats") + [...present].map((type) => mk(type, `${GLYPH[type] ? GLYPH[type] + " " : ""}${type[0].toUpperCase()}${type.slice(1).replace(/-/g, " ")}`)).join("");
2230
2312
  this.regions["top-left"].appendChild(chips);
2231
2313
  this.a11yChipsEl = chips;
2232
- const active = /* @__PURE__ */ new Set();
2233
- const syncChips = () => {
2234
- chips.querySelectorAll("button").forEach((b) => {
2235
- const f = b.dataset.f;
2236
- const on = f === "all" ? active.size === 0 : active.has(f);
2237
- b.classList.toggle("on", on);
2238
- b.setAttribute("aria-pressed", String(on));
2314
+ if (present.size) {
2315
+ const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
2316
+ const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
2317
+ chips.insertAdjacentHTML(
2318
+ "beforeend",
2319
+ mk("all", "All seats") + [...present].map((type) => mk(type, `${GLYPH[type] ? GLYPH[type] + " " : ""}${type[0].toUpperCase()}${type.slice(1).replace(/-/g, " ")}`)).join("")
2320
+ );
2321
+ const active = /* @__PURE__ */ new Set();
2322
+ const syncChips = () => {
2323
+ chips.querySelectorAll("button[data-a11y]").forEach((b) => {
2324
+ const f = b.dataset.f;
2325
+ const on = f === "all" ? active.size === 0 : active.has(f);
2326
+ b.classList.toggle("on", on);
2327
+ b.setAttribute("aria-pressed", String(on));
2328
+ });
2329
+ const filter = active.size ? [...active] : null;
2330
+ this.controller.setAccessibilityFilter(filter);
2331
+ if (filter) focusSeatsForFilter();
2332
+ };
2333
+ chips.querySelectorAll("button[data-a11y]").forEach((btn) => {
2334
+ btn.addEventListener("click", () => {
2335
+ const f = btn.dataset.f;
2336
+ if (f === "all") active.clear();
2337
+ else if (active.has(f)) active.delete(f);
2338
+ else active.add(f);
2339
+ syncChips();
2340
+ });
2239
2341
  });
2240
- const filter = active.size ? [...active] : null;
2241
- this.controller.setAccessibilityFilter(filter);
2242
- if (filter && this.rungsEl && this.controller.getRung() !== "seats") {
2243
- this.controller.setRung("seats");
2244
- this.collapseSectionCard();
2245
- this.syncRung();
2246
- }
2247
- };
2248
- chips.querySelectorAll("button").forEach((btn) => {
2249
- btn.addEventListener("click", () => {
2250
- const f = btn.dataset.f;
2251
- if (f === "all") active.clear();
2252
- else if (active.has(f)) active.delete(f);
2253
- else active.add(f);
2254
- syncChips();
2342
+ }
2343
+ if (hasLimitedView) {
2344
+ const limited = document.createElement("button");
2345
+ limited.type = "button";
2346
+ limited.className = "sl-chip-f";
2347
+ limited.setAttribute("aria-pressed", "false");
2348
+ limited.innerHTML = `\u25D0 ${this.tf("picker.hideLimitedView", "Hide limited-view seats")}`;
2349
+ chips.appendChild(limited);
2350
+ let limitedOn = false;
2351
+ limited.addEventListener("click", () => {
2352
+ limitedOn = !limitedOn;
2353
+ limited.classList.toggle("on", limitedOn);
2354
+ limited.setAttribute("aria-pressed", String(limitedOn));
2355
+ this.controller.setCommercialLimitedFilter(limitedOn);
2356
+ if (limitedOn) focusSeatsForFilter();
2255
2357
  });
2256
- });
2358
+ }
2257
2359
  }
2258
2360
  const cb = document.createElement("button");
2259
2361
  cb.type = "button";
@@ -2976,7 +3078,7 @@ var SeatPicker = class _SeatPicker {
2976
3078
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
2977
3079
  return `<span class="sl-seccard-mix-item${dim ? " sl-dim" : ""}"><span class="sl-seccard-mix-dot" style="background:${c.color}"></span>${c.label} <span class="sl-seccard-mix-price">${this.money(this.paidPrice(c.key, null, c.price))}</span></span>`;
2978
3080
  }).join("");
2979
- card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${(0, import_core2.t)("picker.overview")}</button><span class="sl-seccard-hint">${(0, import_core2.t)("picker.tapSeatHint")}</span></div>`;
3081
+ card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (summary.entrance ? `<div class="sl-seccard-entrance">${(0, import_core2.t)("picker.entrance")} ${String(summary.entrance).replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch])}</div>` : "") + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${(0, import_core2.t)("picker.overview")}</button><span class="sl-seccard-hint">${(0, import_core2.t)("picker.tapSeatHint")}</span></div>`;
2980
3082
  card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
2981
3083
  card.querySelector(".sl-seccard-overview").addEventListener("click", () => this.controller.overview());
2982
3084
  (this.regions["top-center"] ?? this.els.map).appendChild(card);
@@ -3070,7 +3172,7 @@ var SeatPicker = class _SeatPicker {
3070
3172
  el.setAttribute("aria-modal", "true");
3071
3173
  el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
3072
3174
  el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
3073
- el.innerHTML = `<div class="sl-confirm-grid"><div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details?.sectionLabel)}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key">Row</span><span class="sl-confirm-value">${safe(this.rowShort(details))}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key">Seat</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? seat.label)}</span></div></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.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + `<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>`;
3175
+ el.innerHTML = `<div class="sl-confirm-grid"><div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details?.sectionLabel)}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key">${safe(this.rowTypeWord(details))}</span><span class="sl-confirm-value">${safe(this.rowShort(details))}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key">Seat</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? seat.displayLabel ?? seat.label)}</span></div></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.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + `<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>`;
3074
3176
  this.els.map.appendChild(el);
3075
3177
  this.confirmEl = el;
3076
3178
  this.reanchorConfirm();
@@ -3365,14 +3467,16 @@ var SeatPicker = class _SeatPicker {
3365
3467
  const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
3366
3468
  if (!this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
3367
3469
  const cats = this.controller.doc?.categories ?? [];
3368
- parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Replace your current choices?</div><div class="sl-ba-replace"><b>We\u2019ll find ${this.baQty} seats together.</b><span>Your manually selected tickets will be removed only after a new group is secured.</span></div><div class="sl-ba-actions"><button type="button" data-ba-cancel>Keep mine</button><button type="button" class="replace" data-ba-replace>Find new seats</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Find the best seats together</div><div class="sl-ba-copy"><span class="wide">We\u2019ll choose the closest available group for you.</span><span class="narrow">Closest available group, chosen instantly.</span></div>` + (cats.length > 1 ? `<select aria-label="Preferred ticket type" data-ba-cat><option value="">Any ticket type</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : `<span aria-hidden="true"></span>`) + `<div class="sl-ba-qty"><button type="button" data-ba="-1" aria-label="Fewer seats">\u2212</button><span>${this.baQty}</span><button type="button" data-ba="1" aria-label="More seats">+</button></div><button type="button" class="sl-ba-go"${this.bestAvailableBusy ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>Finding the best seats\u2026` : `Find ${this.baQty} best ${this.baQty === 1 ? "seat" : "seats"}`) + `</button></div>`);
3470
+ parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Replace your current choices?</div><div class="sl-ba-replace"><b>We\u2019ll find ${this.baQty} seats together.</b><span>Your manually selected tickets will be removed only after a new group is secured.</span></div><div class="sl-ba-actions"><button type="button" data-ba-cancel>Keep mine</button><button type="button" class="replace" data-ba-replace>Find new seats</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Find the best seats together</div><div class="sl-ba-copy"><span class="wide">We\u2019ll choose the closest available group for you.</span><span class="narrow">Closest available group, chosen instantly.</span></div>` + // Premium quick-pick present only when the chart actually has premium
3471
+ // seats (same present-only philosophy as the a11y filter chips).
3472
+ (this.controller.hasPremiumSeats() ? `<button type="button" class="sl-ba-premium${this.baPremium ? " on" : ""}" data-ba-premium aria-pressed="${this.baPremium ? "true" : "false"}"><span class="star" aria-hidden="true">\u2605</span>${this.tf("picker.bestSeatsPremium", "Best seats")}</button>` : "") + (cats.length > 1 ? `<select aria-label="Preferred ticket type" data-ba-cat><option value="">Any ticket type</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : `<span aria-hidden="true"></span>`) + `<div class="sl-ba-qty"><button type="button" data-ba="-1" aria-label="Fewer seats">\u2212</button><span>${this.baQty}</span><button type="button" data-ba="1" aria-label="More seats">+</button></div><button type="button" class="sl-ba-go"${this.bestAvailableBusy ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>Finding the best seats\u2026` : `Find ${this.baQty} best ${this.baQty === 1 ? "seat" : "seats"}`) + `</button></div>`);
3369
3473
  }
3370
3474
  const idGrid = (seatId, label) => {
3371
3475
  const d = seatId ? this.controller.seatDetails(seatId) : null;
3372
3476
  if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
3373
3477
  return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Seat</span><span class="val">${label}</span></span></div>`;
3374
3478
  }
3375
- return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${d.sectionLabel ?? "\u2014"}</span></span>` + (d.rowLabel ? `<span class="fld mid"><span class="sl-chip-eb">Row</span><span class="val">${this.rowShort(d)}</span></span>` : "") + (d.seatNumber ? `<span class="fld mid"><span class="sl-chip-eb">Seat</span><span class="val">${d.seatNumber}</span></span>` : "") + `</div>`;
3479
+ return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${d.sectionLabel ?? "\u2014"}</span></span>` + (d.rowLabel ? `<span class="fld mid"><span class="sl-chip-eb">${this.rowTypeWord(d)}</span><span class="val">${this.rowShort(d)}</span></span>` : "") + (d.seatNumber ? `<span class="fld mid"><span class="sl-chip-eb">Seat</span><span class="val">${d.seatNumber}</span></span>` : "") + `</div>`;
3376
3480
  };
3377
3481
  const iconRail = (rmAria, viewLabel) => `<div class="sl-chip-rail"><button type="button" class="rm" aria-label="${rmAria}"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>` + (viewLabel ? `<button type="button" class="view" data-view-label="${viewLabel}" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: viewLabel })}"><svg viewBox="0 0 24 24"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg></button>` : "") + `</div>`;
3378
3482
  for (const item of heldItems) {
@@ -3383,7 +3487,7 @@ var SeatPicker = class _SeatPicker {
3383
3487
  const heldSeat = item.objectType !== "ga" ? this.controller.seatByLabel(item.label) : null;
3384
3488
  const canView2 = this.seatViewEnabled() && !!heldSeat;
3385
3489
  parts.push(
3386
- `<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"${heldSeat ? ` data-locate="${heldSeat.id}"` : ""}><div class="sl-chip-main">` + idGrid(heldSeat?.id ?? null, item.label) + `<div class="sl-chip-sub"><span class="sl-ticket-state held" aria-label="Held for you" title="Held for you"><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></span><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span><span class="amt">${this.money(this.paidPrice(item.categoryKey, item.tierId, item.unitPrice) * (item.quantity ?? 1))}</span></div></div>` + iconRail(`Remove held ticket ${item.label}`, canView2 ? item.label : null) + `</div>`
3490
+ `<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"${heldSeat ? ` data-locate="${heldSeat.id}"` : ""}><div class="sl-chip-main">` + idGrid(heldSeat?.id ?? null, item.label) + `<div class="sl-chip-sub"><span class="sl-ticket-state held" aria-label="Held for you" title="Held for you"><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></span><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span>` + this.commercialChipMarker(heldSeat?.commercial) + `<span class="amt">${this.money(this.paidPrice(item.categoryKey, item.tierId, item.unitPrice) * (item.quantity ?? 1))}</span></div></div>` + iconRail(`Remove held ticket ${item.label}`, canView2 ? item.label : null) + `</div>`
3387
3491
  );
3388
3492
  }
3389
3493
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -3394,7 +3498,7 @@ var SeatPicker = class _SeatPicker {
3394
3498
  const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
3395
3499
  const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${(0, import_core2.t)("picker.ticketTierFor", { label: s.label })}">` + s.tiers.map((ti) => `<option value="${ti.id}"${ti.id === s.tierId ? " selected" : ""}>${ti.name} \xB7 ${this.money(this.paidPrice(s.categoryKey, ti.id, ti.price))}</option>`).join("") + `</select>` : "";
3396
3500
  parts.push(
3397
- `<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}" data-locate="${s.id}"><div class="sl-chip-main">` + idGrid(s.id, s.label) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="Selected" title="Selected"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price))}</span></div></div>` + iconRail(`Remove ${s.label}`, canView ? s.label : null) + `</div>`
3501
+ `<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}" data-locate="${s.id}"><div class="sl-chip-main">` + idGrid(s.id, s.displayLabel ?? s.label) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="Selected" title="Selected"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price))}</span></div></div>` + iconRail(`Remove ${s.label}`, canView ? s.label : null) + `</div>`
3398
3502
  );
3399
3503
  }
3400
3504
  for (const area of gaAreas) {
@@ -3414,6 +3518,10 @@ var SeatPicker = class _SeatPicker {
3414
3518
  this.els.tray.querySelector("[data-ba-cat]")?.addEventListener("change", (e) => {
3415
3519
  this.baCat = e.target.value;
3416
3520
  });
3521
+ this.els.tray.querySelector("[data-ba-premium]")?.addEventListener("click", () => {
3522
+ this.baPremium = !this.baPremium;
3523
+ this.syncTray();
3524
+ });
3417
3525
  this.els.tray.querySelector(".sl-ba-go")?.addEventListener("click", () => {
3418
3526
  if (this.pendingSelectionCount() > 0) {
3419
3527
  this.bestAvailableConfirm = true;
@@ -3421,7 +3529,7 @@ var SeatPicker = class _SeatPicker {
3421
3529
  this.els.tray.querySelector("[data-ba-replace]")?.focus();
3422
3530
  return;
3423
3531
  }
3424
- void this.bestAvailable(this.baQty, this.baCat || void 0);
3532
+ void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
3425
3533
  });
3426
3534
  this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
3427
3535
  this.bestAvailableConfirm = false;
@@ -3430,7 +3538,7 @@ var SeatPicker = class _SeatPicker {
3430
3538
  });
3431
3539
  this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
3432
3540
  this.bestAvailableConfirm = false;
3433
- void this.bestAvailable(this.baQty, this.baCat || void 0);
3541
+ void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
3434
3542
  });
3435
3543
  this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
3436
3544
  btn.addEventListener("click", () => {
@@ -3812,6 +3920,12 @@ var SeatPicker = class _SeatPicker {
3812
3920
  * the prefix is exact (won't touch "1040-A" under section "104"); otherwise
3813
3921
  * the label is shown verbatim.
3814
3922
  */
3923
+ /** Buyer-facing type word for the row/table key label — the designer's
3924
+ * per-object "Displayed type" override, or the default "Row". */
3925
+ rowTypeWord(details) {
3926
+ const t3 = details?.rowType?.trim();
3927
+ return t3 || "Row";
3928
+ }
3815
3929
  rowShort(details) {
3816
3930
  const row = details?.rowLabel;
3817
3931
  const sec = details?.sectionLabel;
@@ -3831,10 +3945,12 @@ var SeatPicker = class _SeatPicker {
3831
3945
  const esc2 = (v) => String(v ?? "\u2014").replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch]);
3832
3946
  const price = this.money(this.paidPrice(details.categoryKey, details.tierId ?? null, details.price));
3833
3947
  const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
3834
- const grid = hasLoc ? `<div class="sl-tip-grid"><div class="sl-tip-field"><span class="sl-tip-key">Section</span><span class="sl-tip-val">${esc2(details.sectionLabel)}</span></div><div class="sl-tip-field"><span class="sl-tip-key">Row</span><span class="sl-tip-val">${esc2(this.rowShort(details))}</span></div><div class="sl-tip-field"><span class="sl-tip-key">Seat</span><span class="sl-tip-val">${esc2(details.seatNumber ?? details.label)}</span></div></div>` : `<div class="sl-tip-grid one"><div class="sl-tip-field"><span class="sl-tip-key">Seat</span><span class="sl-tip-val">${esc2(details.label)}</span></div></div>`;
3948
+ const grid = hasLoc ? `<div class="sl-tip-grid"><div class="sl-tip-field"><span class="sl-tip-key">Section</span><span class="sl-tip-val">${esc2(details.sectionLabel)}</span></div><div class="sl-tip-field"><span class="sl-tip-key">${esc2(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc2(this.rowShort(details))}</span></div><div class="sl-tip-field"><span class="sl-tip-key">Seat</span><span class="sl-tip-val">${esc2(details.seatNumber ?? details.displayLabel ?? details.label)}</span></div></div>` : `<div class="sl-tip-grid one"><div class="sl-tip-field"><span class="sl-tip-key">Seat</span><span class="sl-tip-val">${esc2(details.displayLabel ?? details.label)}</span></div></div>`;
3835
3949
  const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${details.status === "held" ? (0, import_core2.t)("map.statusHeld") : (0, import_core2.t)("map.statusTaken")}</div>`;
3950
+ const limited = this.limitedViewLabel(details.commercial);
3951
+ const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc2(limited)}</div>` : "";
3836
3952
  this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
3837
- this.tipEl.innerHTML = grid + `<div class="sl-tip-cat"><span class="sl-tip-dot" style="background:${details.categoryColor}"></span><span class="sl-tip-name">${esc2(details.categoryLabel)}</span><span class="sl-tip-amt">${price}</span></div>` + statusLine;
3953
+ this.tipEl.innerHTML = grid + `<div class="sl-tip-cat"><span class="sl-tip-dot" style="background:${details.categoryColor}"></span><span class="sl-tip-name">${esc2(details.categoryLabel)}</span><span class="sl-tip-amt">${price}</span></div>` + cxLine + statusLine;
3838
3954
  this.tipEl.style.display = "block";
3839
3955
  this.placeTooltip();
3840
3956
  }
@@ -3854,7 +3970,7 @@ var SeatPicker = class _SeatPicker {
3854
3970
  async removeHeldTicket(label) {
3855
3971
  return this.removeHeldLabel(label);
3856
3972
  }
3857
- async bestAvailable(qty, categoryKey) {
3973
+ async bestAvailable(qty, categoryKey, opts = {}) {
3858
3974
  if (this.salesClosed || this.bestAvailableBusy) return null;
3859
3975
  qty = Math.max(1, Math.min(this.maxTickets, Math.floor(qty)));
3860
3976
  if (this.confirmSeat) this.cancelConfirm();
@@ -3866,7 +3982,7 @@ var SeatPicker = class _SeatPicker {
3866
3982
  button.innerHTML = '<span class="sl-ba-spin" aria-hidden="true"></span>Finding\u2026';
3867
3983
  }
3868
3984
  try {
3869
- const h = await this.controller.bestAvailable(qty, categoryKey);
3985
+ const h = await this.controller.bestAvailable(qty, categoryKey, opts);
3870
3986
  if (h) {
3871
3987
  this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
3872
3988
  this.handedOff = false;
@@ -3876,6 +3992,9 @@ var SeatPicker = class _SeatPicker {
3876
3992
  this.flashHeldSeats(this.hold);
3877
3993
  this.syncTray();
3878
3994
  this.emitHoldChange();
3995
+ if (opts.preferPremium && h.seats.length && !h.seats.every((s) => s.commercial?.premium)) {
3996
+ this.toast((0, import_core2.t)("picker.premiumFallbackNote", { count: qty }), "neutral");
3997
+ }
3879
3998
  return this.hold;
3880
3999
  }
3881
4000
  return null;