@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 +158 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +158 -39
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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
|
-
|
|
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
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
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
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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"
|
|
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>` +
|
|
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"
|
|
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
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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"
|
|
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;
|