@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.d.cts CHANGED
@@ -684,6 +684,8 @@ declare class SeatPicker {
684
684
  private srEl;
685
685
  private baQty;
686
686
  private baCat;
687
+ /** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
688
+ private baPremium;
687
689
  private bestAvailableConfirm;
688
690
  private releasingHold;
689
691
  /** Event sales window is closed (read-only load state / live close). */
@@ -736,6 +738,20 @@ declare class SeatPicker {
736
738
  * sightline" line — the premium at-a-glance moment; click opens the 360.
737
739
  */
738
740
  private confirmThumbHtml;
741
+ /** Minimal HTML/attribute escaper for buyer-authored commercial text (notes). */
742
+ private escCx;
743
+ /** Localized "Restricted view" / "Obstructed view" label for a seat's flags,
744
+ * or '' when neither is set. Restricted takes precedence when both are on. */
745
+ private limitedViewLabel;
746
+ /**
747
+ * Commercial flags block for the confirm/detail surface: a subtle ★ Premium
748
+ * tag plus an amber ◐ limited-view caution (with the organizer's note when
749
+ * present). '' when the seat carries no surfaced commercial flag.
750
+ */
751
+ private commercialConfirmHtml;
752
+ /** Small ◐ limited-view marker for a cart chip; title/aria uses the seat's
753
+ * note when present, else the generic view label. '' for a clear-view seat. */
754
+ private commercialChipMarker;
739
755
  /** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
740
756
  private isFramed;
741
757
  /**
@@ -997,6 +1013,9 @@ declare class SeatPicker {
997
1013
  * the prefix is exact (won't touch "1040-A" under section "104"); otherwise
998
1014
  * the label is shown verbatim.
999
1015
  */
1016
+ /** Buyer-facing type word for the row/table key label — the designer's
1017
+ * per-object "Displayed type" override, or the default "Row". */
1018
+ private rowTypeWord;
1000
1019
  private rowShort;
1001
1020
  private updateTooltip;
1002
1021
  getSelection(): PickerSeat[];
@@ -1006,7 +1025,9 @@ declare class SeatPicker {
1006
1025
  resumeHold(holdId: string): Promise<HoldResult | null>;
1007
1026
  /** Remove one server-held ticket while keeping the rest of the hold active. */
1008
1027
  removeHeldTicket(label: string): Promise<boolean>;
1009
- bestAvailable(qty: number, categoryKey?: string): Promise<HoldResult | null>;
1028
+ bestAvailable(qty: number, categoryKey?: string, opts?: {
1029
+ preferPremium?: boolean;
1030
+ }): Promise<HoldResult | null>;
1010
1031
  release(): Promise<void>;
1011
1032
  destroy(): void;
1012
1033
  }
package/dist/index.d.ts CHANGED
@@ -684,6 +684,8 @@ declare class SeatPicker {
684
684
  private srEl;
685
685
  private baQty;
686
686
  private baCat;
687
+ /** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
688
+ private baPremium;
687
689
  private bestAvailableConfirm;
688
690
  private releasingHold;
689
691
  /** Event sales window is closed (read-only load state / live close). */
@@ -736,6 +738,20 @@ declare class SeatPicker {
736
738
  * sightline" line — the premium at-a-glance moment; click opens the 360.
737
739
  */
738
740
  private confirmThumbHtml;
741
+ /** Minimal HTML/attribute escaper for buyer-authored commercial text (notes). */
742
+ private escCx;
743
+ /** Localized "Restricted view" / "Obstructed view" label for a seat's flags,
744
+ * or '' when neither is set. Restricted takes precedence when both are on. */
745
+ private limitedViewLabel;
746
+ /**
747
+ * Commercial flags block for the confirm/detail surface: a subtle ★ Premium
748
+ * tag plus an amber ◐ limited-view caution (with the organizer's note when
749
+ * present). '' when the seat carries no surfaced commercial flag.
750
+ */
751
+ private commercialConfirmHtml;
752
+ /** Small ◐ limited-view marker for a cart chip; title/aria uses the seat's
753
+ * note when present, else the generic view label. '' for a clear-view seat. */
754
+ private commercialChipMarker;
739
755
  /** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
740
756
  private isFramed;
741
757
  /**
@@ -997,6 +1013,9 @@ declare class SeatPicker {
997
1013
  * the prefix is exact (won't touch "1040-A" under section "104"); otherwise
998
1014
  * the label is shown verbatim.
999
1015
  */
1016
+ /** Buyer-facing type word for the row/table key label — the designer's
1017
+ * per-object "Displayed type" override, or the default "Row". */
1018
+ private rowTypeWord;
1000
1019
  private rowShort;
1001
1020
  private updateTooltip;
1002
1021
  getSelection(): PickerSeat[];
@@ -1006,7 +1025,9 @@ declare class SeatPicker {
1006
1025
  resumeHold(holdId: string): Promise<HoldResult | null>;
1007
1026
  /** Remove one server-held ticket while keeping the rest of the hold active. */
1008
1027
  removeHeldTicket(label: string): Promise<boolean>;
1009
- bestAvailable(qty: number, categoryKey?: string): Promise<HoldResult | null>;
1028
+ bestAvailable(qty: number, categoryKey?: string, opts?: {
1029
+ preferPremium?: boolean;
1030
+ }): Promise<HoldResult | null>;
1010
1031
  release(): Promise<void>;
1011
1032
  destroy(): void;
1012
1033
  }
package/dist/index.js CHANGED
@@ -1438,6 +1438,17 @@ var CSS = `
1438
1438
  .sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
1439
1439
  .sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
1440
1440
  .sl-ba-copy .narrow{display:none}
1441
+ /* "\u2605 Best seats" premium quick-pick \u2014 gold accent echoing the \u2605 Premium pill on
1442
+ the confirm popover; deliberately distinct from the accent-toned qty/go. */
1443
+ .sl-ba-premium{position:relative;z-index:1;grid-column:1/-1;justify-self:start;display:inline-flex;align-items:center;gap:6px;
1444
+ padding:6px 12px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;cursor:pointer;
1445
+ color:#c9a24b;background:color-mix(in srgb,#e8c15a 10%,var(--sl-surface));
1446
+ border:1px solid color-mix(in srgb,#e8c15a 34%,var(--sl-line));transition:filter .15s,background .15s,color .15s}
1447
+ .sl-ba-premium .star{font-size:12px;line-height:1;color:#e8c15a}
1448
+ .sl-ba-premium:hover{filter:brightness(1.05)}
1449
+ .sl-ba-premium.on{color:#1c1608;background:linear-gradient(135deg,#f0cf6b,#e0b23f);border-color:transparent;
1450
+ box-shadow:0 6px 16px color-mix(in srgb,#e8c15a 26%,transparent)}
1451
+ .sl-ba-premium.on .star{color:#5a4410}
1441
1452
  .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
1442
1453
  font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
1443
1454
  .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)}
@@ -1535,6 +1546,25 @@ var CSS = `
1535
1546
  .sl-confirm-view:hover{border-color:var(--sl-muted)}
1536
1547
  .sl-confirm-view svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
1537
1548
 
1549
+ /* commercial seat flags \u2014 limited-view caution + premium tag. Amber tone,
1550
+ deliberately distinct from the red taken/held state; shown on the confirm
1551
+ card, echoed as a small \u25D0 marker on cart chips and the hover tip. */
1552
+ .sl-cx{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}
1553
+ .sl-cx-warn{display:flex;align-items:flex-start;gap:7px;padding:8px 10px;border-radius:9px;
1554
+ background:color-mix(in srgb,#f4b740 13%,var(--sl-surface));border:1px solid color-mix(in srgb,#f4b740 40%,var(--sl-line));
1555
+ animation:slNoticeIn .28s ease both}
1556
+ .sl-cx-glyph{flex:none;font-size:14px;line-height:1.2;color:#f4b740}
1557
+ .sl-cx-txt{min-width:0;display:flex;flex-direction:column;gap:2px}
1558
+ .sl-cx-txt b{font-size:12px;font-weight:800;color:var(--sl-text)}
1559
+ .sl-cx-note{font-size:11px;line-height:1.4;color:var(--sl-muted)}
1560
+ .sl-cx-premium{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;padding:4px 10px;border-radius:999px;
1561
+ font-size:11px;font-weight:800;letter-spacing:.02em;color:#c9a24b;
1562
+ background:color-mix(in srgb,#e8c15a 13%,var(--sl-surface));border:1px solid color-mix(in srgb,#e8c15a 38%,var(--sl-line))}
1563
+ .sl-cx-star{font-size:12px;line-height:1;color:#e8c15a}
1564
+ .sl-cx-mark{flex:none;font-size:12px;line-height:1;color:#f4b740;cursor:help}
1565
+ .sl-tip-cx{display:flex;align-items:center;gap:6px;padding:5px 10px 7px;font-size:10.5px;font-weight:700;color:#e8b24a}
1566
+ .sl-tip-cx .g{font-size:12px}
1567
+
1538
1568
  /* 360\xB0 seat-view modal (fills the widget; drag-to-look-around equirectangular) */
1539
1569
  .sl-view{position:absolute;inset:0;z-index:12;display:flex;flex-direction:column;background:var(--sl-bg)}
1540
1570
  .sl-view-head{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
@@ -1663,6 +1693,8 @@ var SeatPicker = class _SeatPicker {
1663
1693
  this.srEl = null;
1664
1694
  this.baQty = 2;
1665
1695
  this.baCat = "";
1696
+ /** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
1697
+ this.baPremium = false;
1666
1698
  this.bestAvailableConfirm = false;
1667
1699
  this.releasingHold = false;
1668
1700
  /** Event sales window is closed (read-only load state / live close). */
@@ -1819,6 +1851,50 @@ var SeatPicker = class _SeatPicker {
1819
1851
  const sight = distance != null ? t2("picker.sightline", { m: distance }) : this.tf("picker.sightlineClear", "Clear sightline");
1820
1852
  return `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${t2("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>`;
1821
1853
  }
1854
+ /** Minimal HTML/attribute escaper for buyer-authored commercial text (notes). */
1855
+ escCx(value) {
1856
+ return String(value ?? "").replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch]);
1857
+ }
1858
+ /** Localized "Restricted view" / "Obstructed view" label for a seat's flags,
1859
+ * or '' when neither is set. Restricted takes precedence when both are on. */
1860
+ limitedViewLabel(c) {
1861
+ if (c?.restrictedView) return this.tf("picker.restrictedView", "Restricted view");
1862
+ if (c?.obstructedView) return this.tf("picker.obstructedView", "Obstructed view");
1863
+ return "";
1864
+ }
1865
+ /**
1866
+ * Commercial flags block for the confirm/detail surface: a subtle ★ Premium
1867
+ * tag plus an amber ◐ limited-view caution (with the organizer's note when
1868
+ * present). '' when the seat carries no surfaced commercial flag.
1869
+ */
1870
+ commercialConfirmHtml(c) {
1871
+ if (!c) return "";
1872
+ const rows = [];
1873
+ if (c.premium) {
1874
+ rows.push(
1875
+ `<div class="sl-cx-premium"><span class="sl-cx-star" aria-hidden="true">\u2605</span>${this.tf("picker.premiumSeat", "Premium seat")}</div>`
1876
+ );
1877
+ }
1878
+ const limited = this.limitedViewLabel(c);
1879
+ if (limited) {
1880
+ rows.push(
1881
+ `<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>`
1882
+ );
1883
+ } else if (c.note) {
1884
+ rows.push(
1885
+ `<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>`
1886
+ );
1887
+ }
1888
+ return rows.length ? `<div class="sl-cx">${rows.join("")}</div>` : "";
1889
+ }
1890
+ /** Small ◐ limited-view marker for a cart chip; title/aria uses the seat's
1891
+ * note when present, else the generic view label. '' for a clear-view seat. */
1892
+ commercialChipMarker(c) {
1893
+ const limited = this.limitedViewLabel(c);
1894
+ if (!limited) return "";
1895
+ const title = this.escCx(c?.note ? c.note : limited);
1896
+ return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
1897
+ }
1822
1898
  /** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
1823
1899
  isFramed() {
1824
1900
  return typeof window !== "undefined" && window.parent !== window;
@@ -2191,45 +2267,71 @@ var SeatPicker = class _SeatPicker {
2191
2267
  this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
2192
2268
  this.buildBadge(chartTheme);
2193
2269
  const present = /* @__PURE__ */ new Set();
2270
+ let hasLimitedView = false;
2194
2271
  if (this.controller.doc) {
2195
2272
  for (const seat of expandChart(this.controller.doc)) {
2196
2273
  for (const type of seat.accessibility ?? []) present.add(type);
2197
2274
  if (seat.accessible && !seat.accessibility?.length) present.add("wheelchair");
2275
+ if (seat.commercial?.restrictedView || seat.commercial?.obstructedView) hasLimitedView = true;
2198
2276
  }
2199
2277
  }
2200
- if (present.size) {
2278
+ const focusSeatsForFilter = () => {
2279
+ if (this.rungsEl && this.controller.getRung() !== "seats") {
2280
+ this.controller.setRung("seats");
2281
+ this.collapseSectionCard();
2282
+ this.syncRung();
2283
+ }
2284
+ };
2285
+ if (present.size || hasLimitedView) {
2201
2286
  const chips = document.createElement("div");
2202
2287
  chips.className = "sl-chips";
2203
- const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
2204
- const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-f="${key}">${label}</button>`;
2205
- chips.innerHTML = mk("all", "All seats") + [...present].map((type) => mk(type, `${GLYPH[type] ? GLYPH[type] + " " : ""}${type[0].toUpperCase()}${type.slice(1).replace(/-/g, " ")}`)).join("");
2206
2288
  this.regions["top-left"].appendChild(chips);
2207
2289
  this.a11yChipsEl = chips;
2208
- const active = /* @__PURE__ */ new Set();
2209
- const syncChips = () => {
2210
- chips.querySelectorAll("button").forEach((b) => {
2211
- const f = b.dataset.f;
2212
- const on = f === "all" ? active.size === 0 : active.has(f);
2213
- b.classList.toggle("on", on);
2214
- b.setAttribute("aria-pressed", String(on));
2290
+ if (present.size) {
2291
+ const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
2292
+ const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
2293
+ chips.insertAdjacentHTML(
2294
+ "beforeend",
2295
+ mk("all", "All seats") + [...present].map((type) => mk(type, `${GLYPH[type] ? GLYPH[type] + " " : ""}${type[0].toUpperCase()}${type.slice(1).replace(/-/g, " ")}`)).join("")
2296
+ );
2297
+ const active = /* @__PURE__ */ new Set();
2298
+ const syncChips = () => {
2299
+ chips.querySelectorAll("button[data-a11y]").forEach((b) => {
2300
+ const f = b.dataset.f;
2301
+ const on = f === "all" ? active.size === 0 : active.has(f);
2302
+ b.classList.toggle("on", on);
2303
+ b.setAttribute("aria-pressed", String(on));
2304
+ });
2305
+ const filter = active.size ? [...active] : null;
2306
+ this.controller.setAccessibilityFilter(filter);
2307
+ if (filter) focusSeatsForFilter();
2308
+ };
2309
+ chips.querySelectorAll("button[data-a11y]").forEach((btn) => {
2310
+ btn.addEventListener("click", () => {
2311
+ const f = btn.dataset.f;
2312
+ if (f === "all") active.clear();
2313
+ else if (active.has(f)) active.delete(f);
2314
+ else active.add(f);
2315
+ syncChips();
2316
+ });
2215
2317
  });
2216
- const filter = active.size ? [...active] : null;
2217
- this.controller.setAccessibilityFilter(filter);
2218
- if (filter && this.rungsEl && this.controller.getRung() !== "seats") {
2219
- this.controller.setRung("seats");
2220
- this.collapseSectionCard();
2221
- this.syncRung();
2222
- }
2223
- };
2224
- chips.querySelectorAll("button").forEach((btn) => {
2225
- btn.addEventListener("click", () => {
2226
- const f = btn.dataset.f;
2227
- if (f === "all") active.clear();
2228
- else if (active.has(f)) active.delete(f);
2229
- else active.add(f);
2230
- syncChips();
2318
+ }
2319
+ if (hasLimitedView) {
2320
+ const limited = document.createElement("button");
2321
+ limited.type = "button";
2322
+ limited.className = "sl-chip-f";
2323
+ limited.setAttribute("aria-pressed", "false");
2324
+ limited.innerHTML = `\u25D0 ${this.tf("picker.hideLimitedView", "Hide limited-view seats")}`;
2325
+ chips.appendChild(limited);
2326
+ let limitedOn = false;
2327
+ limited.addEventListener("click", () => {
2328
+ limitedOn = !limitedOn;
2329
+ limited.classList.toggle("on", limitedOn);
2330
+ limited.setAttribute("aria-pressed", String(limitedOn));
2331
+ this.controller.setCommercialLimitedFilter(limitedOn);
2332
+ if (limitedOn) focusSeatsForFilter();
2231
2333
  });
2232
- });
2334
+ }
2233
2335
  }
2234
2336
  const cb = document.createElement("button");
2235
2337
  cb.type = "button";
@@ -2952,7 +3054,7 @@ var SeatPicker = class _SeatPicker {
2952
3054
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
2953
3055
  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>`;
2954
3056
  }).join("");
2955
- 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 ${t2("picker.overview")}</button><span class="sl-seccard-hint">${t2("picker.tapSeatHint")}</span></div>`;
3057
+ 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">${t2("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 ${t2("picker.overview")}</button><span class="sl-seccard-hint">${t2("picker.tapSeatHint")}</span></div>`;
2956
3058
  card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
2957
3059
  card.querySelector(".sl-seccard-overview").addEventListener("click", () => this.controller.overview());
2958
3060
  (this.regions["top-center"] ?? this.els.map).appendChild(card);
@@ -3046,7 +3148,7 @@ var SeatPicker = class _SeatPicker {
3046
3148
  el.setAttribute("aria-modal", "true");
3047
3149
  el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
3048
3150
  el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
3049
- 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>`;
3151
+ 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>`;
3050
3152
  this.els.map.appendChild(el);
3051
3153
  this.confirmEl = el;
3052
3154
  this.reanchorConfirm();
@@ -3341,14 +3443,16 @@ var SeatPicker = class _SeatPicker {
3341
3443
  const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
3342
3444
  if (!this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
3343
3445
  const cats = this.controller.doc?.categories ?? [];
3344
- 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>`);
3446
+ 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
3447
+ // seats (same present-only philosophy as the a11y filter chips).
3448
+ (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>`);
3345
3449
  }
3346
3450
  const idGrid = (seatId, label) => {
3347
3451
  const d = seatId ? this.controller.seatDetails(seatId) : null;
3348
3452
  if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
3349
3453
  return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Seat</span><span class="val">${label}</span></span></div>`;
3350
3454
  }
3351
- 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>`;
3455
+ 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>`;
3352
3456
  };
3353
3457
  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="${t2("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>`;
3354
3458
  for (const item of heldItems) {
@@ -3359,7 +3463,7 @@ var SeatPicker = class _SeatPicker {
3359
3463
  const heldSeat = item.objectType !== "ga" ? this.controller.seatByLabel(item.label) : null;
3360
3464
  const canView2 = this.seatViewEnabled() && !!heldSeat;
3361
3465
  parts.push(
3362
- `<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>`
3466
+ `<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>`
3363
3467
  );
3364
3468
  }
3365
3469
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -3370,7 +3474,7 @@ var SeatPicker = class _SeatPicker {
3370
3474
  const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
3371
3475
  const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${t2("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>` : "";
3372
3476
  parts.push(
3373
- `<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>`
3477
+ `<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>`
3374
3478
  );
3375
3479
  }
3376
3480
  for (const area of gaAreas) {
@@ -3390,6 +3494,10 @@ var SeatPicker = class _SeatPicker {
3390
3494
  this.els.tray.querySelector("[data-ba-cat]")?.addEventListener("change", (e) => {
3391
3495
  this.baCat = e.target.value;
3392
3496
  });
3497
+ this.els.tray.querySelector("[data-ba-premium]")?.addEventListener("click", () => {
3498
+ this.baPremium = !this.baPremium;
3499
+ this.syncTray();
3500
+ });
3393
3501
  this.els.tray.querySelector(".sl-ba-go")?.addEventListener("click", () => {
3394
3502
  if (this.pendingSelectionCount() > 0) {
3395
3503
  this.bestAvailableConfirm = true;
@@ -3397,7 +3505,7 @@ var SeatPicker = class _SeatPicker {
3397
3505
  this.els.tray.querySelector("[data-ba-replace]")?.focus();
3398
3506
  return;
3399
3507
  }
3400
- void this.bestAvailable(this.baQty, this.baCat || void 0);
3508
+ void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
3401
3509
  });
3402
3510
  this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
3403
3511
  this.bestAvailableConfirm = false;
@@ -3406,7 +3514,7 @@ var SeatPicker = class _SeatPicker {
3406
3514
  });
3407
3515
  this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
3408
3516
  this.bestAvailableConfirm = false;
3409
- void this.bestAvailable(this.baQty, this.baCat || void 0);
3517
+ void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
3410
3518
  });
3411
3519
  this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
3412
3520
  btn.addEventListener("click", () => {
@@ -3788,6 +3896,12 @@ var SeatPicker = class _SeatPicker {
3788
3896
  * the prefix is exact (won't touch "1040-A" under section "104"); otherwise
3789
3897
  * the label is shown verbatim.
3790
3898
  */
3899
+ /** Buyer-facing type word for the row/table key label — the designer's
3900
+ * per-object "Displayed type" override, or the default "Row". */
3901
+ rowTypeWord(details) {
3902
+ const t3 = details?.rowType?.trim();
3903
+ return t3 || "Row";
3904
+ }
3791
3905
  rowShort(details) {
3792
3906
  const row = details?.rowLabel;
3793
3907
  const sec = details?.sectionLabel;
@@ -3807,10 +3921,12 @@ var SeatPicker = class _SeatPicker {
3807
3921
  const esc2 = (v) => String(v ?? "\u2014").replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch]);
3808
3922
  const price = this.money(this.paidPrice(details.categoryKey, details.tierId ?? null, details.price));
3809
3923
  const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
3810
- 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>`;
3924
+ 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>`;
3811
3925
  const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${details.status === "held" ? t2("map.statusHeld") : t2("map.statusTaken")}</div>`;
3926
+ const limited = this.limitedViewLabel(details.commercial);
3927
+ const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc2(limited)}</div>` : "";
3812
3928
  this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
3813
- 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;
3929
+ 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;
3814
3930
  this.tipEl.style.display = "block";
3815
3931
  this.placeTooltip();
3816
3932
  }
@@ -3830,7 +3946,7 @@ var SeatPicker = class _SeatPicker {
3830
3946
  async removeHeldTicket(label) {
3831
3947
  return this.removeHeldLabel(label);
3832
3948
  }
3833
- async bestAvailable(qty, categoryKey) {
3949
+ async bestAvailable(qty, categoryKey, opts = {}) {
3834
3950
  if (this.salesClosed || this.bestAvailableBusy) return null;
3835
3951
  qty = Math.max(1, Math.min(this.maxTickets, Math.floor(qty)));
3836
3952
  if (this.confirmSeat) this.cancelConfirm();
@@ -3842,7 +3958,7 @@ var SeatPicker = class _SeatPicker {
3842
3958
  button.innerHTML = '<span class="sl-ba-spin" aria-hidden="true"></span>Finding\u2026';
3843
3959
  }
3844
3960
  try {
3845
- const h = await this.controller.bestAvailable(qty, categoryKey);
3961
+ const h = await this.controller.bestAvailable(qty, categoryKey, opts);
3846
3962
  if (h) {
3847
3963
  this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
3848
3964
  this.handedOff = false;
@@ -3852,6 +3968,9 @@ var SeatPicker = class _SeatPicker {
3852
3968
  this.flashHeldSeats(this.hold);
3853
3969
  this.syncTray();
3854
3970
  this.emitHoldChange();
3971
+ if (opts.preferPremium && h.seats.length && !h.seats.every((s) => s.commercial?.premium)) {
3972
+ this.toast(t2("picker.premiumFallbackNote", { count: qty }), "neutral");
3973
+ }
3855
3974
  return this.hold;
3856
3975
  }
3857
3976
  return null;