@seatlayer/js 0.26.0 → 0.28.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 +422 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -6
- package/dist/index.d.ts +57 -6
- package/dist/index.js +423 -53
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1089,6 +1089,15 @@ function resolveContainer3(container) {
|
|
|
1089
1089
|
}
|
|
1090
1090
|
return container;
|
|
1091
1091
|
}
|
|
1092
|
+
function escapeOption(value) {
|
|
1093
|
+
return String(value ?? "").replace(/[&<>"']/g, (character) => ({
|
|
1094
|
+
"&": "&",
|
|
1095
|
+
"<": "<",
|
|
1096
|
+
">": ">",
|
|
1097
|
+
'"': """,
|
|
1098
|
+
"'": "'"
|
|
1099
|
+
})[character]);
|
|
1100
|
+
}
|
|
1092
1101
|
var STYLE_ID = "seatlayer-picker-style";
|
|
1093
1102
|
var CSS = `
|
|
1094
1103
|
.sl-picker{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:420px;overflow:hidden;
|
|
@@ -1200,6 +1209,11 @@ var CSS = `
|
|
|
1200
1209
|
.sl-picker[data-layout="narrow"] .sl-filters.has{display:none}
|
|
1201
1210
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec.has,
|
|
1202
1211
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters.has{display:none}
|
|
1212
|
+
/* Accessibility and colour-safety controls must remain reachable on phones.
|
|
1213
|
+
Keep them out of the 86px peek, then reveal their consolidated row whenever
|
|
1214
|
+
the buyer explicitly opens the ticket panel. */
|
|
1215
|
+
.sl-picker[data-layout="narrow"][data-sheet="open"] .sl-filtersec.has{display:block!important}
|
|
1216
|
+
.sl-picker[data-layout="narrow"][data-sheet="open"] .sl-filters.has{display:flex!important}
|
|
1203
1217
|
.sl-cbbtn{width:32px;height:32px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
1204
1218
|
color:var(--sl-text);display:flex;align-items:center;justify-content:center;transition:border-color .15s}
|
|
1205
1219
|
.sl-cbbtn:hover{border-color:var(--sl-muted)}
|
|
@@ -1492,6 +1506,37 @@ var CSS = `
|
|
|
1492
1506
|
.sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
|
|
1493
1507
|
transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
1494
1508
|
|
|
1509
|
+
/* Atomic whole/variable-table chooser. Lives inside the widget so the same
|
|
1510
|
+
accessible dialog works in inline, modal, desktop and 390px mobile hosts. */
|
|
1511
|
+
.sl-table-scrim{position:absolute;inset:0;z-index:45;display:flex;align-items:center;justify-content:center;padding:18px;
|
|
1512
|
+
background:color-mix(in srgb,var(--sl-bg) 66%,transparent);backdrop-filter:blur(3px)}
|
|
1513
|
+
.sl-table-dialog{width:min(408px,100%);max-height:calc(100% - 24px);overflow:auto;border:1px solid var(--sl-line);
|
|
1514
|
+
border-radius:calc(var(--sl-radius) * 1.15);background:var(--sl-surface);box-shadow:0 28px 70px rgba(0,0,0,.42)}
|
|
1515
|
+
.sl-table-head{padding:18px 18px 14px;border-bottom:1px solid var(--sl-line)}
|
|
1516
|
+
.sl-table-eyebrow{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--sl-muted);font-weight:800}
|
|
1517
|
+
.sl-table-title{margin-top:5px;font-size:22px;line-height:1.15;font-weight:850}
|
|
1518
|
+
.sl-table-copy{margin-top:7px;color:var(--sl-muted);font-size:13px;line-height:1.45}
|
|
1519
|
+
.sl-table-body{padding:16px 18px 18px}
|
|
1520
|
+
.sl-table-summary{display:grid;grid-template-columns:1fr auto;gap:8px 16px;padding:12px;border:1px solid var(--sl-line);
|
|
1521
|
+
border-radius:var(--sl-r-sm);background:color-mix(in srgb,var(--sl-line) 22%,transparent);font-size:13px}
|
|
1522
|
+
.sl-table-summary b{font-size:15px}.sl-table-summary .muted{color:var(--sl-muted)}
|
|
1523
|
+
.sl-table-qtylabel{display:block;margin:16px 0 8px;font-size:12px;font-weight:800}
|
|
1524
|
+
.sl-table-stepper{display:grid;grid-template-columns:48px 1fr 48px;align-items:center;border:1px solid var(--sl-line);
|
|
1525
|
+
border-radius:12px;overflow:hidden;background:var(--sl-bg)}
|
|
1526
|
+
.sl-table-stepper button{height:48px;font-size:24px;font-weight:700;background:color-mix(in srgb,var(--sl-line) 34%,transparent)!important}
|
|
1527
|
+
.sl-table-stepper button:disabled{opacity:.38;cursor:not-allowed}
|
|
1528
|
+
.sl-table-stepper output{text-align:center;font-size:19px;font-weight:850;font-variant-numeric:tabular-nums}
|
|
1529
|
+
.sl-table-range{margin-top:7px;color:var(--sl-muted);font-size:11px;text-align:center}
|
|
1530
|
+
.sl-table-actions{display:flex;gap:9px;margin-top:17px}.sl-table-actions button{flex:1;min-height:46px;border-radius:10px;font-weight:800}
|
|
1531
|
+
.sl-table-cancel{border:1px solid var(--sl-line)!important;color:var(--sl-muted)!important}
|
|
1532
|
+
.sl-table-confirm{background:var(--sl-accent)!important;color:var(--sl-accent-ink)!important}
|
|
1533
|
+
.sl-table-confirm:disabled{opacity:.62;cursor:wait}
|
|
1534
|
+
.sl-table-edit{margin-left:4px;padding:2px 7px!important;border:1px solid var(--sl-line)!important;border-radius:999px!important;
|
|
1535
|
+
color:var(--sl-muted)!important;font-size:10px!important;font-weight:800!important}
|
|
1536
|
+
.sl-picker[data-layout="narrow"] .sl-table-scrim{align-items:flex-end;padding:0;background:rgba(5,7,12,.58)}
|
|
1537
|
+
.sl-picker[data-layout="narrow"] .sl-table-dialog{width:100%;max-height:min(78%,620px);border-radius:18px 18px 0 0;border-width:1px 0 0}
|
|
1538
|
+
.sl-picker[data-layout="narrow"] .sl-table-head{padding-top:22px}.sl-picker[data-layout="narrow"] .sl-table-body{padding-bottom:max(20px,env(safe-area-inset-bottom))}
|
|
1539
|
+
|
|
1495
1540
|
/* hover preview \u2014 a COMPACT echo of the confirm card (deliberately smaller: it's
|
|
1496
1541
|
a passing preview on hover, not the click/select action surface). Reuses the
|
|
1497
1542
|
Section\xB7Row\xB7Seat identity grid so hover, confirm and the cart chip all share
|
|
@@ -1555,6 +1600,7 @@ var CSS = `
|
|
|
1555
1600
|
.sl-picker[data-layout="narrow"] .sl-ba{padding:11px}
|
|
1556
1601
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
|
|
1557
1602
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
|
|
1603
|
+
.sl-picker[data-layout="narrow"] .sl-ba select{min-height:44px}
|
|
1558
1604
|
|
|
1559
1605
|
/* screen-reader live region */
|
|
1560
1606
|
.sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
@@ -1573,6 +1619,16 @@ var CSS = `
|
|
|
1573
1619
|
/* narrow: shrink the rung pills so the centered row can't reach the corner regions */
|
|
1574
1620
|
.sl-picker[data-layout="narrow"] .sl-rungs button{padding:5px 9px;font-size:9px;letter-spacing:.03em}
|
|
1575
1621
|
|
|
1622
|
+
/* Projection is deliberately a separate control from zoom/LOD. Perspective
|
|
1623
|
+
changes geometry; the two choices stay explicit and keyboard-native. */
|
|
1624
|
+
.sl-projection{display:inline-flex;align-items:center;gap:2px;padding:3px;border-radius:999px;
|
|
1625
|
+
background:var(--sl-surface);border:1px solid var(--sl-line);box-shadow:0 8px 24px -16px rgba(0,0,0,.65)}
|
|
1626
|
+
.sl-projection button{min-width:42px;min-height:30px;padding:5px 10px;border-radius:999px;color:var(--sl-muted);
|
|
1627
|
+
font-size:10px;font-weight:800;letter-spacing:.04em;white-space:nowrap}
|
|
1628
|
+
.sl-projection button:hover,.sl-projection button:focus-visible{color:var(--sl-text)}
|
|
1629
|
+
.sl-projection button.on{background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
1630
|
+
.sl-picker[data-layout="narrow"] .sl-projection button{min-width:38px;min-height:32px;padding:5px 8px;font-size:9.5px}
|
|
1631
|
+
|
|
1576
1632
|
/* multi-floor switcher (flows within the left-rail region) */
|
|
1577
1633
|
.sl-floors{display:none;flex-direction:column;gap:6px;max-width:100%}
|
|
1578
1634
|
.sl-floors.on{display:flex}
|
|
@@ -1701,6 +1757,7 @@ var CSS = `
|
|
|
1701
1757
|
.sl-picker *,.sl-modal-scrim *{animation-duration:.001ms!important;animation-iteration-count:1!important;
|
|
1702
1758
|
transition-duration:.001ms!important;scroll-behavior:auto!important}
|
|
1703
1759
|
}
|
|
1760
|
+
.sl-ba [data-ba-zone]{grid-column:1/-1;width:100%}
|
|
1704
1761
|
|
|
1705
1762
|
/* modal host */
|
|
1706
1763
|
.sl-modal-scrim{position:fixed;inset:0;z-index:2147483000;background:rgba(5,7,12,.66);display:flex;align-items:center;justify-content:center;padding:18px}
|
|
@@ -1776,9 +1833,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
1776
1833
|
this.tipPos = { x: 0, y: 0 };
|
|
1777
1834
|
this.confirmEl = null;
|
|
1778
1835
|
this.confirmSeat = null;
|
|
1836
|
+
this.tableDialogEl = null;
|
|
1837
|
+
this.tableDialog = null;
|
|
1838
|
+
this.tableDialogHeld = false;
|
|
1839
|
+
this.tableDialogReturnFocus = null;
|
|
1779
1840
|
this.srEl = null;
|
|
1780
1841
|
this.baQty = 2;
|
|
1781
1842
|
this.baCat = "";
|
|
1843
|
+
/** Optional navigation-zone scope for buyer best-available. */
|
|
1844
|
+
this.baZone = "";
|
|
1782
1845
|
/** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
|
|
1783
1846
|
this.baPremium = false;
|
|
1784
1847
|
this.bestAvailableConfirm = false;
|
|
@@ -1792,6 +1855,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1792
1855
|
this.cbSafe = false;
|
|
1793
1856
|
// arena / multi-floor / seat-view chrome
|
|
1794
1857
|
this.rungsEl = null;
|
|
1858
|
+
this.projectionEl = null;
|
|
1795
1859
|
this.floorsEl = null;
|
|
1796
1860
|
this.secCardEl = null;
|
|
1797
1861
|
this.viewEl = null;
|
|
@@ -1887,11 +1951,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
1887
1951
|
this.toast(this.tf("picker.salesClosedToast", "Sales are closed for this event."), "warning");
|
|
1888
1952
|
return;
|
|
1889
1953
|
}
|
|
1954
|
+
if (this.controller.tableSelection(seat.id)) return;
|
|
1890
1955
|
this.flashPickedSeat(seat.id);
|
|
1891
1956
|
if (this.opts.confirmSelection) this.showConfirm(seat);
|
|
1892
1957
|
},
|
|
1958
|
+
onTableSelectionRequest: (table) => {
|
|
1959
|
+
if (this.salesClosed) {
|
|
1960
|
+
this.controller.deselect(table.physicalSeatIds);
|
|
1961
|
+
this.toast(this.tf("picker.salesClosedToast", "Sales are closed for this event."), "warning");
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
this.showTableDialog(table, false);
|
|
1965
|
+
},
|
|
1893
1966
|
onDeselect: (seat) => {
|
|
1894
1967
|
if (this.confirmSeat?.id === seat.id) this.dismissConfirm();
|
|
1968
|
+
if (this.tableDialog?.physicalSeatIds.includes(seat.id)) this.dismissTableDialog();
|
|
1895
1969
|
},
|
|
1896
1970
|
onSelectionLimit: () => {
|
|
1897
1971
|
this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
|
|
@@ -1899,6 +1973,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1899
1973
|
onViewChange: () => {
|
|
1900
1974
|
this.reanchorConfirm();
|
|
1901
1975
|
this.syncRung();
|
|
1976
|
+
this.syncProjection();
|
|
1902
1977
|
this.drawMinimapRect();
|
|
1903
1978
|
this.sectionCardOnView();
|
|
1904
1979
|
},
|
|
@@ -1915,27 +1990,47 @@ var SeatPicker = class _SeatPicker {
|
|
|
1915
1990
|
onError: (err) => this.opts.onError?.(err)
|
|
1916
1991
|
});
|
|
1917
1992
|
}
|
|
1993
|
+
/** True when the chart carries a real performance anchor — a stage-kind shape.
|
|
1994
|
+
* Every chart has a `focalPoint` (a bare look-at coordinate), so its presence
|
|
1995
|
+
* alone never justifies a "to stage" claim; only an actual stage does. */
|
|
1996
|
+
chartHasStage() {
|
|
1997
|
+
const doc = this.controller.doc;
|
|
1998
|
+
if (!doc) return false;
|
|
1999
|
+
const objectSets = doc.floors?.length ? doc.floors.map((f) => f.objects ?? []) : [doc.objects ?? []];
|
|
2000
|
+
for (const objects of objectSets) {
|
|
2001
|
+
for (const obj of objects) {
|
|
2002
|
+
if (obj.type === "shape" && (obj.role === "stage" || obj.stageKind)) return true;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return false;
|
|
2006
|
+
}
|
|
1918
2007
|
/**
|
|
1919
|
-
* Eager sightline preview for the confirm card:
|
|
1920
|
-
*
|
|
1921
|
-
*
|
|
2008
|
+
* Eager sightline preview for the confirm card: the organizer's real view
|
|
2009
|
+
* photo, or — only when the chart has an actual stage to look at — a generated
|
|
2010
|
+
* forward view plus an "≈ Nm to stage · clear sightline" line. On a stageless
|
|
2011
|
+
* chart both the distance/sightline claim and the generic stage silhouette are
|
|
2012
|
+
* invented promises, so we suppress them; a real attached photo always shows.
|
|
2013
|
+
* (OV-52)
|
|
1922
2014
|
*/
|
|
1923
2015
|
confirmThumbHtml(seat) {
|
|
1924
2016
|
const doc = this.controller.doc;
|
|
1925
2017
|
if (!doc) return "";
|
|
1926
|
-
|
|
2018
|
+
const realPhoto = seat.viewUrl ?? "";
|
|
2019
|
+
const hasStage = this.chartHasStage();
|
|
2020
|
+
if (!realPhoto && !hasStage) return "";
|
|
2021
|
+
let url = realPhoto;
|
|
1927
2022
|
let distance = null;
|
|
1928
2023
|
if (!url) {
|
|
1929
2024
|
try {
|
|
1930
|
-
const thumb = (0, import_core2.generateSeatThumb)(seat, doc.focalPoint);
|
|
2025
|
+
const thumb = (0, import_core2.generateSeatThumb)(seat, seat.focalPoint ?? doc.focalPoint);
|
|
1931
2026
|
url = thumb.url;
|
|
1932
2027
|
distance = thumb.distanceM ?? null;
|
|
1933
2028
|
} catch {
|
|
1934
2029
|
return "";
|
|
1935
2030
|
}
|
|
1936
2031
|
}
|
|
1937
|
-
const sight =
|
|
1938
|
-
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
|
|
2032
|
+
const sightHtml = hasStage ? `<div class="sl-confirm-sight"><span aria-hidden="true">\u2713</span>${distance != null ? (0, import_core2.t)("picker.sightline", { m: distance }) : this.tf("picker.sightlineClear", "Clear sightline")}</div>` : "";
|
|
2033
|
+
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>` + sightHtml;
|
|
1939
2034
|
}
|
|
1940
2035
|
/** Minimal HTML/attribute escaper for buyer-authored commercial text (notes). */
|
|
1941
2036
|
escCx(value) {
|
|
@@ -1981,6 +2076,19 @@ var SeatPicker = class _SeatPicker {
|
|
|
1981
2076
|
const title = this.escCx(c?.note ? c.note : limited);
|
|
1982
2077
|
return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
|
|
1983
2078
|
}
|
|
2079
|
+
wheelchairProvisionLabel(type) {
|
|
2080
|
+
if (type === "no-seat") return "Empty wheelchair space";
|
|
2081
|
+
if (type === "seat-present") return "Accessible physical seat";
|
|
2082
|
+
return "";
|
|
2083
|
+
}
|
|
2084
|
+
wheelchairConfirmHtml(type) {
|
|
2085
|
+
const label = this.wheelchairProvisionLabel(type);
|
|
2086
|
+
return label ? `<div class="sl-cx"><div class="sl-cx-warn"><span class="sl-cx-glyph" aria-hidden="true">\u267F</span><span class="sl-cx-txt"><b>${label}</b></span></div></div>` : "";
|
|
2087
|
+
}
|
|
2088
|
+
wheelchairChipMarker(type) {
|
|
2089
|
+
const label = this.wheelchairProvisionLabel(type);
|
|
2090
|
+
return label ? `<span class="sl-cx-mark" role="img" aria-label="${label}" title="${label}">\u267F</span>` : "";
|
|
2091
|
+
}
|
|
1984
2092
|
/** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
|
|
1985
2093
|
isFramed() {
|
|
1986
2094
|
return typeof window !== "undefined" && window.parent !== window;
|
|
@@ -2132,7 +2240,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
2132
2240
|
});
|
|
2133
2241
|
picker.escHandler = (e) => {
|
|
2134
2242
|
if (e.key !== "Escape") return;
|
|
2135
|
-
if (picker.
|
|
2243
|
+
if (picker.tableDialog) {
|
|
2244
|
+
e.preventDefault();
|
|
2245
|
+
picker.cancelTableDialog();
|
|
2246
|
+
} else if (picker.confirmSeat) {
|
|
2136
2247
|
e.preventDefault();
|
|
2137
2248
|
picker.cancelConfirm();
|
|
2138
2249
|
} else if (picker.bestAvailableConfirm) {
|
|
@@ -2163,7 +2274,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
2163
2274
|
mount.appendChild(root);
|
|
2164
2275
|
root.addEventListener("keydown", (e) => {
|
|
2165
2276
|
if (e.key !== "Escape") return;
|
|
2166
|
-
if (this.
|
|
2277
|
+
if (this.tableDialog) {
|
|
2278
|
+
e.preventDefault();
|
|
2279
|
+
e.stopPropagation();
|
|
2280
|
+
this.cancelTableDialog();
|
|
2281
|
+
} else if (this.confirmSeat) {
|
|
2167
2282
|
e.preventDefault();
|
|
2168
2283
|
e.stopPropagation();
|
|
2169
2284
|
this.cancelConfirm();
|
|
@@ -2332,6 +2447,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2332
2447
|
}
|
|
2333
2448
|
this.els.boot.remove();
|
|
2334
2449
|
this.salesClosed = !!info.salesClosed;
|
|
2450
|
+
this.controller.setViewMode(this.opts.initialView ?? "flat");
|
|
2335
2451
|
this.buildRegions();
|
|
2336
2452
|
this.regions["bottom-right"].appendChild(this.els.zoom);
|
|
2337
2453
|
this.regions["bottom-center"].appendChild(this.els.toast);
|
|
@@ -2374,11 +2490,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
2374
2490
|
this.regions["top-left"].appendChild(chips);
|
|
2375
2491
|
this.a11yChipsEl = chips;
|
|
2376
2492
|
if (present.size) {
|
|
2377
|
-
const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
|
|
2378
2493
|
const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
|
|
2379
2494
|
chips.insertAdjacentHTML(
|
|
2380
2495
|
"beforeend",
|
|
2381
|
-
mk("all", "All seats") +
|
|
2496
|
+
mk("all", "All seats") + import_core2.ACCESSIBILITY_TYPES.filter(({ key }) => present.has(key)).map(({ key, short, icon }) => mk(key, `${icon} ${short}`)).join("")
|
|
2382
2497
|
);
|
|
2383
2498
|
const active = /* @__PURE__ */ new Set();
|
|
2384
2499
|
const syncChips = () => {
|
|
@@ -2428,10 +2543,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2428
2543
|
cb.innerHTML = '<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>';
|
|
2429
2544
|
this.els.zfit.parentElement.appendChild(cb);
|
|
2430
2545
|
cb.addEventListener("click", () => {
|
|
2431
|
-
this.
|
|
2432
|
-
cb.setAttribute("aria-pressed", String(this.cbSafe));
|
|
2433
|
-
this.controller.setColorblindSafe(this.cbSafe);
|
|
2434
|
-
writeStoredColorblind(this.cbSafe);
|
|
2546
|
+
this.setColorblindSafe(!this.cbSafe);
|
|
2435
2547
|
});
|
|
2436
2548
|
this.srEl = document.createElement("div");
|
|
2437
2549
|
this.srEl.className = "sl-sr";
|
|
@@ -2553,6 +2665,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2553
2665
|
this.applySalesClosed();
|
|
2554
2666
|
}
|
|
2555
2667
|
applySalesClosed() {
|
|
2668
|
+
if (this.salesClosed && this.tableDialog && !this.tableDialogHeld) this.cancelTableDialog();
|
|
2556
2669
|
const pill = this.els.closedPill;
|
|
2557
2670
|
if (pill) {
|
|
2558
2671
|
pill.classList.toggle("on", this.salesClosed);
|
|
@@ -2641,12 +2754,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
2641
2754
|
/** Update only the action affordance; selection callbacks must not refire. */
|
|
2642
2755
|
committedSelection() {
|
|
2643
2756
|
const candidateId = this.confirmSeat?.id;
|
|
2644
|
-
|
|
2757
|
+
const pendingTableId = this.tableDialog && !this.tableDialogHeld ? this.tableDialog.id : null;
|
|
2758
|
+
return this.controller.getSelection().filter((seat) => seat.id !== candidateId && seat.id !== pendingTableId);
|
|
2645
2759
|
}
|
|
2646
2760
|
pendingSelectionCount() {
|
|
2647
2761
|
const heldItems = this.hold?.items ?? [];
|
|
2648
2762
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
2649
|
-
const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).
|
|
2763
|
+
const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).reduce((sum, seat) => sum + (seat.quantity ?? 1), 0);
|
|
2650
2764
|
return pendingSeats + this.pendingGACount();
|
|
2651
2765
|
}
|
|
2652
2766
|
heldGACounts() {
|
|
@@ -2668,13 +2782,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
2668
2782
|
}
|
|
2669
2783
|
totalTicketCount() {
|
|
2670
2784
|
const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
|
|
2671
|
-
const freshSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).
|
|
2785
|
+
const freshSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).reduce((sum, seat) => sum + (seat.quantity ?? 1), 0);
|
|
2672
2786
|
return this.heldTicketCount() + freshSeats + this.pendingGACount();
|
|
2673
2787
|
}
|
|
2674
2788
|
/** Held tickets and standing quantities consume the same order-wide cap. */
|
|
2675
2789
|
updateSelectionCapacity() {
|
|
2676
2790
|
const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
|
|
2677
|
-
const selectedHeld = this.committedSelection().filter((seat) => heldLabels.has(seat.label)).
|
|
2791
|
+
const selectedHeld = this.committedSelection().filter((seat) => heldLabels.has(seat.label)).reduce((sum, seat) => sum + (seat.quantity ?? 1), 0);
|
|
2678
2792
|
const remaining = Math.max(0, this.maxTickets - this.heldTicketCount() - this.pendingGACount());
|
|
2679
2793
|
this.controller.setMaxSelection(selectedHeld + remaining);
|
|
2680
2794
|
}
|
|
@@ -2701,9 +2815,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
2701
2815
|
cta.textContent = this.tf("picker.salesClosedCta", "Sales closed");
|
|
2702
2816
|
return;
|
|
2703
2817
|
}
|
|
2704
|
-
if (this.confirmSeat) {
|
|
2818
|
+
if (this.confirmSeat || this.tableDialog && !this.tableDialogHeld) {
|
|
2705
2819
|
cta.disabled = true;
|
|
2706
|
-
cta.textContent = "Confirm or cancel this seat";
|
|
2820
|
+
cta.textContent = this.tableDialog ? "Confirm your table" : "Confirm or cancel this seat";
|
|
2707
2821
|
return;
|
|
2708
2822
|
}
|
|
2709
2823
|
if (this.ctaPhase === "holding") {
|
|
@@ -3012,11 +3126,26 @@ var SeatPicker = class _SeatPicker {
|
|
|
3012
3126
|
});
|
|
3013
3127
|
}
|
|
3014
3128
|
// ---- arena / multi-floor chrome -------------------------------------------
|
|
3015
|
-
/** Build
|
|
3129
|
+
/** Build projection, rung and floor controls for arena-scale charts. */
|
|
3016
3130
|
buildArenaChrome() {
|
|
3017
3131
|
const doc = this.controller.doc;
|
|
3018
3132
|
if (!doc || !this.els.map) return;
|
|
3019
3133
|
const hasSections = doc.objects.some((o) => o.type === "section") || (doc.floors ?? []).some((f) => f.objects.some((o) => o.type === "section"));
|
|
3134
|
+
if (hasSections) {
|
|
3135
|
+
const projection = document.createElement("div");
|
|
3136
|
+
projection.className = "sl-projection";
|
|
3137
|
+
projection.setAttribute("role", "group");
|
|
3138
|
+
projection.setAttribute("aria-label", "Map projection");
|
|
3139
|
+
projection.innerHTML = '<button type="button" data-projection="flat" aria-pressed="false" title="Flat 2D map">2D</button><button type="button" data-projection="perspective" aria-pressed="false" title="Perspective 2.5D map">2.5D</button>';
|
|
3140
|
+
projection.querySelectorAll("button").forEach((button) => {
|
|
3141
|
+
button.addEventListener("click", () => {
|
|
3142
|
+
this.setViewMode(button.dataset.projection);
|
|
3143
|
+
});
|
|
3144
|
+
});
|
|
3145
|
+
this.regions["top-right"].appendChild(projection);
|
|
3146
|
+
this.projectionEl = projection;
|
|
3147
|
+
this.syncProjection();
|
|
3148
|
+
}
|
|
3020
3149
|
if (hasSections) {
|
|
3021
3150
|
const RUNGS = ["zones", "sections", "seats"];
|
|
3022
3151
|
const pills = document.createElement("div");
|
|
@@ -3078,6 +3207,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
3078
3207
|
btn.setAttribute("aria-pressed", String(on));
|
|
3079
3208
|
});
|
|
3080
3209
|
}
|
|
3210
|
+
syncProjection() {
|
|
3211
|
+
if (!this.projectionEl) return;
|
|
3212
|
+
const active = this.controller.getViewMode();
|
|
3213
|
+
this.projectionEl.querySelectorAll("button").forEach((button) => {
|
|
3214
|
+
const on = button.dataset.projection === active;
|
|
3215
|
+
button.classList.toggle("on", on);
|
|
3216
|
+
button.setAttribute("aria-pressed", String(on));
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3081
3219
|
/** Reflect the active floor onto the switcher rail. */
|
|
3082
3220
|
syncFloors() {
|
|
3083
3221
|
if (!this.floorsEl) return;
|
|
@@ -3206,7 +3344,150 @@ var SeatPicker = class _SeatPicker {
|
|
|
3206
3344
|
const status = this.controller.getStatus(seat.id) ?? "free";
|
|
3207
3345
|
const statusText = status === "free" ? "available" : status === "held" ? "on hold" : "taken";
|
|
3208
3346
|
const price = cat ? this.catPrice(cat) : void 0;
|
|
3209
|
-
|
|
3347
|
+
const table = this.controller.tableSelection(seat.id);
|
|
3348
|
+
const details = table ?? this.controller.seatDetails(seat.id);
|
|
3349
|
+
const typeWord = this.rowTypeWord(details);
|
|
3350
|
+
const buyerName = details?.rowLabel ?? details?.displayLabel ?? seat.displayLabel ?? seat.label;
|
|
3351
|
+
const identity = table ? `${typeWord} ${buyerName}, ${table.bookingMode === "variable" ? `${table.minOccupancy} to ${table.maxOccupancy} guests` : `${table.capacity} guests`}` : details?.objectType === "booth" ? `${typeWord} ${buyerName}` : details?.objectType === "table" ? `${typeWord} ${buyerName}, seat ${details.seatNumber ?? seat.label}` : `Seat ${details?.displayLabel ?? seat.displayLabel ?? seat.label}`;
|
|
3352
|
+
this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
|
|
3353
|
+
}
|
|
3354
|
+
// ---- atomic table confirmation / guest quantity ---------------------------
|
|
3355
|
+
showTableDialog(table, held, returnFocus) {
|
|
3356
|
+
this.dismissTableDialog(false);
|
|
3357
|
+
this.tableDialog = { ...table };
|
|
3358
|
+
this.tableDialogHeld = held;
|
|
3359
|
+
this.tableDialogReturnFocus = returnFocus ?? document.activeElement;
|
|
3360
|
+
const esc2 = (value) => String(value ?? "").replace(/[&<>"']/g, (ch) => ({
|
|
3361
|
+
"&": "&",
|
|
3362
|
+
"<": "<",
|
|
3363
|
+
">": ">",
|
|
3364
|
+
'"': """,
|
|
3365
|
+
"'": "'"
|
|
3366
|
+
})[ch]);
|
|
3367
|
+
const cat = this.controller.doc?.categories.find((candidate) => candidate.key === table.categoryKey);
|
|
3368
|
+
const variable = table.bookingMode === "variable";
|
|
3369
|
+
const typeWord = this.rowTypeWord(table);
|
|
3370
|
+
const el = document.createElement("div");
|
|
3371
|
+
el.className = "sl-table-scrim";
|
|
3372
|
+
el.innerHTML = `<section class="sl-table-dialog" role="dialog" aria-modal="true" aria-labelledby="sl-table-title" aria-describedby="sl-table-copy"><div class="sl-table-head"><div class="sl-table-eyebrow">${esc2(variable ? `Flexible party \xB7 ${typeWord}` : `Whole ${typeWord}`)}</div><h2 class="sl-table-title" id="sl-table-title">${esc2(table.displayLabel ?? table.label)}</h2><p class="sl-table-copy" id="sl-table-copy">${variable ? `Choose how many guests will sit together. This table is held exclusively for your party.` : `All ${table.capacity} places are booked together as one exclusive table.`}</p></div><div class="sl-table-body"><div class="sl-table-summary"><span>${esc2(cat?.label ?? table.categoryKey)}</span><b data-table-unit>${this.money(this.paidPrice(table.categoryKey, table.tierId ?? null, table.price))} per guest</b><span class="muted">Table capacity</span><span>${table.capacity} guests</span><span class="muted">Total</span><b data-table-total></b></div>` + (variable ? `<label class="sl-table-qtylabel" id="sl-table-qty-label">Number of guests</label><div class="sl-table-stepper" role="group" aria-labelledby="sl-table-qty-label"><button type="button" data-table-step="-1" aria-label="Fewer guests">\u2212</button><output aria-live="polite" data-table-qty>${table.quantity}</output><button type="button" data-table-step="1" aria-label="More guests">+</button></div><div class="sl-table-range">Choose ${table.minOccupancy}\u2013${table.maxOccupancy} guests</div>` : `<input type="hidden" data-table-qty value="${table.capacity}">`) + `<div class="sl-table-actions"><button type="button" class="sl-table-cancel">Cancel</button><button type="button" class="sl-table-confirm">${held ? "Update table" : variable ? "Select table" : "Select whole table"}</button></div></div></section>`;
|
|
3373
|
+
this.root.appendChild(el);
|
|
3374
|
+
this.tableDialogEl = el;
|
|
3375
|
+
this.renderTableDialogState();
|
|
3376
|
+
el.querySelectorAll("[data-table-step]").forEach((button) => {
|
|
3377
|
+
button.addEventListener("click", () => {
|
|
3378
|
+
if (!this.tableDialog) return;
|
|
3379
|
+
const next = Math.max(
|
|
3380
|
+
this.tableDialog.minOccupancy,
|
|
3381
|
+
Math.min(this.tableDialog.maxOccupancy, this.tableDialog.quantity + Number(button.dataset.tableStep))
|
|
3382
|
+
);
|
|
3383
|
+
this.tableDialog = { ...this.tableDialog, quantity: next };
|
|
3384
|
+
this.renderTableDialogState();
|
|
3385
|
+
});
|
|
3386
|
+
});
|
|
3387
|
+
el.querySelector(".sl-table-cancel").addEventListener("click", () => this.cancelTableDialog());
|
|
3388
|
+
el.querySelector(".sl-table-confirm").addEventListener("click", () => void this.confirmTableDialog());
|
|
3389
|
+
el.addEventListener("mousedown", (event) => {
|
|
3390
|
+
if (event.target === el) this.cancelTableDialog();
|
|
3391
|
+
});
|
|
3392
|
+
el.addEventListener("keydown", (event) => {
|
|
3393
|
+
if (event.key !== "Tab") return;
|
|
3394
|
+
const focusable = [...el.querySelectorAll('button:not(:disabled),[tabindex]:not([tabindex="-1"])')];
|
|
3395
|
+
if (!focusable.length) return;
|
|
3396
|
+
const first = focusable[0];
|
|
3397
|
+
const last = focusable[focusable.length - 1];
|
|
3398
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
3399
|
+
event.preventDefault();
|
|
3400
|
+
last.focus();
|
|
3401
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
3402
|
+
event.preventDefault();
|
|
3403
|
+
first.focus();
|
|
3404
|
+
}
|
|
3405
|
+
});
|
|
3406
|
+
requestAnimationFrame(() => el.querySelector(variable ? '[data-table-step="-1"]' : ".sl-table-confirm")?.focus());
|
|
3407
|
+
}
|
|
3408
|
+
renderTableDialogState() {
|
|
3409
|
+
const table = this.tableDialog;
|
|
3410
|
+
const el = this.tableDialogEl;
|
|
3411
|
+
if (!table || !el) return;
|
|
3412
|
+
const output = el.querySelector("output[data-table-qty]");
|
|
3413
|
+
if (output) output.value = String(table.quantity);
|
|
3414
|
+
const hidden = el.querySelector("input[data-table-qty]");
|
|
3415
|
+
if (hidden) hidden.value = String(table.quantity);
|
|
3416
|
+
el.querySelectorAll("[data-table-step]").forEach((button) => {
|
|
3417
|
+
const delta = Number(button.dataset.tableStep);
|
|
3418
|
+
button.disabled = delta < 0 ? table.quantity <= table.minOccupancy : table.quantity >= table.maxOccupancy;
|
|
3419
|
+
});
|
|
3420
|
+
const unit = this.paidPrice(table.categoryKey, table.tierId ?? null, table.price);
|
|
3421
|
+
const total = el.querySelector("[data-table-total]");
|
|
3422
|
+
if (total) total.textContent = this.money(unit * table.quantity);
|
|
3423
|
+
}
|
|
3424
|
+
async confirmTableDialog() {
|
|
3425
|
+
const table = this.tableDialog;
|
|
3426
|
+
const held = this.tableDialogHeld;
|
|
3427
|
+
if (!table) return;
|
|
3428
|
+
const button = this.tableDialogEl?.querySelector(".sl-table-confirm");
|
|
3429
|
+
if (button) {
|
|
3430
|
+
button.disabled = true;
|
|
3431
|
+
button.textContent = held ? "Updating\u2026" : "Selecting\u2026";
|
|
3432
|
+
}
|
|
3433
|
+
if (held) {
|
|
3434
|
+
try {
|
|
3435
|
+
const updated = await this.controller.replaceTableQuantity(table.label, table.quantity);
|
|
3436
|
+
if (!updated) {
|
|
3437
|
+
this.toast("That guest count could not be secured. Your current table hold is unchanged.", "warning");
|
|
3438
|
+
this.renderTableDialogState();
|
|
3439
|
+
if (button) {
|
|
3440
|
+
button.disabled = false;
|
|
3441
|
+
button.textContent = "Update table";
|
|
3442
|
+
}
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
this.hold = {
|
|
3446
|
+
holdId: updated.holdId,
|
|
3447
|
+
expiresAt: updated.expiresAt,
|
|
3448
|
+
seats: updated.seats,
|
|
3449
|
+
items: updated.items
|
|
3450
|
+
};
|
|
3451
|
+
this.startHoldTimer(updated.expiresAt);
|
|
3452
|
+
this.dismissTableDialog();
|
|
3453
|
+
this.syncTray();
|
|
3454
|
+
this.emitHoldChange();
|
|
3455
|
+
this.toast(`${table.label} updated for ${table.quantity} guests.`, "success");
|
|
3456
|
+
} catch (error) {
|
|
3457
|
+
this.opts.onError?.(error);
|
|
3458
|
+
this.toast("That guest count is no longer available. Your current hold is unchanged.", "error");
|
|
3459
|
+
if (button) {
|
|
3460
|
+
button.disabled = false;
|
|
3461
|
+
button.textContent = "Update table";
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
if (!this.controller.setTableQuantity(table.label, table.quantity)) {
|
|
3467
|
+
if (button) {
|
|
3468
|
+
button.disabled = false;
|
|
3469
|
+
button.textContent = table.bookingMode === "variable" ? "Select table" : "Select whole table";
|
|
3470
|
+
}
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
this.dismissTableDialog();
|
|
3474
|
+
this.collapseSectionCard();
|
|
3475
|
+
this.syncTray();
|
|
3476
|
+
}
|
|
3477
|
+
cancelTableDialog() {
|
|
3478
|
+
const table = this.tableDialog;
|
|
3479
|
+
const held = this.tableDialogHeld;
|
|
3480
|
+
this.dismissTableDialog();
|
|
3481
|
+
if (table && !held) this.controller.deselect(table.physicalSeatIds);
|
|
3482
|
+
}
|
|
3483
|
+
dismissTableDialog(restoreFocus = true) {
|
|
3484
|
+
const focus = this.tableDialogReturnFocus;
|
|
3485
|
+
this.tableDialogEl?.remove();
|
|
3486
|
+
this.tableDialogEl = null;
|
|
3487
|
+
this.tableDialog = null;
|
|
3488
|
+
this.tableDialogHeld = false;
|
|
3489
|
+
this.tableDialogReturnFocus = null;
|
|
3490
|
+
if (restoreFocus) requestAnimationFrame(() => (focus?.isConnected ? focus : this.root)?.focus());
|
|
3210
3491
|
}
|
|
3211
3492
|
// ---- seat candidate confirmation ------------------------------------------
|
|
3212
3493
|
showConfirm(seat) {
|
|
@@ -3228,13 +3509,18 @@ var SeatPicker = class _SeatPicker {
|
|
|
3228
3509
|
">": ">",
|
|
3229
3510
|
'"': """
|
|
3230
3511
|
})[char]);
|
|
3512
|
+
const identityFields = [
|
|
3513
|
+
details?.sectionLabel ? `<div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details.sectionLabel)}</span></div>` : "",
|
|
3514
|
+
details?.rowLabel || details?.objectType === "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">${safe(this.rowTypeWord(details))}</span><span class="sl-confirm-value">${safe(this.rowShort(details) ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : "",
|
|
3515
|
+
details?.objectType !== "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">Seat</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : ""
|
|
3516
|
+
].filter(Boolean).join("");
|
|
3231
3517
|
const el = document.createElement("div");
|
|
3232
3518
|
el.className = "sl-confirm";
|
|
3233
3519
|
el.setAttribute("role", "dialog");
|
|
3234
3520
|
el.setAttribute("aria-modal", "true");
|
|
3235
3521
|
el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
|
|
3236
3522
|
el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
|
|
3237
|
-
el.innerHTML = `<div class="sl-confirm-grid"
|
|
3523
|
+
el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</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.wheelchairConfirmHtml(details?.wheelchairSpaceType) + 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>`;
|
|
3238
3524
|
this.els.map.appendChild(el);
|
|
3239
3525
|
this.confirmEl = el;
|
|
3240
3526
|
this.reanchorConfirm();
|
|
@@ -3305,7 +3591,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3305
3591
|
this.closeSeatView();
|
|
3306
3592
|
const doc = this.controller.doc;
|
|
3307
3593
|
const activeId = this.controller.getActiveFloorId();
|
|
3308
|
-
const focal = doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
3594
|
+
const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
3309
3595
|
let panoUrl;
|
|
3310
3596
|
let caption;
|
|
3311
3597
|
let real = false;
|
|
@@ -3529,16 +3815,37 @@ var SeatPicker = class _SeatPicker {
|
|
|
3529
3815
|
const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
|
|
3530
3816
|
if (!this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
|
|
3531
3817
|
const cats = this.controller.doc?.categories ?? [];
|
|
3818
|
+
const zones = this.controller.getBestAvailableZones();
|
|
3819
|
+
if (this.baZone && !zones.some((zone) => zone.id === this.baZone)) this.baZone = "";
|
|
3532
3820
|
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
|
|
3533
3821
|
// seats (same present-only philosophy as the a11y filter chips).
|
|
3534
|
-
(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>`);
|
|
3535
|
-
}
|
|
3536
|
-
const idGrid = (seatId, label) => {
|
|
3822
|
+
(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>`) + (zones.length ? `<select aria-label="Preferred venue zone" data-ba-zone><option value="">Any venue zone</option>` + zones.map((zone) => `<option value="${escapeOption(zone.id)}"${this.baZone === zone.id ? " selected" : ""}>${escapeOption(zone.label)}</option>`).join("") + `</select>` : "") + `<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>`);
|
|
3823
|
+
}
|
|
3824
|
+
const idGrid = (seatId, label, objectType, quantity = 1, objectId, identity) => {
|
|
3825
|
+
const esc2 = (value) => String(value ?? "\u2014").replace(/[&<>"]/g, (char) => ({
|
|
3826
|
+
"&": "&",
|
|
3827
|
+
"<": "<",
|
|
3828
|
+
">": ">",
|
|
3829
|
+
'"': """
|
|
3830
|
+
})[char]);
|
|
3537
3831
|
const d = seatId ? this.controller.seatDetails(seatId) : null;
|
|
3832
|
+
const area = objectType === "ga" ? gaAreas.find((candidate) => candidate.id === objectId) : void 0;
|
|
3833
|
+
const effectiveType = objectType === "ga" ? "ga" : d?.objectType ?? objectType;
|
|
3834
|
+
const typeWord = identity?.displayType?.trim() || d?.displayType?.trim() || area?.displayType?.trim() || (effectiveType === "table" ? "Table" : effectiveType === "booth" ? "Booth" : effectiveType === "ga" ? "General admission" : "Row");
|
|
3835
|
+
const buyerName = identity?.rowLabel ?? identity?.displayLabel ?? d?.rowLabel ?? d?.displayLabel ?? area?.displayLabel ?? area?.label ?? label;
|
|
3836
|
+
if (effectiveType === "table" && identity?.bookingMode) {
|
|
3837
|
+
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">${esc2(typeWord)}</span><span class="val">${esc2(buyerName)}</span></span><span class="fld mid"><span class="sl-chip-eb">Guests</span><span class="val">${quantity}</span></span></div>`;
|
|
3838
|
+
}
|
|
3839
|
+
if (effectiveType === "ga") {
|
|
3840
|
+
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">${esc2(typeWord)}</span><span class="val">${esc2(buyerName)}</span></span>` + (quantity > 1 ? `<span class="fld mid"><span class="sl-chip-eb">Tickets</span><span class="val">${quantity}</span></span>` : "") + `</div>`;
|
|
3841
|
+
}
|
|
3842
|
+
if (effectiveType === "booth") {
|
|
3843
|
+
return `<div class="sl-chip-id">` + (d?.sectionLabel ? `<span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${esc2(d.sectionLabel)}</span></span>` : "") + `<span class="fld mid"><span class="sl-chip-eb">${esc2(typeWord)}</span><span class="val">${esc2(buyerName)}</span></span></div>`;
|
|
3844
|
+
}
|
|
3538
3845
|
if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
|
|
3539
|
-
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Seat</span><span class="val">${
|
|
3846
|
+
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Seat</span><span class="val">${esc2(buyerName)}</span></span></div>`;
|
|
3540
3847
|
}
|
|
3541
|
-
return `<div class="sl-chip-id"
|
|
3848
|
+
return `<div class="sl-chip-id">` + (d.sectionLabel ? `<span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${esc2(d.sectionLabel)}</span></span>` : "") + (d.rowLabel ? `<span class="fld mid"><span class="sl-chip-eb">${esc2(typeWord)}</span><span class="val">${esc2(this.rowShort(d))}</span></span>` : "") + (d.seatNumber ? `<span class="fld mid"><span class="sl-chip-eb">Seat</span><span class="val">${esc2(d.seatNumber)}</span></span>` : "") + `</div>`;
|
|
3542
3849
|
};
|
|
3543
3850
|
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>`;
|
|
3544
3851
|
for (const item of heldItems) {
|
|
@@ -3547,9 +3854,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
3547
3854
|
const cat = this.controller.doc?.categories.find((c) => c.key === item.categoryKey);
|
|
3548
3855
|
const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
|
|
3549
3856
|
const heldSeat = item.objectType !== "ga" ? this.controller.seatByLabel(item.label) : null;
|
|
3550
|
-
const
|
|
3857
|
+
const table = item.objectType === "table" ? this.controller.tableSelection(item.label) : null;
|
|
3858
|
+
const canView2 = this.seatViewEnabled() && !!heldSeat && item.objectType !== "table";
|
|
3551
3859
|
parts.push(
|
|
3552
|
-
`<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>`
|
|
3860
|
+
`<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, item.objectType, item.quantity ?? 1, item.objectId, table ?? void 0) + `<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>` + (table?.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(item.label)}">${item.quantity ?? 1} guests \xB7 Edit</button>` : "") + this.wheelchairChipMarker(heldSeat?.wheelchairSpaceType) + 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>`
|
|
3553
3861
|
);
|
|
3554
3862
|
}
|
|
3555
3863
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
@@ -3560,13 +3868,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
3560
3868
|
const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
|
|
3561
3869
|
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>` : "";
|
|
3562
3870
|
parts.push(
|
|
3563
|
-
`<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.
|
|
3871
|
+
`<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, s.objectType, s.quantity ?? 1, s.objectId, s) + `<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>` + (s.objectType === "table" && s.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(s.label)}">${s.quantity ?? 1} guests \xB7 Edit</button>` : "") + `${this.wheelchairChipMarker(s.wheelchairSpaceType)}${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1))}</span></div></div>` + iconRail(`Remove ${s.label}`, canView && s.objectType !== "table" ? s.label : null) + `</div>`
|
|
3564
3872
|
);
|
|
3565
3873
|
}
|
|
3566
3874
|
for (const area of gaAreas) {
|
|
3567
3875
|
const qty = this.gaQty.get(area.id) ?? 0;
|
|
3568
3876
|
parts.push(
|
|
3569
|
-
`<div class="sl-ga" data-ga="${area.id}"><div class="sl-ga-info"><div class="sl-ga-name">${area.label}</div><div class="sl-ga-sub">${this.money(this.paidPrice(area.categoryKey, null, area.price))} \xB7 ${area.available} left</div></div><div class="sl-ga-qty"><button type="button" data-d="-1" aria-label="Fewer">\u2212</button><span>${qty}</span><button type="button" data-d="1" aria-label="More">+</button></div></div>`
|
|
3877
|
+
`<div class="sl-ga" data-ga="${area.id}"><div class="sl-ga-info"><div class="sl-ga-name">${area.displayLabel ?? area.label}</div><div class="sl-ga-sub">${area.displayType ?? "General admission"} \xB7 ${this.money(this.paidPrice(area.categoryKey, null, area.price))} \xB7 ${area.available} left</div></div><div class="sl-ga-qty"><button type="button" data-d="-1" aria-label="Fewer">\u2212</button><span>${qty}</span><button type="button" data-d="1" aria-label="More">+</button></div></div>`
|
|
3570
3878
|
);
|
|
3571
3879
|
}
|
|
3572
3880
|
this.els.tray.innerHTML = parts.join("");
|
|
@@ -3580,6 +3888,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
3580
3888
|
this.els.tray.querySelector("[data-ba-cat]")?.addEventListener("change", (e) => {
|
|
3581
3889
|
this.baCat = e.target.value;
|
|
3582
3890
|
});
|
|
3891
|
+
this.els.tray.querySelector("[data-ba-zone]")?.addEventListener("change", (e) => {
|
|
3892
|
+
this.baZone = e.target.value;
|
|
3893
|
+
});
|
|
3583
3894
|
this.els.tray.querySelector("[data-ba-premium]")?.addEventListener("click", () => {
|
|
3584
3895
|
this.baPremium = !this.baPremium;
|
|
3585
3896
|
this.syncTray();
|
|
@@ -3591,7 +3902,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3591
3902
|
this.els.tray.querySelector("[data-ba-replace]")?.focus();
|
|
3592
3903
|
return;
|
|
3593
3904
|
}
|
|
3594
|
-
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
|
|
3905
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium, zoneId: this.baZone || void 0 });
|
|
3595
3906
|
});
|
|
3596
3907
|
this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
|
|
3597
3908
|
this.bestAvailableConfirm = false;
|
|
@@ -3600,7 +3911,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3600
3911
|
});
|
|
3601
3912
|
this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
|
|
3602
3913
|
this.bestAvailableConfirm = false;
|
|
3603
|
-
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
|
|
3914
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium, zoneId: this.baZone || void 0 });
|
|
3604
3915
|
});
|
|
3605
3916
|
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
3606
3917
|
btn.addEventListener("click", () => {
|
|
@@ -3632,6 +3943,20 @@ var SeatPicker = class _SeatPicker {
|
|
|
3632
3943
|
this.scheduleMotion(remove, 150);
|
|
3633
3944
|
});
|
|
3634
3945
|
});
|
|
3946
|
+
this.els.tray.querySelectorAll("[data-table-edit]").forEach((button) => {
|
|
3947
|
+
button.addEventListener("click", (event) => {
|
|
3948
|
+
event.stopPropagation();
|
|
3949
|
+
const label = decodeURIComponent(button.dataset.tableEdit ?? "");
|
|
3950
|
+
const details = this.controller.tableSelection(label);
|
|
3951
|
+
if (!details) return;
|
|
3952
|
+
const heldItem = heldItems.find((item) => item.label === label && item.objectType === "table");
|
|
3953
|
+
this.showTableDialog(
|
|
3954
|
+
{ ...details, quantity: heldItem?.quantity ?? details.quantity },
|
|
3955
|
+
!!heldItem,
|
|
3956
|
+
button
|
|
3957
|
+
);
|
|
3958
|
+
});
|
|
3959
|
+
});
|
|
3635
3960
|
this.els.tray.querySelectorAll(".sl-chip .tier").forEach((sel) => {
|
|
3636
3961
|
sel.addEventListener("change", () => this.controller.setSeatTier(sel.dataset.tier, sel.value || null));
|
|
3637
3962
|
});
|
|
@@ -3659,7 +3984,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3659
3984
|
});
|
|
3660
3985
|
});
|
|
3661
3986
|
if (this.salesClosed) {
|
|
3662
|
-
this.els.tray.querySelectorAll(".sl-ba-go,[data-ba],[data-ba-cat],[data-ba-replace],.sl-ga button").forEach((el) => {
|
|
3987
|
+
this.els.tray.querySelectorAll(".sl-ba-go,[data-ba],[data-ba-cat],[data-ba-zone],[data-ba-replace],.sl-ga button").forEach((el) => {
|
|
3663
3988
|
el.disabled = true;
|
|
3664
3989
|
});
|
|
3665
3990
|
}
|
|
@@ -3668,8 +3993,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3668
3993
|
const heldTotal = heldItems.reduce((sum, item) => sum + this.paidPrice(item.categoryKey, item.tierId, item.unitPrice) * (item.quantity ?? 1), 0);
|
|
3669
3994
|
const heldCount = heldItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
|
|
3670
3995
|
const freshSeats = seats.filter((seat) => !heldLabels.has(seat.label));
|
|
3671
|
-
const total = freshSeats.reduce(
|
|
3672
|
-
|
|
3996
|
+
const total = freshSeats.reduce(
|
|
3997
|
+
(sum, s) => sum + this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1),
|
|
3998
|
+
0
|
|
3999
|
+
) + gaTotal + heldTotal;
|
|
4000
|
+
const count = freshSeats.reduce((sum, seat) => sum + (seat.quantity ?? 1), 0) + gaCount + heldCount;
|
|
3673
4001
|
const pendingCount = this.pendingSelectionCount();
|
|
3674
4002
|
const previousCount = this.lastTrayCount;
|
|
3675
4003
|
const previousTotal = this.lastTrayTotal;
|
|
@@ -3915,16 +4243,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
3915
4243
|
/** Assemble the stable {@link CheckoutHandoff} from a hold's server line items. */
|
|
3916
4244
|
buildHandoff(hold) {
|
|
3917
4245
|
const items = hold.items ?? [];
|
|
3918
|
-
const lineItems = items.map((it) =>
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
4246
|
+
const lineItems = items.map((it) => {
|
|
4247
|
+
const display = this.controller.lineItemDisplay(it);
|
|
4248
|
+
return {
|
|
4249
|
+
label: it.label,
|
|
4250
|
+
...display.displayLabel ? { displayLabel: display.displayLabel } : {},
|
|
4251
|
+
...display.displayType ? { displayType: display.displayType } : {},
|
|
4252
|
+
objectId: it.objectId,
|
|
4253
|
+
objectType: it.objectType,
|
|
4254
|
+
categoryKey: it.categoryKey,
|
|
4255
|
+
tierId: it.tierId,
|
|
4256
|
+
unitPrice: this.paidPrice(it.categoryKey, it.tierId, it.unitPrice),
|
|
4257
|
+
currency: it.currency ?? this.currency,
|
|
4258
|
+
quantity: it.quantity ?? 1
|
|
4259
|
+
};
|
|
4260
|
+
});
|
|
3928
4261
|
const currency = lineItems[0]?.currency ?? this.currency;
|
|
3929
4262
|
const total = lineItems.reduce((sum, i) => sum + i.unitPrice * i.quantity, 0);
|
|
3930
4263
|
return { holdId: hold.holdId, expiresAt: hold.expiresAt, currency, lineItems, total };
|
|
@@ -3985,8 +4318,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3985
4318
|
/** Buyer-facing type word for the row/table key label — the designer's
|
|
3986
4319
|
* per-object "Displayed type" override, or the default "Row". */
|
|
3987
4320
|
rowTypeWord(details) {
|
|
3988
|
-
const
|
|
3989
|
-
|
|
4321
|
+
const authored = details?.displayType?.trim() || details?.rowType?.trim();
|
|
4322
|
+
if (authored) return authored;
|
|
4323
|
+
if (details?.objectType === "table") return "Table";
|
|
4324
|
+
if (details?.objectType === "booth") return "Booth";
|
|
4325
|
+
return "Row";
|
|
3990
4326
|
}
|
|
3991
4327
|
rowShort(details) {
|
|
3992
4328
|
const row = details?.rowLabel;
|
|
@@ -4006,13 +4342,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
4006
4342
|
}
|
|
4007
4343
|
const esc2 = (v) => String(v ?? "\u2014").replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
4008
4344
|
const price = this.money(this.paidPrice(details.categoryKey, details.tierId ?? null, details.price));
|
|
4345
|
+
const isGroupedTable = details.objectType === "table" && !!details.bookingMode;
|
|
4346
|
+
const isBooth = details.objectType === "booth";
|
|
4009
4347
|
const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
|
|
4010
|
-
const grid =
|
|
4348
|
+
const grid = isGroupedTable ? `<div class="sl-tip-grid"><div class="sl-tip-field"><span class="sl-tip-key">${esc2(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc2(details.rowLabel ?? details.displayLabel ?? details.label)}</span></div><div class="sl-tip-field"><span class="sl-tip-key">Guests</span><span class="sl-tip-val">${details.bookingMode === "variable" ? `${details.minOccupancy}\u2013${details.maxOccupancy}` : details.capacity}</span></div></div>` : isBooth ? `<div class="sl-tip-grid">` + (details.sectionLabel ? `<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(details.rowLabel ?? details.displayLabel ?? details.label)}</span></div></div>` : hasLoc ? `<div class="sl-tip-grid">` + (details.sectionLabel ? `<div class="sl-tip-field"><span class="sl-tip-key">Section</span><span class="sl-tip-val">${esc2(details.sectionLabel)}</span></div>` : "") + (details.rowLabel ? `<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>` : "") + (details.seatNumber ? `<div class="sl-tip-field"><span class="sl-tip-key">Seat</span><span class="sl-tip-val">${esc2(details.seatNumber)}</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>`;
|
|
4011
4349
|
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>`;
|
|
4012
4350
|
const limited = this.limitedViewLabel(details.commercial);
|
|
4013
4351
|
const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc2(limited)}</div>` : "";
|
|
4352
|
+
const wheelchair = this.wheelchairProvisionLabel(details.wheelchairSpaceType);
|
|
4353
|
+
const wheelchairLine = wheelchair ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u267F</span>${esc2(wheelchair)}</div>` : "";
|
|
4014
4354
|
this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
|
|
4015
|
-
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;
|
|
4355
|
+
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>` + wheelchairLine + cxLine + statusLine;
|
|
4016
4356
|
this.tipEl.style.display = "block";
|
|
4017
4357
|
this.placeTooltip();
|
|
4018
4358
|
}
|
|
@@ -4020,6 +4360,33 @@ var SeatPicker = class _SeatPicker {
|
|
|
4020
4360
|
getSelection() {
|
|
4021
4361
|
return this.committedSelection();
|
|
4022
4362
|
}
|
|
4363
|
+
/** Current colorblind-safe render state, resolved from the stored buyer
|
|
4364
|
+
* preference at mount. Host chrome (e.g. the Designer preview) reads this to
|
|
4365
|
+
* surface the state rather than rendering colorblind colors silently. */
|
|
4366
|
+
isColorblindSafe() {
|
|
4367
|
+
return this.cbSafe;
|
|
4368
|
+
}
|
|
4369
|
+
/** Single source of truth for the colorblind-safe toggle — used by both the
|
|
4370
|
+
* in-widget button and host chrome. Updates the renderer, the persisted
|
|
4371
|
+
* cross-surface preference, and the in-widget button's pressed state together
|
|
4372
|
+
* so the two controls never diverge. */
|
|
4373
|
+
setColorblindSafe(on) {
|
|
4374
|
+
if (on === this.cbSafe) return;
|
|
4375
|
+
this.cbSafe = on;
|
|
4376
|
+
this.cbEl?.setAttribute("aria-pressed", String(on));
|
|
4377
|
+
this.controller.setColorblindSafe(on);
|
|
4378
|
+
writeStoredColorblind(on);
|
|
4379
|
+
}
|
|
4380
|
+
/** Switch the buyer canvas between flat, isometric and Perspective 2.5D. */
|
|
4381
|
+
setViewMode(mode) {
|
|
4382
|
+
this.controller.setViewMode(mode);
|
|
4383
|
+
this.syncProjection();
|
|
4384
|
+
this.dismissConfirm();
|
|
4385
|
+
}
|
|
4386
|
+
/** Current buyer canvas projection. */
|
|
4387
|
+
getViewMode() {
|
|
4388
|
+
return this.controller.getViewMode();
|
|
4389
|
+
}
|
|
4023
4390
|
/** Current active/restored hold reflected in the tray. */
|
|
4024
4391
|
getCurrentHold() {
|
|
4025
4392
|
return this.hold;
|
|
@@ -4109,6 +4476,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4109
4476
|
this.destroyed = true;
|
|
4110
4477
|
if (this.hold && !this.handedOff) void this.controller.release();
|
|
4111
4478
|
this.closeConfirm();
|
|
4479
|
+
this.dismissTableDialog(false);
|
|
4112
4480
|
this.closeSeatView();
|
|
4113
4481
|
this.stopHoldTimer();
|
|
4114
4482
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
@@ -4122,6 +4490,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4122
4490
|
if (this.fsChangeHandler) document.removeEventListener("fullscreenchange", this.fsChangeHandler);
|
|
4123
4491
|
if (this.fsEscHandler) window.removeEventListener("keydown", this.fsEscHandler);
|
|
4124
4492
|
this.controller.destroy();
|
|
4493
|
+
this.projectionEl = null;
|
|
4125
4494
|
this.root?.remove();
|
|
4126
4495
|
this.root = null;
|
|
4127
4496
|
if (this.modalScrim) {
|