@seatlayer/js 0.27.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.js
CHANGED
|
@@ -1035,6 +1035,7 @@ var EmbeddedDesigner = class {
|
|
|
1035
1035
|
// src/SeatPicker.ts
|
|
1036
1036
|
import {
|
|
1037
1037
|
PickerController as PickerController2,
|
|
1038
|
+
ACCESSIBILITY_TYPES,
|
|
1038
1039
|
expandChart,
|
|
1039
1040
|
generateSeatPanorama,
|
|
1040
1041
|
generateSeatThumb,
|
|
@@ -1065,6 +1066,15 @@ function resolveContainer3(container) {
|
|
|
1065
1066
|
}
|
|
1066
1067
|
return container;
|
|
1067
1068
|
}
|
|
1069
|
+
function escapeOption(value) {
|
|
1070
|
+
return String(value ?? "").replace(/[&<>"']/g, (character) => ({
|
|
1071
|
+
"&": "&",
|
|
1072
|
+
"<": "<",
|
|
1073
|
+
">": ">",
|
|
1074
|
+
'"': """,
|
|
1075
|
+
"'": "'"
|
|
1076
|
+
})[character]);
|
|
1077
|
+
}
|
|
1068
1078
|
var STYLE_ID = "seatlayer-picker-style";
|
|
1069
1079
|
var CSS = `
|
|
1070
1080
|
.sl-picker{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:420px;overflow:hidden;
|
|
@@ -1176,6 +1186,11 @@ var CSS = `
|
|
|
1176
1186
|
.sl-picker[data-layout="narrow"] .sl-filters.has{display:none}
|
|
1177
1187
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec.has,
|
|
1178
1188
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters.has{display:none}
|
|
1189
|
+
/* Accessibility and colour-safety controls must remain reachable on phones.
|
|
1190
|
+
Keep them out of the 86px peek, then reveal their consolidated row whenever
|
|
1191
|
+
the buyer explicitly opens the ticket panel. */
|
|
1192
|
+
.sl-picker[data-layout="narrow"][data-sheet="open"] .sl-filtersec.has{display:block!important}
|
|
1193
|
+
.sl-picker[data-layout="narrow"][data-sheet="open"] .sl-filters.has{display:flex!important}
|
|
1179
1194
|
.sl-cbbtn{width:32px;height:32px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
1180
1195
|
color:var(--sl-text);display:flex;align-items:center;justify-content:center;transition:border-color .15s}
|
|
1181
1196
|
.sl-cbbtn:hover{border-color:var(--sl-muted)}
|
|
@@ -1468,6 +1483,37 @@ var CSS = `
|
|
|
1468
1483
|
.sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
|
|
1469
1484
|
transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
1470
1485
|
|
|
1486
|
+
/* Atomic whole/variable-table chooser. Lives inside the widget so the same
|
|
1487
|
+
accessible dialog works in inline, modal, desktop and 390px mobile hosts. */
|
|
1488
|
+
.sl-table-scrim{position:absolute;inset:0;z-index:45;display:flex;align-items:center;justify-content:center;padding:18px;
|
|
1489
|
+
background:color-mix(in srgb,var(--sl-bg) 66%,transparent);backdrop-filter:blur(3px)}
|
|
1490
|
+
.sl-table-dialog{width:min(408px,100%);max-height:calc(100% - 24px);overflow:auto;border:1px solid var(--sl-line);
|
|
1491
|
+
border-radius:calc(var(--sl-radius) * 1.15);background:var(--sl-surface);box-shadow:0 28px 70px rgba(0,0,0,.42)}
|
|
1492
|
+
.sl-table-head{padding:18px 18px 14px;border-bottom:1px solid var(--sl-line)}
|
|
1493
|
+
.sl-table-eyebrow{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--sl-muted);font-weight:800}
|
|
1494
|
+
.sl-table-title{margin-top:5px;font-size:22px;line-height:1.15;font-weight:850}
|
|
1495
|
+
.sl-table-copy{margin-top:7px;color:var(--sl-muted);font-size:13px;line-height:1.45}
|
|
1496
|
+
.sl-table-body{padding:16px 18px 18px}
|
|
1497
|
+
.sl-table-summary{display:grid;grid-template-columns:1fr auto;gap:8px 16px;padding:12px;border:1px solid var(--sl-line);
|
|
1498
|
+
border-radius:var(--sl-r-sm);background:color-mix(in srgb,var(--sl-line) 22%,transparent);font-size:13px}
|
|
1499
|
+
.sl-table-summary b{font-size:15px}.sl-table-summary .muted{color:var(--sl-muted)}
|
|
1500
|
+
.sl-table-qtylabel{display:block;margin:16px 0 8px;font-size:12px;font-weight:800}
|
|
1501
|
+
.sl-table-stepper{display:grid;grid-template-columns:48px 1fr 48px;align-items:center;border:1px solid var(--sl-line);
|
|
1502
|
+
border-radius:12px;overflow:hidden;background:var(--sl-bg)}
|
|
1503
|
+
.sl-table-stepper button{height:48px;font-size:24px;font-weight:700;background:color-mix(in srgb,var(--sl-line) 34%,transparent)!important}
|
|
1504
|
+
.sl-table-stepper button:disabled{opacity:.38;cursor:not-allowed}
|
|
1505
|
+
.sl-table-stepper output{text-align:center;font-size:19px;font-weight:850;font-variant-numeric:tabular-nums}
|
|
1506
|
+
.sl-table-range{margin-top:7px;color:var(--sl-muted);font-size:11px;text-align:center}
|
|
1507
|
+
.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}
|
|
1508
|
+
.sl-table-cancel{border:1px solid var(--sl-line)!important;color:var(--sl-muted)!important}
|
|
1509
|
+
.sl-table-confirm{background:var(--sl-accent)!important;color:var(--sl-accent-ink)!important}
|
|
1510
|
+
.sl-table-confirm:disabled{opacity:.62;cursor:wait}
|
|
1511
|
+
.sl-table-edit{margin-left:4px;padding:2px 7px!important;border:1px solid var(--sl-line)!important;border-radius:999px!important;
|
|
1512
|
+
color:var(--sl-muted)!important;font-size:10px!important;font-weight:800!important}
|
|
1513
|
+
.sl-picker[data-layout="narrow"] .sl-table-scrim{align-items:flex-end;padding:0;background:rgba(5,7,12,.58)}
|
|
1514
|
+
.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}
|
|
1515
|
+
.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))}
|
|
1516
|
+
|
|
1471
1517
|
/* hover preview \u2014 a COMPACT echo of the confirm card (deliberately smaller: it's
|
|
1472
1518
|
a passing preview on hover, not the click/select action surface). Reuses the
|
|
1473
1519
|
Section\xB7Row\xB7Seat identity grid so hover, confirm and the cart chip all share
|
|
@@ -1531,6 +1577,7 @@ var CSS = `
|
|
|
1531
1577
|
.sl-picker[data-layout="narrow"] .sl-ba{padding:11px}
|
|
1532
1578
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
|
|
1533
1579
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
|
|
1580
|
+
.sl-picker[data-layout="narrow"] .sl-ba select{min-height:44px}
|
|
1534
1581
|
|
|
1535
1582
|
/* screen-reader live region */
|
|
1536
1583
|
.sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
@@ -1549,6 +1596,16 @@ var CSS = `
|
|
|
1549
1596
|
/* narrow: shrink the rung pills so the centered row can't reach the corner regions */
|
|
1550
1597
|
.sl-picker[data-layout="narrow"] .sl-rungs button{padding:5px 9px;font-size:9px;letter-spacing:.03em}
|
|
1551
1598
|
|
|
1599
|
+
/* Projection is deliberately a separate control from zoom/LOD. Perspective
|
|
1600
|
+
changes geometry; the two choices stay explicit and keyboard-native. */
|
|
1601
|
+
.sl-projection{display:inline-flex;align-items:center;gap:2px;padding:3px;border-radius:999px;
|
|
1602
|
+
background:var(--sl-surface);border:1px solid var(--sl-line);box-shadow:0 8px 24px -16px rgba(0,0,0,.65)}
|
|
1603
|
+
.sl-projection button{min-width:42px;min-height:30px;padding:5px 10px;border-radius:999px;color:var(--sl-muted);
|
|
1604
|
+
font-size:10px;font-weight:800;letter-spacing:.04em;white-space:nowrap}
|
|
1605
|
+
.sl-projection button:hover,.sl-projection button:focus-visible{color:var(--sl-text)}
|
|
1606
|
+
.sl-projection button.on{background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
1607
|
+
.sl-picker[data-layout="narrow"] .sl-projection button{min-width:38px;min-height:32px;padding:5px 8px;font-size:9.5px}
|
|
1608
|
+
|
|
1552
1609
|
/* multi-floor switcher (flows within the left-rail region) */
|
|
1553
1610
|
.sl-floors{display:none;flex-direction:column;gap:6px;max-width:100%}
|
|
1554
1611
|
.sl-floors.on{display:flex}
|
|
@@ -1677,6 +1734,7 @@ var CSS = `
|
|
|
1677
1734
|
.sl-picker *,.sl-modal-scrim *{animation-duration:.001ms!important;animation-iteration-count:1!important;
|
|
1678
1735
|
transition-duration:.001ms!important;scroll-behavior:auto!important}
|
|
1679
1736
|
}
|
|
1737
|
+
.sl-ba [data-ba-zone]{grid-column:1/-1;width:100%}
|
|
1680
1738
|
|
|
1681
1739
|
/* modal host */
|
|
1682
1740
|
.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}
|
|
@@ -1752,9 +1810,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
1752
1810
|
this.tipPos = { x: 0, y: 0 };
|
|
1753
1811
|
this.confirmEl = null;
|
|
1754
1812
|
this.confirmSeat = null;
|
|
1813
|
+
this.tableDialogEl = null;
|
|
1814
|
+
this.tableDialog = null;
|
|
1815
|
+
this.tableDialogHeld = false;
|
|
1816
|
+
this.tableDialogReturnFocus = null;
|
|
1755
1817
|
this.srEl = null;
|
|
1756
1818
|
this.baQty = 2;
|
|
1757
1819
|
this.baCat = "";
|
|
1820
|
+
/** Optional navigation-zone scope for buyer best-available. */
|
|
1821
|
+
this.baZone = "";
|
|
1758
1822
|
/** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
|
|
1759
1823
|
this.baPremium = false;
|
|
1760
1824
|
this.bestAvailableConfirm = false;
|
|
@@ -1768,6 +1832,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1768
1832
|
this.cbSafe = false;
|
|
1769
1833
|
// arena / multi-floor / seat-view chrome
|
|
1770
1834
|
this.rungsEl = null;
|
|
1835
|
+
this.projectionEl = null;
|
|
1771
1836
|
this.floorsEl = null;
|
|
1772
1837
|
this.secCardEl = null;
|
|
1773
1838
|
this.viewEl = null;
|
|
@@ -1863,11 +1928,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
1863
1928
|
this.toast(this.tf("picker.salesClosedToast", "Sales are closed for this event."), "warning");
|
|
1864
1929
|
return;
|
|
1865
1930
|
}
|
|
1931
|
+
if (this.controller.tableSelection(seat.id)) return;
|
|
1866
1932
|
this.flashPickedSeat(seat.id);
|
|
1867
1933
|
if (this.opts.confirmSelection) this.showConfirm(seat);
|
|
1868
1934
|
},
|
|
1935
|
+
onTableSelectionRequest: (table) => {
|
|
1936
|
+
if (this.salesClosed) {
|
|
1937
|
+
this.controller.deselect(table.physicalSeatIds);
|
|
1938
|
+
this.toast(this.tf("picker.salesClosedToast", "Sales are closed for this event."), "warning");
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
this.showTableDialog(table, false);
|
|
1942
|
+
},
|
|
1869
1943
|
onDeselect: (seat) => {
|
|
1870
1944
|
if (this.confirmSeat?.id === seat.id) this.dismissConfirm();
|
|
1945
|
+
if (this.tableDialog?.physicalSeatIds.includes(seat.id)) this.dismissTableDialog();
|
|
1871
1946
|
},
|
|
1872
1947
|
onSelectionLimit: () => {
|
|
1873
1948
|
this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
|
|
@@ -1875,6 +1950,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1875
1950
|
onViewChange: () => {
|
|
1876
1951
|
this.reanchorConfirm();
|
|
1877
1952
|
this.syncRung();
|
|
1953
|
+
this.syncProjection();
|
|
1878
1954
|
this.drawMinimapRect();
|
|
1879
1955
|
this.sectionCardOnView();
|
|
1880
1956
|
},
|
|
@@ -1891,27 +1967,47 @@ var SeatPicker = class _SeatPicker {
|
|
|
1891
1967
|
onError: (err) => this.opts.onError?.(err)
|
|
1892
1968
|
});
|
|
1893
1969
|
}
|
|
1970
|
+
/** True when the chart carries a real performance anchor — a stage-kind shape.
|
|
1971
|
+
* Every chart has a `focalPoint` (a bare look-at coordinate), so its presence
|
|
1972
|
+
* alone never justifies a "to stage" claim; only an actual stage does. */
|
|
1973
|
+
chartHasStage() {
|
|
1974
|
+
const doc = this.controller.doc;
|
|
1975
|
+
if (!doc) return false;
|
|
1976
|
+
const objectSets = doc.floors?.length ? doc.floors.map((f) => f.objects ?? []) : [doc.objects ?? []];
|
|
1977
|
+
for (const objects of objectSets) {
|
|
1978
|
+
for (const obj of objects) {
|
|
1979
|
+
if (obj.type === "shape" && (obj.role === "stage" || obj.stageKind)) return true;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1894
1984
|
/**
|
|
1895
|
-
* Eager sightline preview for the confirm card:
|
|
1896
|
-
*
|
|
1897
|
-
*
|
|
1985
|
+
* Eager sightline preview for the confirm card: the organizer's real view
|
|
1986
|
+
* photo, or — only when the chart has an actual stage to look at — a generated
|
|
1987
|
+
* forward view plus an "≈ Nm to stage · clear sightline" line. On a stageless
|
|
1988
|
+
* chart both the distance/sightline claim and the generic stage silhouette are
|
|
1989
|
+
* invented promises, so we suppress them; a real attached photo always shows.
|
|
1990
|
+
* (OV-52)
|
|
1898
1991
|
*/
|
|
1899
1992
|
confirmThumbHtml(seat) {
|
|
1900
1993
|
const doc = this.controller.doc;
|
|
1901
1994
|
if (!doc) return "";
|
|
1902
|
-
|
|
1995
|
+
const realPhoto = seat.viewUrl ?? "";
|
|
1996
|
+
const hasStage = this.chartHasStage();
|
|
1997
|
+
if (!realPhoto && !hasStage) return "";
|
|
1998
|
+
let url = realPhoto;
|
|
1903
1999
|
let distance = null;
|
|
1904
2000
|
if (!url) {
|
|
1905
2001
|
try {
|
|
1906
|
-
const thumb = generateSeatThumb(seat, doc.focalPoint);
|
|
2002
|
+
const thumb = generateSeatThumb(seat, seat.focalPoint ?? doc.focalPoint);
|
|
1907
2003
|
url = thumb.url;
|
|
1908
2004
|
distance = thumb.distanceM ?? null;
|
|
1909
2005
|
} catch {
|
|
1910
2006
|
return "";
|
|
1911
2007
|
}
|
|
1912
2008
|
}
|
|
1913
|
-
const sight =
|
|
1914
|
-
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
|
|
2009
|
+
const sightHtml = hasStage ? `<div class="sl-confirm-sight"><span aria-hidden="true">\u2713</span>${distance != null ? t2("picker.sightline", { m: distance }) : this.tf("picker.sightlineClear", "Clear sightline")}</div>` : "";
|
|
2010
|
+
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>` + sightHtml;
|
|
1915
2011
|
}
|
|
1916
2012
|
/** Minimal HTML/attribute escaper for buyer-authored commercial text (notes). */
|
|
1917
2013
|
escCx(value) {
|
|
@@ -1957,6 +2053,19 @@ var SeatPicker = class _SeatPicker {
|
|
|
1957
2053
|
const title = this.escCx(c?.note ? c.note : limited);
|
|
1958
2054
|
return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
|
|
1959
2055
|
}
|
|
2056
|
+
wheelchairProvisionLabel(type) {
|
|
2057
|
+
if (type === "no-seat") return "Empty wheelchair space";
|
|
2058
|
+
if (type === "seat-present") return "Accessible physical seat";
|
|
2059
|
+
return "";
|
|
2060
|
+
}
|
|
2061
|
+
wheelchairConfirmHtml(type) {
|
|
2062
|
+
const label = this.wheelchairProvisionLabel(type);
|
|
2063
|
+
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>` : "";
|
|
2064
|
+
}
|
|
2065
|
+
wheelchairChipMarker(type) {
|
|
2066
|
+
const label = this.wheelchairProvisionLabel(type);
|
|
2067
|
+
return label ? `<span class="sl-cx-mark" role="img" aria-label="${label}" title="${label}">\u267F</span>` : "";
|
|
2068
|
+
}
|
|
1960
2069
|
/** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
|
|
1961
2070
|
isFramed() {
|
|
1962
2071
|
return typeof window !== "undefined" && window.parent !== window;
|
|
@@ -2108,7 +2217,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
2108
2217
|
});
|
|
2109
2218
|
picker.escHandler = (e) => {
|
|
2110
2219
|
if (e.key !== "Escape") return;
|
|
2111
|
-
if (picker.
|
|
2220
|
+
if (picker.tableDialog) {
|
|
2221
|
+
e.preventDefault();
|
|
2222
|
+
picker.cancelTableDialog();
|
|
2223
|
+
} else if (picker.confirmSeat) {
|
|
2112
2224
|
e.preventDefault();
|
|
2113
2225
|
picker.cancelConfirm();
|
|
2114
2226
|
} else if (picker.bestAvailableConfirm) {
|
|
@@ -2139,7 +2251,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
2139
2251
|
mount.appendChild(root);
|
|
2140
2252
|
root.addEventListener("keydown", (e) => {
|
|
2141
2253
|
if (e.key !== "Escape") return;
|
|
2142
|
-
if (this.
|
|
2254
|
+
if (this.tableDialog) {
|
|
2255
|
+
e.preventDefault();
|
|
2256
|
+
e.stopPropagation();
|
|
2257
|
+
this.cancelTableDialog();
|
|
2258
|
+
} else if (this.confirmSeat) {
|
|
2143
2259
|
e.preventDefault();
|
|
2144
2260
|
e.stopPropagation();
|
|
2145
2261
|
this.cancelConfirm();
|
|
@@ -2308,6 +2424,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2308
2424
|
}
|
|
2309
2425
|
this.els.boot.remove();
|
|
2310
2426
|
this.salesClosed = !!info.salesClosed;
|
|
2427
|
+
this.controller.setViewMode(this.opts.initialView ?? "flat");
|
|
2311
2428
|
this.buildRegions();
|
|
2312
2429
|
this.regions["bottom-right"].appendChild(this.els.zoom);
|
|
2313
2430
|
this.regions["bottom-center"].appendChild(this.els.toast);
|
|
@@ -2350,11 +2467,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
2350
2467
|
this.regions["top-left"].appendChild(chips);
|
|
2351
2468
|
this.a11yChipsEl = chips;
|
|
2352
2469
|
if (present.size) {
|
|
2353
|
-
const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
|
|
2354
2470
|
const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
|
|
2355
2471
|
chips.insertAdjacentHTML(
|
|
2356
2472
|
"beforeend",
|
|
2357
|
-
mk("all", "All seats") +
|
|
2473
|
+
mk("all", "All seats") + ACCESSIBILITY_TYPES.filter(({ key }) => present.has(key)).map(({ key, short, icon }) => mk(key, `${icon} ${short}`)).join("")
|
|
2358
2474
|
);
|
|
2359
2475
|
const active = /* @__PURE__ */ new Set();
|
|
2360
2476
|
const syncChips = () => {
|
|
@@ -2404,10 +2520,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2404
2520
|
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>';
|
|
2405
2521
|
this.els.zfit.parentElement.appendChild(cb);
|
|
2406
2522
|
cb.addEventListener("click", () => {
|
|
2407
|
-
this.
|
|
2408
|
-
cb.setAttribute("aria-pressed", String(this.cbSafe));
|
|
2409
|
-
this.controller.setColorblindSafe(this.cbSafe);
|
|
2410
|
-
writeStoredColorblind(this.cbSafe);
|
|
2523
|
+
this.setColorblindSafe(!this.cbSafe);
|
|
2411
2524
|
});
|
|
2412
2525
|
this.srEl = document.createElement("div");
|
|
2413
2526
|
this.srEl.className = "sl-sr";
|
|
@@ -2529,6 +2642,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2529
2642
|
this.applySalesClosed();
|
|
2530
2643
|
}
|
|
2531
2644
|
applySalesClosed() {
|
|
2645
|
+
if (this.salesClosed && this.tableDialog && !this.tableDialogHeld) this.cancelTableDialog();
|
|
2532
2646
|
const pill = this.els.closedPill;
|
|
2533
2647
|
if (pill) {
|
|
2534
2648
|
pill.classList.toggle("on", this.salesClosed);
|
|
@@ -2617,12 +2731,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
2617
2731
|
/** Update only the action affordance; selection callbacks must not refire. */
|
|
2618
2732
|
committedSelection() {
|
|
2619
2733
|
const candidateId = this.confirmSeat?.id;
|
|
2620
|
-
|
|
2734
|
+
const pendingTableId = this.tableDialog && !this.tableDialogHeld ? this.tableDialog.id : null;
|
|
2735
|
+
return this.controller.getSelection().filter((seat) => seat.id !== candidateId && seat.id !== pendingTableId);
|
|
2621
2736
|
}
|
|
2622
2737
|
pendingSelectionCount() {
|
|
2623
2738
|
const heldItems = this.hold?.items ?? [];
|
|
2624
2739
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
2625
|
-
const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).
|
|
2740
|
+
const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).reduce((sum, seat) => sum + (seat.quantity ?? 1), 0);
|
|
2626
2741
|
return pendingSeats + this.pendingGACount();
|
|
2627
2742
|
}
|
|
2628
2743
|
heldGACounts() {
|
|
@@ -2644,13 +2759,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
2644
2759
|
}
|
|
2645
2760
|
totalTicketCount() {
|
|
2646
2761
|
const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
|
|
2647
|
-
const freshSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).
|
|
2762
|
+
const freshSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).reduce((sum, seat) => sum + (seat.quantity ?? 1), 0);
|
|
2648
2763
|
return this.heldTicketCount() + freshSeats + this.pendingGACount();
|
|
2649
2764
|
}
|
|
2650
2765
|
/** Held tickets and standing quantities consume the same order-wide cap. */
|
|
2651
2766
|
updateSelectionCapacity() {
|
|
2652
2767
|
const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
|
|
2653
|
-
const selectedHeld = this.committedSelection().filter((seat) => heldLabels.has(seat.label)).
|
|
2768
|
+
const selectedHeld = this.committedSelection().filter((seat) => heldLabels.has(seat.label)).reduce((sum, seat) => sum + (seat.quantity ?? 1), 0);
|
|
2654
2769
|
const remaining = Math.max(0, this.maxTickets - this.heldTicketCount() - this.pendingGACount());
|
|
2655
2770
|
this.controller.setMaxSelection(selectedHeld + remaining);
|
|
2656
2771
|
}
|
|
@@ -2677,9 +2792,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
2677
2792
|
cta.textContent = this.tf("picker.salesClosedCta", "Sales closed");
|
|
2678
2793
|
return;
|
|
2679
2794
|
}
|
|
2680
|
-
if (this.confirmSeat) {
|
|
2795
|
+
if (this.confirmSeat || this.tableDialog && !this.tableDialogHeld) {
|
|
2681
2796
|
cta.disabled = true;
|
|
2682
|
-
cta.textContent = "Confirm or cancel this seat";
|
|
2797
|
+
cta.textContent = this.tableDialog ? "Confirm your table" : "Confirm or cancel this seat";
|
|
2683
2798
|
return;
|
|
2684
2799
|
}
|
|
2685
2800
|
if (this.ctaPhase === "holding") {
|
|
@@ -2988,11 +3103,26 @@ var SeatPicker = class _SeatPicker {
|
|
|
2988
3103
|
});
|
|
2989
3104
|
}
|
|
2990
3105
|
// ---- arena / multi-floor chrome -------------------------------------------
|
|
2991
|
-
/** Build
|
|
3106
|
+
/** Build projection, rung and floor controls for arena-scale charts. */
|
|
2992
3107
|
buildArenaChrome() {
|
|
2993
3108
|
const doc = this.controller.doc;
|
|
2994
3109
|
if (!doc || !this.els.map) return;
|
|
2995
3110
|
const hasSections = doc.objects.some((o) => o.type === "section") || (doc.floors ?? []).some((f) => f.objects.some((o) => o.type === "section"));
|
|
3111
|
+
if (hasSections) {
|
|
3112
|
+
const projection = document.createElement("div");
|
|
3113
|
+
projection.className = "sl-projection";
|
|
3114
|
+
projection.setAttribute("role", "group");
|
|
3115
|
+
projection.setAttribute("aria-label", "Map projection");
|
|
3116
|
+
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>';
|
|
3117
|
+
projection.querySelectorAll("button").forEach((button) => {
|
|
3118
|
+
button.addEventListener("click", () => {
|
|
3119
|
+
this.setViewMode(button.dataset.projection);
|
|
3120
|
+
});
|
|
3121
|
+
});
|
|
3122
|
+
this.regions["top-right"].appendChild(projection);
|
|
3123
|
+
this.projectionEl = projection;
|
|
3124
|
+
this.syncProjection();
|
|
3125
|
+
}
|
|
2996
3126
|
if (hasSections) {
|
|
2997
3127
|
const RUNGS = ["zones", "sections", "seats"];
|
|
2998
3128
|
const pills = document.createElement("div");
|
|
@@ -3054,6 +3184,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
3054
3184
|
btn.setAttribute("aria-pressed", String(on));
|
|
3055
3185
|
});
|
|
3056
3186
|
}
|
|
3187
|
+
syncProjection() {
|
|
3188
|
+
if (!this.projectionEl) return;
|
|
3189
|
+
const active = this.controller.getViewMode();
|
|
3190
|
+
this.projectionEl.querySelectorAll("button").forEach((button) => {
|
|
3191
|
+
const on = button.dataset.projection === active;
|
|
3192
|
+
button.classList.toggle("on", on);
|
|
3193
|
+
button.setAttribute("aria-pressed", String(on));
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3057
3196
|
/** Reflect the active floor onto the switcher rail. */
|
|
3058
3197
|
syncFloors() {
|
|
3059
3198
|
if (!this.floorsEl) return;
|
|
@@ -3182,7 +3321,150 @@ var SeatPicker = class _SeatPicker {
|
|
|
3182
3321
|
const status = this.controller.getStatus(seat.id) ?? "free";
|
|
3183
3322
|
const statusText = status === "free" ? "available" : status === "held" ? "on hold" : "taken";
|
|
3184
3323
|
const price = cat ? this.catPrice(cat) : void 0;
|
|
3185
|
-
|
|
3324
|
+
const table = this.controller.tableSelection(seat.id);
|
|
3325
|
+
const details = table ?? this.controller.seatDetails(seat.id);
|
|
3326
|
+
const typeWord = this.rowTypeWord(details);
|
|
3327
|
+
const buyerName = details?.rowLabel ?? details?.displayLabel ?? seat.displayLabel ?? seat.label;
|
|
3328
|
+
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}`;
|
|
3329
|
+
this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
|
|
3330
|
+
}
|
|
3331
|
+
// ---- atomic table confirmation / guest quantity ---------------------------
|
|
3332
|
+
showTableDialog(table, held, returnFocus) {
|
|
3333
|
+
this.dismissTableDialog(false);
|
|
3334
|
+
this.tableDialog = { ...table };
|
|
3335
|
+
this.tableDialogHeld = held;
|
|
3336
|
+
this.tableDialogReturnFocus = returnFocus ?? document.activeElement;
|
|
3337
|
+
const esc2 = (value) => String(value ?? "").replace(/[&<>"']/g, (ch) => ({
|
|
3338
|
+
"&": "&",
|
|
3339
|
+
"<": "<",
|
|
3340
|
+
">": ">",
|
|
3341
|
+
'"': """,
|
|
3342
|
+
"'": "'"
|
|
3343
|
+
})[ch]);
|
|
3344
|
+
const cat = this.controller.doc?.categories.find((candidate) => candidate.key === table.categoryKey);
|
|
3345
|
+
const variable = table.bookingMode === "variable";
|
|
3346
|
+
const typeWord = this.rowTypeWord(table);
|
|
3347
|
+
const el = document.createElement("div");
|
|
3348
|
+
el.className = "sl-table-scrim";
|
|
3349
|
+
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>`;
|
|
3350
|
+
this.root.appendChild(el);
|
|
3351
|
+
this.tableDialogEl = el;
|
|
3352
|
+
this.renderTableDialogState();
|
|
3353
|
+
el.querySelectorAll("[data-table-step]").forEach((button) => {
|
|
3354
|
+
button.addEventListener("click", () => {
|
|
3355
|
+
if (!this.tableDialog) return;
|
|
3356
|
+
const next = Math.max(
|
|
3357
|
+
this.tableDialog.minOccupancy,
|
|
3358
|
+
Math.min(this.tableDialog.maxOccupancy, this.tableDialog.quantity + Number(button.dataset.tableStep))
|
|
3359
|
+
);
|
|
3360
|
+
this.tableDialog = { ...this.tableDialog, quantity: next };
|
|
3361
|
+
this.renderTableDialogState();
|
|
3362
|
+
});
|
|
3363
|
+
});
|
|
3364
|
+
el.querySelector(".sl-table-cancel").addEventListener("click", () => this.cancelTableDialog());
|
|
3365
|
+
el.querySelector(".sl-table-confirm").addEventListener("click", () => void this.confirmTableDialog());
|
|
3366
|
+
el.addEventListener("mousedown", (event) => {
|
|
3367
|
+
if (event.target === el) this.cancelTableDialog();
|
|
3368
|
+
});
|
|
3369
|
+
el.addEventListener("keydown", (event) => {
|
|
3370
|
+
if (event.key !== "Tab") return;
|
|
3371
|
+
const focusable = [...el.querySelectorAll('button:not(:disabled),[tabindex]:not([tabindex="-1"])')];
|
|
3372
|
+
if (!focusable.length) return;
|
|
3373
|
+
const first = focusable[0];
|
|
3374
|
+
const last = focusable[focusable.length - 1];
|
|
3375
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
3376
|
+
event.preventDefault();
|
|
3377
|
+
last.focus();
|
|
3378
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
3379
|
+
event.preventDefault();
|
|
3380
|
+
first.focus();
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
requestAnimationFrame(() => el.querySelector(variable ? '[data-table-step="-1"]' : ".sl-table-confirm")?.focus());
|
|
3384
|
+
}
|
|
3385
|
+
renderTableDialogState() {
|
|
3386
|
+
const table = this.tableDialog;
|
|
3387
|
+
const el = this.tableDialogEl;
|
|
3388
|
+
if (!table || !el) return;
|
|
3389
|
+
const output = el.querySelector("output[data-table-qty]");
|
|
3390
|
+
if (output) output.value = String(table.quantity);
|
|
3391
|
+
const hidden = el.querySelector("input[data-table-qty]");
|
|
3392
|
+
if (hidden) hidden.value = String(table.quantity);
|
|
3393
|
+
el.querySelectorAll("[data-table-step]").forEach((button) => {
|
|
3394
|
+
const delta = Number(button.dataset.tableStep);
|
|
3395
|
+
button.disabled = delta < 0 ? table.quantity <= table.minOccupancy : table.quantity >= table.maxOccupancy;
|
|
3396
|
+
});
|
|
3397
|
+
const unit = this.paidPrice(table.categoryKey, table.tierId ?? null, table.price);
|
|
3398
|
+
const total = el.querySelector("[data-table-total]");
|
|
3399
|
+
if (total) total.textContent = this.money(unit * table.quantity);
|
|
3400
|
+
}
|
|
3401
|
+
async confirmTableDialog() {
|
|
3402
|
+
const table = this.tableDialog;
|
|
3403
|
+
const held = this.tableDialogHeld;
|
|
3404
|
+
if (!table) return;
|
|
3405
|
+
const button = this.tableDialogEl?.querySelector(".sl-table-confirm");
|
|
3406
|
+
if (button) {
|
|
3407
|
+
button.disabled = true;
|
|
3408
|
+
button.textContent = held ? "Updating\u2026" : "Selecting\u2026";
|
|
3409
|
+
}
|
|
3410
|
+
if (held) {
|
|
3411
|
+
try {
|
|
3412
|
+
const updated = await this.controller.replaceTableQuantity(table.label, table.quantity);
|
|
3413
|
+
if (!updated) {
|
|
3414
|
+
this.toast("That guest count could not be secured. Your current table hold is unchanged.", "warning");
|
|
3415
|
+
this.renderTableDialogState();
|
|
3416
|
+
if (button) {
|
|
3417
|
+
button.disabled = false;
|
|
3418
|
+
button.textContent = "Update table";
|
|
3419
|
+
}
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
this.hold = {
|
|
3423
|
+
holdId: updated.holdId,
|
|
3424
|
+
expiresAt: updated.expiresAt,
|
|
3425
|
+
seats: updated.seats,
|
|
3426
|
+
items: updated.items
|
|
3427
|
+
};
|
|
3428
|
+
this.startHoldTimer(updated.expiresAt);
|
|
3429
|
+
this.dismissTableDialog();
|
|
3430
|
+
this.syncTray();
|
|
3431
|
+
this.emitHoldChange();
|
|
3432
|
+
this.toast(`${table.label} updated for ${table.quantity} guests.`, "success");
|
|
3433
|
+
} catch (error) {
|
|
3434
|
+
this.opts.onError?.(error);
|
|
3435
|
+
this.toast("That guest count is no longer available. Your current hold is unchanged.", "error");
|
|
3436
|
+
if (button) {
|
|
3437
|
+
button.disabled = false;
|
|
3438
|
+
button.textContent = "Update table";
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
if (!this.controller.setTableQuantity(table.label, table.quantity)) {
|
|
3444
|
+
if (button) {
|
|
3445
|
+
button.disabled = false;
|
|
3446
|
+
button.textContent = table.bookingMode === "variable" ? "Select table" : "Select whole table";
|
|
3447
|
+
}
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
this.dismissTableDialog();
|
|
3451
|
+
this.collapseSectionCard();
|
|
3452
|
+
this.syncTray();
|
|
3453
|
+
}
|
|
3454
|
+
cancelTableDialog() {
|
|
3455
|
+
const table = this.tableDialog;
|
|
3456
|
+
const held = this.tableDialogHeld;
|
|
3457
|
+
this.dismissTableDialog();
|
|
3458
|
+
if (table && !held) this.controller.deselect(table.physicalSeatIds);
|
|
3459
|
+
}
|
|
3460
|
+
dismissTableDialog(restoreFocus = true) {
|
|
3461
|
+
const focus = this.tableDialogReturnFocus;
|
|
3462
|
+
this.tableDialogEl?.remove();
|
|
3463
|
+
this.tableDialogEl = null;
|
|
3464
|
+
this.tableDialog = null;
|
|
3465
|
+
this.tableDialogHeld = false;
|
|
3466
|
+
this.tableDialogReturnFocus = null;
|
|
3467
|
+
if (restoreFocus) requestAnimationFrame(() => (focus?.isConnected ? focus : this.root)?.focus());
|
|
3186
3468
|
}
|
|
3187
3469
|
// ---- seat candidate confirmation ------------------------------------------
|
|
3188
3470
|
showConfirm(seat) {
|
|
@@ -3204,13 +3486,18 @@ var SeatPicker = class _SeatPicker {
|
|
|
3204
3486
|
">": ">",
|
|
3205
3487
|
'"': """
|
|
3206
3488
|
})[char]);
|
|
3489
|
+
const identityFields = [
|
|
3490
|
+
details?.sectionLabel ? `<div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details.sectionLabel)}</span></div>` : "",
|
|
3491
|
+
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>` : "",
|
|
3492
|
+
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>` : ""
|
|
3493
|
+
].filter(Boolean).join("");
|
|
3207
3494
|
const el = document.createElement("div");
|
|
3208
3495
|
el.className = "sl-confirm";
|
|
3209
3496
|
el.setAttribute("role", "dialog");
|
|
3210
3497
|
el.setAttribute("aria-modal", "true");
|
|
3211
3498
|
el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
|
|
3212
3499
|
el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
|
|
3213
|
-
el.innerHTML = `<div class="sl-confirm-grid"
|
|
3500
|
+
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>`;
|
|
3214
3501
|
this.els.map.appendChild(el);
|
|
3215
3502
|
this.confirmEl = el;
|
|
3216
3503
|
this.reanchorConfirm();
|
|
@@ -3281,7 +3568,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3281
3568
|
this.closeSeatView();
|
|
3282
3569
|
const doc = this.controller.doc;
|
|
3283
3570
|
const activeId = this.controller.getActiveFloorId();
|
|
3284
|
-
const focal = doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
3571
|
+
const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
3285
3572
|
let panoUrl;
|
|
3286
3573
|
let caption;
|
|
3287
3574
|
let real = false;
|
|
@@ -3505,16 +3792,37 @@ var SeatPicker = class _SeatPicker {
|
|
|
3505
3792
|
const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
|
|
3506
3793
|
if (!this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
|
|
3507
3794
|
const cats = this.controller.doc?.categories ?? [];
|
|
3795
|
+
const zones = this.controller.getBestAvailableZones();
|
|
3796
|
+
if (this.baZone && !zones.some((zone) => zone.id === this.baZone)) this.baZone = "";
|
|
3508
3797
|
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
|
|
3509
3798
|
// seats (same present-only philosophy as the a11y filter chips).
|
|
3510
|
-
(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>`);
|
|
3511
|
-
}
|
|
3512
|
-
const idGrid = (seatId, label) => {
|
|
3799
|
+
(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>`);
|
|
3800
|
+
}
|
|
3801
|
+
const idGrid = (seatId, label, objectType, quantity = 1, objectId, identity) => {
|
|
3802
|
+
const esc2 = (value) => String(value ?? "\u2014").replace(/[&<>"]/g, (char) => ({
|
|
3803
|
+
"&": "&",
|
|
3804
|
+
"<": "<",
|
|
3805
|
+
">": ">",
|
|
3806
|
+
'"': """
|
|
3807
|
+
})[char]);
|
|
3513
3808
|
const d = seatId ? this.controller.seatDetails(seatId) : null;
|
|
3809
|
+
const area = objectType === "ga" ? gaAreas.find((candidate) => candidate.id === objectId) : void 0;
|
|
3810
|
+
const effectiveType = objectType === "ga" ? "ga" : d?.objectType ?? objectType;
|
|
3811
|
+
const typeWord = identity?.displayType?.trim() || d?.displayType?.trim() || area?.displayType?.trim() || (effectiveType === "table" ? "Table" : effectiveType === "booth" ? "Booth" : effectiveType === "ga" ? "General admission" : "Row");
|
|
3812
|
+
const buyerName = identity?.rowLabel ?? identity?.displayLabel ?? d?.rowLabel ?? d?.displayLabel ?? area?.displayLabel ?? area?.label ?? label;
|
|
3813
|
+
if (effectiveType === "table" && identity?.bookingMode) {
|
|
3814
|
+
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>`;
|
|
3815
|
+
}
|
|
3816
|
+
if (effectiveType === "ga") {
|
|
3817
|
+
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>`;
|
|
3818
|
+
}
|
|
3819
|
+
if (effectiveType === "booth") {
|
|
3820
|
+
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>`;
|
|
3821
|
+
}
|
|
3514
3822
|
if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
|
|
3515
|
-
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Seat</span><span class="val">${
|
|
3823
|
+
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>`;
|
|
3516
3824
|
}
|
|
3517
|
-
return `<div class="sl-chip-id"
|
|
3825
|
+
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>`;
|
|
3518
3826
|
};
|
|
3519
3827
|
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>`;
|
|
3520
3828
|
for (const item of heldItems) {
|
|
@@ -3523,9 +3831,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
3523
3831
|
const cat = this.controller.doc?.categories.find((c) => c.key === item.categoryKey);
|
|
3524
3832
|
const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
|
|
3525
3833
|
const heldSeat = item.objectType !== "ga" ? this.controller.seatByLabel(item.label) : null;
|
|
3526
|
-
const
|
|
3834
|
+
const table = item.objectType === "table" ? this.controller.tableSelection(item.label) : null;
|
|
3835
|
+
const canView2 = this.seatViewEnabled() && !!heldSeat && item.objectType !== "table";
|
|
3527
3836
|
parts.push(
|
|
3528
|
-
`<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>`
|
|
3837
|
+
`<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>`
|
|
3529
3838
|
);
|
|
3530
3839
|
}
|
|
3531
3840
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
@@ -3536,13 +3845,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
3536
3845
|
const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
|
|
3537
3846
|
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>` : "";
|
|
3538
3847
|
parts.push(
|
|
3539
|
-
`<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.
|
|
3848
|
+
`<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>`
|
|
3540
3849
|
);
|
|
3541
3850
|
}
|
|
3542
3851
|
for (const area of gaAreas) {
|
|
3543
3852
|
const qty = this.gaQty.get(area.id) ?? 0;
|
|
3544
3853
|
parts.push(
|
|
3545
|
-
`<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>`
|
|
3854
|
+
`<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>`
|
|
3546
3855
|
);
|
|
3547
3856
|
}
|
|
3548
3857
|
this.els.tray.innerHTML = parts.join("");
|
|
@@ -3556,6 +3865,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
3556
3865
|
this.els.tray.querySelector("[data-ba-cat]")?.addEventListener("change", (e) => {
|
|
3557
3866
|
this.baCat = e.target.value;
|
|
3558
3867
|
});
|
|
3868
|
+
this.els.tray.querySelector("[data-ba-zone]")?.addEventListener("change", (e) => {
|
|
3869
|
+
this.baZone = e.target.value;
|
|
3870
|
+
});
|
|
3559
3871
|
this.els.tray.querySelector("[data-ba-premium]")?.addEventListener("click", () => {
|
|
3560
3872
|
this.baPremium = !this.baPremium;
|
|
3561
3873
|
this.syncTray();
|
|
@@ -3567,7 +3879,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3567
3879
|
this.els.tray.querySelector("[data-ba-replace]")?.focus();
|
|
3568
3880
|
return;
|
|
3569
3881
|
}
|
|
3570
|
-
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
|
|
3882
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium, zoneId: this.baZone || void 0 });
|
|
3571
3883
|
});
|
|
3572
3884
|
this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
|
|
3573
3885
|
this.bestAvailableConfirm = false;
|
|
@@ -3576,7 +3888,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3576
3888
|
});
|
|
3577
3889
|
this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
|
|
3578
3890
|
this.bestAvailableConfirm = false;
|
|
3579
|
-
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
|
|
3891
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium, zoneId: this.baZone || void 0 });
|
|
3580
3892
|
});
|
|
3581
3893
|
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
3582
3894
|
btn.addEventListener("click", () => {
|
|
@@ -3608,6 +3920,20 @@ var SeatPicker = class _SeatPicker {
|
|
|
3608
3920
|
this.scheduleMotion(remove, 150);
|
|
3609
3921
|
});
|
|
3610
3922
|
});
|
|
3923
|
+
this.els.tray.querySelectorAll("[data-table-edit]").forEach((button) => {
|
|
3924
|
+
button.addEventListener("click", (event) => {
|
|
3925
|
+
event.stopPropagation();
|
|
3926
|
+
const label = decodeURIComponent(button.dataset.tableEdit ?? "");
|
|
3927
|
+
const details = this.controller.tableSelection(label);
|
|
3928
|
+
if (!details) return;
|
|
3929
|
+
const heldItem = heldItems.find((item) => item.label === label && item.objectType === "table");
|
|
3930
|
+
this.showTableDialog(
|
|
3931
|
+
{ ...details, quantity: heldItem?.quantity ?? details.quantity },
|
|
3932
|
+
!!heldItem,
|
|
3933
|
+
button
|
|
3934
|
+
);
|
|
3935
|
+
});
|
|
3936
|
+
});
|
|
3611
3937
|
this.els.tray.querySelectorAll(".sl-chip .tier").forEach((sel) => {
|
|
3612
3938
|
sel.addEventListener("change", () => this.controller.setSeatTier(sel.dataset.tier, sel.value || null));
|
|
3613
3939
|
});
|
|
@@ -3635,7 +3961,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3635
3961
|
});
|
|
3636
3962
|
});
|
|
3637
3963
|
if (this.salesClosed) {
|
|
3638
|
-
this.els.tray.querySelectorAll(".sl-ba-go,[data-ba],[data-ba-cat],[data-ba-replace],.sl-ga button").forEach((el) => {
|
|
3964
|
+
this.els.tray.querySelectorAll(".sl-ba-go,[data-ba],[data-ba-cat],[data-ba-zone],[data-ba-replace],.sl-ga button").forEach((el) => {
|
|
3639
3965
|
el.disabled = true;
|
|
3640
3966
|
});
|
|
3641
3967
|
}
|
|
@@ -3644,8 +3970,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3644
3970
|
const heldTotal = heldItems.reduce((sum, item) => sum + this.paidPrice(item.categoryKey, item.tierId, item.unitPrice) * (item.quantity ?? 1), 0);
|
|
3645
3971
|
const heldCount = heldItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
|
|
3646
3972
|
const freshSeats = seats.filter((seat) => !heldLabels.has(seat.label));
|
|
3647
|
-
const total = freshSeats.reduce(
|
|
3648
|
-
|
|
3973
|
+
const total = freshSeats.reduce(
|
|
3974
|
+
(sum, s) => sum + this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1),
|
|
3975
|
+
0
|
|
3976
|
+
) + gaTotal + heldTotal;
|
|
3977
|
+
const count = freshSeats.reduce((sum, seat) => sum + (seat.quantity ?? 1), 0) + gaCount + heldCount;
|
|
3649
3978
|
const pendingCount = this.pendingSelectionCount();
|
|
3650
3979
|
const previousCount = this.lastTrayCount;
|
|
3651
3980
|
const previousTotal = this.lastTrayTotal;
|
|
@@ -3891,16 +4220,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
3891
4220
|
/** Assemble the stable {@link CheckoutHandoff} from a hold's server line items. */
|
|
3892
4221
|
buildHandoff(hold) {
|
|
3893
4222
|
const items = hold.items ?? [];
|
|
3894
|
-
const lineItems = items.map((it) =>
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
4223
|
+
const lineItems = items.map((it) => {
|
|
4224
|
+
const display = this.controller.lineItemDisplay(it);
|
|
4225
|
+
return {
|
|
4226
|
+
label: it.label,
|
|
4227
|
+
...display.displayLabel ? { displayLabel: display.displayLabel } : {},
|
|
4228
|
+
...display.displayType ? { displayType: display.displayType } : {},
|
|
4229
|
+
objectId: it.objectId,
|
|
4230
|
+
objectType: it.objectType,
|
|
4231
|
+
categoryKey: it.categoryKey,
|
|
4232
|
+
tierId: it.tierId,
|
|
4233
|
+
unitPrice: this.paidPrice(it.categoryKey, it.tierId, it.unitPrice),
|
|
4234
|
+
currency: it.currency ?? this.currency,
|
|
4235
|
+
quantity: it.quantity ?? 1
|
|
4236
|
+
};
|
|
4237
|
+
});
|
|
3904
4238
|
const currency = lineItems[0]?.currency ?? this.currency;
|
|
3905
4239
|
const total = lineItems.reduce((sum, i) => sum + i.unitPrice * i.quantity, 0);
|
|
3906
4240
|
return { holdId: hold.holdId, expiresAt: hold.expiresAt, currency, lineItems, total };
|
|
@@ -3961,8 +4295,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3961
4295
|
/** Buyer-facing type word for the row/table key label — the designer's
|
|
3962
4296
|
* per-object "Displayed type" override, or the default "Row". */
|
|
3963
4297
|
rowTypeWord(details) {
|
|
3964
|
-
const
|
|
3965
|
-
|
|
4298
|
+
const authored = details?.displayType?.trim() || details?.rowType?.trim();
|
|
4299
|
+
if (authored) return authored;
|
|
4300
|
+
if (details?.objectType === "table") return "Table";
|
|
4301
|
+
if (details?.objectType === "booth") return "Booth";
|
|
4302
|
+
return "Row";
|
|
3966
4303
|
}
|
|
3967
4304
|
rowShort(details) {
|
|
3968
4305
|
const row = details?.rowLabel;
|
|
@@ -3982,13 +4319,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
3982
4319
|
}
|
|
3983
4320
|
const esc2 = (v) => String(v ?? "\u2014").replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
3984
4321
|
const price = this.money(this.paidPrice(details.categoryKey, details.tierId ?? null, details.price));
|
|
4322
|
+
const isGroupedTable = details.objectType === "table" && !!details.bookingMode;
|
|
4323
|
+
const isBooth = details.objectType === "booth";
|
|
3985
4324
|
const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
|
|
3986
|
-
const grid =
|
|
4325
|
+
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>`;
|
|
3987
4326
|
const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${details.status === "held" ? t2("map.statusHeld") : t2("map.statusTaken")}</div>`;
|
|
3988
4327
|
const limited = this.limitedViewLabel(details.commercial);
|
|
3989
4328
|
const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc2(limited)}</div>` : "";
|
|
4329
|
+
const wheelchair = this.wheelchairProvisionLabel(details.wheelchairSpaceType);
|
|
4330
|
+
const wheelchairLine = wheelchair ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u267F</span>${esc2(wheelchair)}</div>` : "";
|
|
3990
4331
|
this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
|
|
3991
|
-
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;
|
|
4332
|
+
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;
|
|
3992
4333
|
this.tipEl.style.display = "block";
|
|
3993
4334
|
this.placeTooltip();
|
|
3994
4335
|
}
|
|
@@ -3996,6 +4337,33 @@ var SeatPicker = class _SeatPicker {
|
|
|
3996
4337
|
getSelection() {
|
|
3997
4338
|
return this.committedSelection();
|
|
3998
4339
|
}
|
|
4340
|
+
/** Current colorblind-safe render state, resolved from the stored buyer
|
|
4341
|
+
* preference at mount. Host chrome (e.g. the Designer preview) reads this to
|
|
4342
|
+
* surface the state rather than rendering colorblind colors silently. */
|
|
4343
|
+
isColorblindSafe() {
|
|
4344
|
+
return this.cbSafe;
|
|
4345
|
+
}
|
|
4346
|
+
/** Single source of truth for the colorblind-safe toggle — used by both the
|
|
4347
|
+
* in-widget button and host chrome. Updates the renderer, the persisted
|
|
4348
|
+
* cross-surface preference, and the in-widget button's pressed state together
|
|
4349
|
+
* so the two controls never diverge. */
|
|
4350
|
+
setColorblindSafe(on) {
|
|
4351
|
+
if (on === this.cbSafe) return;
|
|
4352
|
+
this.cbSafe = on;
|
|
4353
|
+
this.cbEl?.setAttribute("aria-pressed", String(on));
|
|
4354
|
+
this.controller.setColorblindSafe(on);
|
|
4355
|
+
writeStoredColorblind(on);
|
|
4356
|
+
}
|
|
4357
|
+
/** Switch the buyer canvas between flat, isometric and Perspective 2.5D. */
|
|
4358
|
+
setViewMode(mode) {
|
|
4359
|
+
this.controller.setViewMode(mode);
|
|
4360
|
+
this.syncProjection();
|
|
4361
|
+
this.dismissConfirm();
|
|
4362
|
+
}
|
|
4363
|
+
/** Current buyer canvas projection. */
|
|
4364
|
+
getViewMode() {
|
|
4365
|
+
return this.controller.getViewMode();
|
|
4366
|
+
}
|
|
3999
4367
|
/** Current active/restored hold reflected in the tray. */
|
|
4000
4368
|
getCurrentHold() {
|
|
4001
4369
|
return this.hold;
|
|
@@ -4085,6 +4453,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4085
4453
|
this.destroyed = true;
|
|
4086
4454
|
if (this.hold && !this.handedOff) void this.controller.release();
|
|
4087
4455
|
this.closeConfirm();
|
|
4456
|
+
this.dismissTableDialog(false);
|
|
4088
4457
|
this.closeSeatView();
|
|
4089
4458
|
this.stopHoldTimer();
|
|
4090
4459
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
@@ -4098,6 +4467,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4098
4467
|
if (this.fsChangeHandler) document.removeEventListener("fullscreenchange", this.fsChangeHandler);
|
|
4099
4468
|
if (this.fsEscHandler) window.removeEventListener("keydown", this.fsEscHandler);
|
|
4100
4469
|
this.controller.destroy();
|
|
4470
|
+
this.projectionEl = null;
|
|
4101
4471
|
this.root?.remove();
|
|
4102
4472
|
this.root = null;
|
|
4103
4473
|
if (this.modalScrim) {
|