@seatlayer/js 0.24.0 → 0.26.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 +228 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -4
- package/dist/index.d.ts +66 -4
- package/dist/index.js +228 -47
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -25,7 +25,13 @@ async function request(base, path, init = {}) {
|
|
|
25
25
|
const data = isJson ? await res.json().catch(() => null) : null;
|
|
26
26
|
if (!res.ok) {
|
|
27
27
|
const err = data;
|
|
28
|
-
throw new ApiError(
|
|
28
|
+
throw new ApiError(
|
|
29
|
+
res.status,
|
|
30
|
+
err?.error ?? `request_failed_${res.status}`,
|
|
31
|
+
err?.code ?? err?.error,
|
|
32
|
+
err?.conflicts,
|
|
33
|
+
err?.reason
|
|
34
|
+
);
|
|
29
35
|
}
|
|
30
36
|
return data;
|
|
31
37
|
}
|
|
@@ -98,6 +104,7 @@ var SeatingChart = class {
|
|
|
98
104
|
this.mount = null;
|
|
99
105
|
this.hostEl = null;
|
|
100
106
|
this.rendered = false;
|
|
107
|
+
this.mode_ = null;
|
|
101
108
|
this.tipEl = null;
|
|
102
109
|
this.tipPos = { x: 0, y: 0 };
|
|
103
110
|
this.onTipMove = null;
|
|
@@ -151,6 +158,7 @@ var SeatingChart = class {
|
|
|
151
158
|
this.rendered = false;
|
|
152
159
|
return this;
|
|
153
160
|
}
|
|
161
|
+
this.mode_ = info.mode === "test" ? "test" : "live";
|
|
154
162
|
if (this.opts.seatTooltip !== false) {
|
|
155
163
|
const tip = document.createElement("div");
|
|
156
164
|
tip.setAttribute("role", "tooltip");
|
|
@@ -224,6 +232,19 @@ var SeatingChart = class {
|
|
|
224
232
|
this.tipEl.style.display = "block";
|
|
225
233
|
this.placeTooltip();
|
|
226
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Whether the SERVED event is a live or a test event (`sk_test_` keys create
|
|
237
|
+
* test events, which never book real inventory). `null` before render()
|
|
238
|
+
* resolves — the mode comes from the server with the chart, not from options.
|
|
239
|
+
*
|
|
240
|
+
* The widget already surfaces this visually with the test-mode ribbon; this
|
|
241
|
+
* getter is for hosts that draw their own chrome — notably a native WebView
|
|
242
|
+
* wrapper, which must be able to tell an integrator that the build they are
|
|
243
|
+
* about to ship is pointed at a test event.
|
|
244
|
+
*/
|
|
245
|
+
getMode() {
|
|
246
|
+
return this.mode_;
|
|
247
|
+
}
|
|
227
248
|
/** Current selection with prices resolved from the chart categories. */
|
|
228
249
|
getSelection() {
|
|
229
250
|
return this.controller.getSelection();
|
|
@@ -231,17 +252,49 @@ var SeatingChart = class {
|
|
|
231
252
|
/** Hold the current selection. Resolves the hold, or null on a 409 conflict. */
|
|
232
253
|
async hold(options = {}) {
|
|
233
254
|
try {
|
|
234
|
-
|
|
235
|
-
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
255
|
+
return await this.holdOrThrow(options);
|
|
236
256
|
} catch (err) {
|
|
237
257
|
this.opts.onError?.(err);
|
|
238
258
|
return null;
|
|
239
259
|
}
|
|
240
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* @internal Like {@link hold} but RE-THROWS the structured API error (409
|
|
263
|
+
* `reason`/`code` + `conflicts`) instead of swallowing it into `onError` +
|
|
264
|
+
* `null`. The native WebView host adapter needs the throw so it can answer the
|
|
265
|
+
* originating command with a correlated error carrying the SPECIFIC reason
|
|
266
|
+
* (`sold_out` vs `not_enough_together`); the public method above keeps the
|
|
267
|
+
* catch-and-onError contract that direct web consumers rely on. Not a stable
|
|
268
|
+
* part of the embed API.
|
|
269
|
+
*/
|
|
270
|
+
async holdOrThrow(options = {}) {
|
|
271
|
+
const h = await this.controller.hold(void 0, options.ttlMs);
|
|
272
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
273
|
+
}
|
|
241
274
|
/** Restore an active hold by its opaque id without extending its expiry. */
|
|
242
275
|
async resumeHold(holdId) {
|
|
243
276
|
try {
|
|
244
|
-
|
|
277
|
+
return await this.resumeHoldOrThrow(holdId);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
this.opts.onError?.(err);
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** @internal Throwing variant of {@link resumeHold} for the native host adapter. See {@link holdOrThrow}. */
|
|
284
|
+
async resumeHoldOrThrow(holdId) {
|
|
285
|
+
const h = await this.controller.resumeHold(holdId);
|
|
286
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Push the OPEN hold's expiry out ("need more time?"). Resolves the refreshed
|
|
290
|
+
* hold, or `null` when there is nothing held or the server refused (the hold
|
|
291
|
+
* is gone, already expired, or at its renewal cap) — refusal is a normal
|
|
292
|
+
* outcome, not an error, so the host decides the copy. The client-side expiry
|
|
293
|
+
* timer is re-armed to match, so `onHoldExpired` won't fire early.
|
|
294
|
+
*/
|
|
295
|
+
async extendHold(ttlMs) {
|
|
296
|
+
try {
|
|
297
|
+
const h = await this.controller.extendHold(ttlMs);
|
|
245
298
|
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
246
299
|
} catch (err) {
|
|
247
300
|
this.opts.onError?.(err);
|
|
@@ -258,23 +311,31 @@ var SeatingChart = class {
|
|
|
258
311
|
}
|
|
259
312
|
async holdGA(areaId, qty, options = {}) {
|
|
260
313
|
try {
|
|
261
|
-
|
|
262
|
-
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
314
|
+
return await this.holdGAOrThrow(areaId, qty, options);
|
|
263
315
|
} catch (err) {
|
|
264
316
|
this.opts.onError?.(err);
|
|
265
317
|
return null;
|
|
266
318
|
}
|
|
267
319
|
}
|
|
320
|
+
/** @internal Throwing variant of {@link holdGA} for the native host adapter. See {@link holdOrThrow}. */
|
|
321
|
+
async holdGAOrThrow(areaId, qty, options = {}) {
|
|
322
|
+
const h = await this.controller.holdGA(areaId, qty, options);
|
|
323
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
324
|
+
}
|
|
268
325
|
/** Ask the server for the `qty` best free seats and hold them atomically. */
|
|
269
326
|
async bestAvailable(qty, categoryKey) {
|
|
270
327
|
try {
|
|
271
|
-
|
|
272
|
-
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, labels: h.labels, seats: h.seats, items: h.items } : null;
|
|
328
|
+
return await this.bestAvailableOrThrow(qty, categoryKey);
|
|
273
329
|
} catch (err) {
|
|
274
330
|
this.opts.onError?.(err);
|
|
275
331
|
return null;
|
|
276
332
|
}
|
|
277
333
|
}
|
|
334
|
+
/** @internal Throwing variant of {@link bestAvailable} for the native host adapter. See {@link holdOrThrow}. */
|
|
335
|
+
async bestAvailableOrThrow(qty, categoryKey) {
|
|
336
|
+
const h = await this.controller.bestAvailable(qty, categoryKey);
|
|
337
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, labels: h.labels, seats: h.seats, items: h.items } : null;
|
|
338
|
+
}
|
|
278
339
|
/**
|
|
279
340
|
* Choose a ticket tier for a selected seat (e.g. Adult → Child). The seat's
|
|
280
341
|
* available `tiers` are on each `SelectedSeat` from `getSelection()` /
|
|
@@ -335,6 +396,7 @@ var SeatingChart = class {
|
|
|
335
396
|
this.hostEl = null;
|
|
336
397
|
this.mount = null;
|
|
337
398
|
this.rendered = false;
|
|
399
|
+
this.mode_ = null;
|
|
338
400
|
}
|
|
339
401
|
};
|
|
340
402
|
|
|
@@ -1438,6 +1500,17 @@ var CSS = `
|
|
|
1438
1500
|
.sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
|
|
1439
1501
|
.sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
|
|
1440
1502
|
.sl-ba-copy .narrow{display:none}
|
|
1503
|
+
/* "\u2605 Best seats" premium quick-pick \u2014 gold accent echoing the \u2605 Premium pill on
|
|
1504
|
+
the confirm popover; deliberately distinct from the accent-toned qty/go. */
|
|
1505
|
+
.sl-ba-premium{position:relative;z-index:1;grid-column:1/-1;justify-self:start;display:inline-flex;align-items:center;gap:6px;
|
|
1506
|
+
padding:6px 12px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;cursor:pointer;
|
|
1507
|
+
color:#c9a24b;background:color-mix(in srgb,#e8c15a 10%,var(--sl-surface));
|
|
1508
|
+
border:1px solid color-mix(in srgb,#e8c15a 34%,var(--sl-line));transition:filter .15s,background .15s,color .15s}
|
|
1509
|
+
.sl-ba-premium .star{font-size:12px;line-height:1;color:#e8c15a}
|
|
1510
|
+
.sl-ba-premium:hover{filter:brightness(1.05)}
|
|
1511
|
+
.sl-ba-premium.on{color:#1c1608;background:linear-gradient(135deg,#f0cf6b,#e0b23f);border-color:transparent;
|
|
1512
|
+
box-shadow:0 6px 16px color-mix(in srgb,#e8c15a 26%,transparent)}
|
|
1513
|
+
.sl-ba-premium.on .star{color:#5a4410}
|
|
1441
1514
|
.sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
|
|
1442
1515
|
font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
|
|
1443
1516
|
.sl-ba-qty{display:flex;align-items:center;gap:7px;padding:3px;border:1px solid var(--sl-line);border-radius:9px;background:var(--sl-surface)}
|
|
@@ -1535,6 +1608,25 @@ var CSS = `
|
|
|
1535
1608
|
.sl-confirm-view:hover{border-color:var(--sl-muted)}
|
|
1536
1609
|
.sl-confirm-view svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
1537
1610
|
|
|
1611
|
+
/* commercial seat flags \u2014 limited-view caution + premium tag. Amber tone,
|
|
1612
|
+
deliberately distinct from the red taken/held state; shown on the confirm
|
|
1613
|
+
card, echoed as a small \u25D0 marker on cart chips and the hover tip. */
|
|
1614
|
+
.sl-cx{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}
|
|
1615
|
+
.sl-cx-warn{display:flex;align-items:flex-start;gap:7px;padding:8px 10px;border-radius:9px;
|
|
1616
|
+
background:color-mix(in srgb,#f4b740 13%,var(--sl-surface));border:1px solid color-mix(in srgb,#f4b740 40%,var(--sl-line));
|
|
1617
|
+
animation:slNoticeIn .28s ease both}
|
|
1618
|
+
.sl-cx-glyph{flex:none;font-size:14px;line-height:1.2;color:#f4b740}
|
|
1619
|
+
.sl-cx-txt{min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
1620
|
+
.sl-cx-txt b{font-size:12px;font-weight:800;color:var(--sl-text)}
|
|
1621
|
+
.sl-cx-note{font-size:11px;line-height:1.4;color:var(--sl-muted)}
|
|
1622
|
+
.sl-cx-premium{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;padding:4px 10px;border-radius:999px;
|
|
1623
|
+
font-size:11px;font-weight:800;letter-spacing:.02em;color:#c9a24b;
|
|
1624
|
+
background:color-mix(in srgb,#e8c15a 13%,var(--sl-surface));border:1px solid color-mix(in srgb,#e8c15a 38%,var(--sl-line))}
|
|
1625
|
+
.sl-cx-star{font-size:12px;line-height:1;color:#e8c15a}
|
|
1626
|
+
.sl-cx-mark{flex:none;font-size:12px;line-height:1;color:#f4b740;cursor:help}
|
|
1627
|
+
.sl-tip-cx{display:flex;align-items:center;gap:6px;padding:5px 10px 7px;font-size:10.5px;font-weight:700;color:#e8b24a}
|
|
1628
|
+
.sl-tip-cx .g{font-size:12px}
|
|
1629
|
+
|
|
1538
1630
|
/* 360\xB0 seat-view modal (fills the widget; drag-to-look-around equirectangular) */
|
|
1539
1631
|
.sl-view{position:absolute;inset:0;z-index:12;display:flex;flex-direction:column;background:var(--sl-bg)}
|
|
1540
1632
|
.sl-view-head{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
|
|
@@ -1663,6 +1755,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
1663
1755
|
this.srEl = null;
|
|
1664
1756
|
this.baQty = 2;
|
|
1665
1757
|
this.baCat = "";
|
|
1758
|
+
/** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
|
|
1759
|
+
this.baPremium = false;
|
|
1666
1760
|
this.bestAvailableConfirm = false;
|
|
1667
1761
|
this.releasingHold = false;
|
|
1668
1762
|
/** Event sales window is closed (read-only load state / live close). */
|
|
@@ -1819,6 +1913,50 @@ var SeatPicker = class _SeatPicker {
|
|
|
1819
1913
|
const sight = distance != null ? t2("picker.sightline", { m: distance }) : this.tf("picker.sightlineClear", "Clear sightline");
|
|
1820
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><div class="sl-confirm-sight"><span aria-hidden="true">\u2713</span>${sight}</div>`;
|
|
1821
1915
|
}
|
|
1916
|
+
/** Minimal HTML/attribute escaper for buyer-authored commercial text (notes). */
|
|
1917
|
+
escCx(value) {
|
|
1918
|
+
return String(value ?? "").replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
1919
|
+
}
|
|
1920
|
+
/** Localized "Restricted view" / "Obstructed view" label for a seat's flags,
|
|
1921
|
+
* or '' when neither is set. Restricted takes precedence when both are on. */
|
|
1922
|
+
limitedViewLabel(c) {
|
|
1923
|
+
if (c?.restrictedView) return this.tf("picker.restrictedView", "Restricted view");
|
|
1924
|
+
if (c?.obstructedView) return this.tf("picker.obstructedView", "Obstructed view");
|
|
1925
|
+
return "";
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Commercial flags block for the confirm/detail surface: a subtle ★ Premium
|
|
1929
|
+
* tag plus an amber ◐ limited-view caution (with the organizer's note when
|
|
1930
|
+
* present). '' when the seat carries no surfaced commercial flag.
|
|
1931
|
+
*/
|
|
1932
|
+
commercialConfirmHtml(c) {
|
|
1933
|
+
if (!c) return "";
|
|
1934
|
+
const rows = [];
|
|
1935
|
+
if (c.premium) {
|
|
1936
|
+
rows.push(
|
|
1937
|
+
`<div class="sl-cx-premium"><span class="sl-cx-star" aria-hidden="true">\u2605</span>${this.tf("picker.premiumSeat", "Premium seat")}</div>`
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
const limited = this.limitedViewLabel(c);
|
|
1941
|
+
if (limited) {
|
|
1942
|
+
rows.push(
|
|
1943
|
+
`<div class="sl-cx-warn"><span class="sl-cx-glyph" aria-hidden="true">\u25D0</span><span class="sl-cx-txt"><b>${limited}</b>${c.note ? `<span class="sl-cx-note">${this.escCx(c.note)}</span>` : ""}</span></div>`
|
|
1944
|
+
);
|
|
1945
|
+
} else if (c.note) {
|
|
1946
|
+
rows.push(
|
|
1947
|
+
`<div class="sl-cx-warn"><span class="sl-cx-glyph" aria-hidden="true">\u2139</span><span class="sl-cx-txt"><span class="sl-cx-note">${this.escCx(c.note)}</span></span></div>`
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
return rows.length ? `<div class="sl-cx">${rows.join("")}</div>` : "";
|
|
1951
|
+
}
|
|
1952
|
+
/** Small ◐ limited-view marker for a cart chip; title/aria uses the seat's
|
|
1953
|
+
* note when present, else the generic view label. '' for a clear-view seat. */
|
|
1954
|
+
commercialChipMarker(c) {
|
|
1955
|
+
const limited = this.limitedViewLabel(c);
|
|
1956
|
+
if (!limited) return "";
|
|
1957
|
+
const title = this.escCx(c?.note ? c.note : limited);
|
|
1958
|
+
return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
|
|
1959
|
+
}
|
|
1822
1960
|
/** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
|
|
1823
1961
|
isFramed() {
|
|
1824
1962
|
return typeof window !== "undefined" && window.parent !== window;
|
|
@@ -2191,45 +2329,71 @@ var SeatPicker = class _SeatPicker {
|
|
|
2191
2329
|
this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
|
|
2192
2330
|
this.buildBadge(chartTheme);
|
|
2193
2331
|
const present = /* @__PURE__ */ new Set();
|
|
2332
|
+
let hasLimitedView = false;
|
|
2194
2333
|
if (this.controller.doc) {
|
|
2195
2334
|
for (const seat of expandChart(this.controller.doc)) {
|
|
2196
2335
|
for (const type of seat.accessibility ?? []) present.add(type);
|
|
2197
2336
|
if (seat.accessible && !seat.accessibility?.length) present.add("wheelchair");
|
|
2337
|
+
if (seat.commercial?.restrictedView || seat.commercial?.obstructedView) hasLimitedView = true;
|
|
2198
2338
|
}
|
|
2199
2339
|
}
|
|
2200
|
-
|
|
2340
|
+
const focusSeatsForFilter = () => {
|
|
2341
|
+
if (this.rungsEl && this.controller.getRung() !== "seats") {
|
|
2342
|
+
this.controller.setRung("seats");
|
|
2343
|
+
this.collapseSectionCard();
|
|
2344
|
+
this.syncRung();
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2347
|
+
if (present.size || hasLimitedView) {
|
|
2201
2348
|
const chips = document.createElement("div");
|
|
2202
2349
|
chips.className = "sl-chips";
|
|
2203
|
-
const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
|
|
2204
|
-
const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-f="${key}">${label}</button>`;
|
|
2205
|
-
chips.innerHTML = mk("all", "All seats") + [...present].map((type) => mk(type, `${GLYPH[type] ? GLYPH[type] + " " : ""}${type[0].toUpperCase()}${type.slice(1).replace(/-/g, " ")}`)).join("");
|
|
2206
2350
|
this.regions["top-left"].appendChild(chips);
|
|
2207
2351
|
this.a11yChipsEl = chips;
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2352
|
+
if (present.size) {
|
|
2353
|
+
const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
|
|
2354
|
+
const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
|
|
2355
|
+
chips.insertAdjacentHTML(
|
|
2356
|
+
"beforeend",
|
|
2357
|
+
mk("all", "All seats") + [...present].map((type) => mk(type, `${GLYPH[type] ? GLYPH[type] + " " : ""}${type[0].toUpperCase()}${type.slice(1).replace(/-/g, " ")}`)).join("")
|
|
2358
|
+
);
|
|
2359
|
+
const active = /* @__PURE__ */ new Set();
|
|
2360
|
+
const syncChips = () => {
|
|
2361
|
+
chips.querySelectorAll("button[data-a11y]").forEach((b) => {
|
|
2362
|
+
const f = b.dataset.f;
|
|
2363
|
+
const on = f === "all" ? active.size === 0 : active.has(f);
|
|
2364
|
+
b.classList.toggle("on", on);
|
|
2365
|
+
b.setAttribute("aria-pressed", String(on));
|
|
2366
|
+
});
|
|
2367
|
+
const filter = active.size ? [...active] : null;
|
|
2368
|
+
this.controller.setAccessibilityFilter(filter);
|
|
2369
|
+
if (filter) focusSeatsForFilter();
|
|
2370
|
+
};
|
|
2371
|
+
chips.querySelectorAll("button[data-a11y]").forEach((btn) => {
|
|
2372
|
+
btn.addEventListener("click", () => {
|
|
2373
|
+
const f = btn.dataset.f;
|
|
2374
|
+
if (f === "all") active.clear();
|
|
2375
|
+
else if (active.has(f)) active.delete(f);
|
|
2376
|
+
else active.add(f);
|
|
2377
|
+
syncChips();
|
|
2378
|
+
});
|
|
2215
2379
|
});
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2380
|
+
}
|
|
2381
|
+
if (hasLimitedView) {
|
|
2382
|
+
const limited = document.createElement("button");
|
|
2383
|
+
limited.type = "button";
|
|
2384
|
+
limited.className = "sl-chip-f";
|
|
2385
|
+
limited.setAttribute("aria-pressed", "false");
|
|
2386
|
+
limited.innerHTML = `\u25D0 ${this.tf("picker.hideLimitedView", "Hide limited-view seats")}`;
|
|
2387
|
+
chips.appendChild(limited);
|
|
2388
|
+
let limitedOn = false;
|
|
2389
|
+
limited.addEventListener("click", () => {
|
|
2390
|
+
limitedOn = !limitedOn;
|
|
2391
|
+
limited.classList.toggle("on", limitedOn);
|
|
2392
|
+
limited.setAttribute("aria-pressed", String(limitedOn));
|
|
2393
|
+
this.controller.setCommercialLimitedFilter(limitedOn);
|
|
2394
|
+
if (limitedOn) focusSeatsForFilter();
|
|
2231
2395
|
});
|
|
2232
|
-
}
|
|
2396
|
+
}
|
|
2233
2397
|
}
|
|
2234
2398
|
const cb = document.createElement("button");
|
|
2235
2399
|
cb.type = "button";
|
|
@@ -2952,7 +3116,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2952
3116
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
2953
3117
|
return `<span class="sl-seccard-mix-item${dim ? " sl-dim" : ""}"><span class="sl-seccard-mix-dot" style="background:${c.color}"></span>${c.label} <span class="sl-seccard-mix-price">${this.money(this.paidPrice(c.key, null, c.price))}</span></span>`;
|
|
2954
3118
|
}).join("");
|
|
2955
|
-
card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${t2("picker.overview")}</button><span class="sl-seccard-hint">${t2("picker.tapSeatHint")}</span></div>`;
|
|
3119
|
+
card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (summary.entrance ? `<div class="sl-seccard-entrance">${t2("picker.entrance")} ${String(summary.entrance).replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch])}</div>` : "") + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${t2("picker.overview")}</button><span class="sl-seccard-hint">${t2("picker.tapSeatHint")}</span></div>`;
|
|
2956
3120
|
card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
|
|
2957
3121
|
card.querySelector(".sl-seccard-overview").addEventListener("click", () => this.controller.overview());
|
|
2958
3122
|
(this.regions["top-center"] ?? this.els.map).appendChild(card);
|
|
@@ -3046,7 +3210,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3046
3210
|
el.setAttribute("aria-modal", "true");
|
|
3047
3211
|
el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
|
|
3048
3212
|
el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
|
|
3049
|
-
el.innerHTML = `<div class="sl-confirm-grid"><div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details?.sectionLabel)}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key"
|
|
3213
|
+
el.innerHTML = `<div class="sl-confirm-grid"><div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details?.sectionLabel)}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key">${safe(this.rowTypeWord(details))}</span><span class="sl-confirm-value">${safe(this.rowShort(details))}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key">Seat</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? seat.displayLabel ?? seat.label)}</span></div></div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
|
|
3050
3214
|
this.els.map.appendChild(el);
|
|
3051
3215
|
this.confirmEl = el;
|
|
3052
3216
|
this.reanchorConfirm();
|
|
@@ -3341,14 +3505,16 @@ var SeatPicker = class _SeatPicker {
|
|
|
3341
3505
|
const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
|
|
3342
3506
|
if (!this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
|
|
3343
3507
|
const cats = this.controller.doc?.categories ?? [];
|
|
3344
|
-
parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Replace your current choices?</div><div class="sl-ba-replace"><b>We\u2019ll find ${this.baQty} seats together.</b><span>Your manually selected tickets will be removed only after a new group is secured.</span></div><div class="sl-ba-actions"><button type="button" data-ba-cancel>Keep mine</button><button type="button" class="replace" data-ba-replace>Find new seats</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Find the best seats together</div><div class="sl-ba-copy"><span class="wide">We\u2019ll choose the closest available group for you.</span><span class="narrow">Closest available group, chosen instantly.</span></div>` +
|
|
3508
|
+
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
|
+
// 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>`);
|
|
3345
3511
|
}
|
|
3346
3512
|
const idGrid = (seatId, label) => {
|
|
3347
3513
|
const d = seatId ? this.controller.seatDetails(seatId) : null;
|
|
3348
3514
|
if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
|
|
3349
3515
|
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Seat</span><span class="val">${label}</span></span></div>`;
|
|
3350
3516
|
}
|
|
3351
|
-
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${d.sectionLabel ?? "\u2014"}</span></span>` + (d.rowLabel ? `<span class="fld mid"><span class="sl-chip-eb"
|
|
3517
|
+
return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${d.sectionLabel ?? "\u2014"}</span></span>` + (d.rowLabel ? `<span class="fld mid"><span class="sl-chip-eb">${this.rowTypeWord(d)}</span><span class="val">${this.rowShort(d)}</span></span>` : "") + (d.seatNumber ? `<span class="fld mid"><span class="sl-chip-eb">Seat</span><span class="val">${d.seatNumber}</span></span>` : "") + `</div>`;
|
|
3352
3518
|
};
|
|
3353
3519
|
const iconRail = (rmAria, viewLabel) => `<div class="sl-chip-rail"><button type="button" class="rm" aria-label="${rmAria}"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>` + (viewLabel ? `<button type="button" class="view" data-view-label="${viewLabel}" aria-label="${t2("picker.viewFromSeat", { label: viewLabel })}"><svg viewBox="0 0 24 24"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg></button>` : "") + `</div>`;
|
|
3354
3520
|
for (const item of heldItems) {
|
|
@@ -3359,7 +3525,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3359
3525
|
const heldSeat = item.objectType !== "ga" ? this.controller.seatByLabel(item.label) : null;
|
|
3360
3526
|
const canView2 = this.seatViewEnabled() && !!heldSeat;
|
|
3361
3527
|
parts.push(
|
|
3362
|
-
`<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"${heldSeat ? ` data-locate="${heldSeat.id}"` : ""}><div class="sl-chip-main">` + idGrid(heldSeat?.id ?? null, item.label) + `<div class="sl-chip-sub"><span class="sl-ticket-state held" aria-label="Held for you" title="Held for you"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></span><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span
|
|
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>`
|
|
3363
3529
|
);
|
|
3364
3530
|
}
|
|
3365
3531
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
@@ -3370,7 +3536,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3370
3536
|
const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
|
|
3371
3537
|
const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${t2("picker.ticketTierFor", { label: s.label })}">` + s.tiers.map((ti) => `<option value="${ti.id}"${ti.id === s.tierId ? " selected" : ""}>${ti.name} \xB7 ${this.money(this.paidPrice(s.categoryKey, ti.id, ti.price))}</option>`).join("") + `</select>` : "";
|
|
3372
3538
|
parts.push(
|
|
3373
|
-
`<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}" data-locate="${s.id}"><div class="sl-chip-main">` + idGrid(s.id, s.label) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="Selected" title="Selected"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price))}</span></div></div>` + iconRail(`Remove ${s.label}`, canView ? s.label : null) + `</div>`
|
|
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.displayLabel ?? s.label) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="Selected" title="Selected"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price))}</span></div></div>` + iconRail(`Remove ${s.label}`, canView ? s.label : null) + `</div>`
|
|
3374
3540
|
);
|
|
3375
3541
|
}
|
|
3376
3542
|
for (const area of gaAreas) {
|
|
@@ -3390,6 +3556,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
3390
3556
|
this.els.tray.querySelector("[data-ba-cat]")?.addEventListener("change", (e) => {
|
|
3391
3557
|
this.baCat = e.target.value;
|
|
3392
3558
|
});
|
|
3559
|
+
this.els.tray.querySelector("[data-ba-premium]")?.addEventListener("click", () => {
|
|
3560
|
+
this.baPremium = !this.baPremium;
|
|
3561
|
+
this.syncTray();
|
|
3562
|
+
});
|
|
3393
3563
|
this.els.tray.querySelector(".sl-ba-go")?.addEventListener("click", () => {
|
|
3394
3564
|
if (this.pendingSelectionCount() > 0) {
|
|
3395
3565
|
this.bestAvailableConfirm = true;
|
|
@@ -3397,7 +3567,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3397
3567
|
this.els.tray.querySelector("[data-ba-replace]")?.focus();
|
|
3398
3568
|
return;
|
|
3399
3569
|
}
|
|
3400
|
-
void this.bestAvailable(this.baQty, this.baCat || void 0);
|
|
3570
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
|
|
3401
3571
|
});
|
|
3402
3572
|
this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
|
|
3403
3573
|
this.bestAvailableConfirm = false;
|
|
@@ -3406,7 +3576,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3406
3576
|
});
|
|
3407
3577
|
this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
|
|
3408
3578
|
this.bestAvailableConfirm = false;
|
|
3409
|
-
void this.bestAvailable(this.baQty, this.baCat || void 0);
|
|
3579
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium });
|
|
3410
3580
|
});
|
|
3411
3581
|
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
3412
3582
|
btn.addEventListener("click", () => {
|
|
@@ -3788,6 +3958,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
3788
3958
|
* the prefix is exact (won't touch "1040-A" under section "104"); otherwise
|
|
3789
3959
|
* the label is shown verbatim.
|
|
3790
3960
|
*/
|
|
3961
|
+
/** Buyer-facing type word for the row/table key label — the designer's
|
|
3962
|
+
* per-object "Displayed type" override, or the default "Row". */
|
|
3963
|
+
rowTypeWord(details) {
|
|
3964
|
+
const t3 = details?.rowType?.trim();
|
|
3965
|
+
return t3 || "Row";
|
|
3966
|
+
}
|
|
3791
3967
|
rowShort(details) {
|
|
3792
3968
|
const row = details?.rowLabel;
|
|
3793
3969
|
const sec = details?.sectionLabel;
|
|
@@ -3807,10 +3983,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
3807
3983
|
const esc2 = (v) => String(v ?? "\u2014").replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
3808
3984
|
const price = this.money(this.paidPrice(details.categoryKey, details.tierId ?? null, details.price));
|
|
3809
3985
|
const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
|
|
3810
|
-
const grid = hasLoc ? `<div class="sl-tip-grid"><div class="sl-tip-field"><span class="sl-tip-key">Section</span><span class="sl-tip-val">${esc2(details.sectionLabel)}</span></div><div class="sl-tip-field"><span class="sl-tip-key"
|
|
3986
|
+
const grid = hasLoc ? `<div class="sl-tip-grid"><div class="sl-tip-field"><span class="sl-tip-key">Section</span><span class="sl-tip-val">${esc2(details.sectionLabel)}</span></div><div class="sl-tip-field"><span class="sl-tip-key">${esc2(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc2(this.rowShort(details))}</span></div><div class="sl-tip-field"><span class="sl-tip-key">Seat</span><span class="sl-tip-val">${esc2(details.seatNumber ?? details.displayLabel ?? details.label)}</span></div></div>` : `<div class="sl-tip-grid one"><div class="sl-tip-field"><span class="sl-tip-key">Seat</span><span class="sl-tip-val">${esc2(details.displayLabel ?? details.label)}</span></div></div>`;
|
|
3811
3987
|
const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${details.status === "held" ? t2("map.statusHeld") : t2("map.statusTaken")}</div>`;
|
|
3988
|
+
const limited = this.limitedViewLabel(details.commercial);
|
|
3989
|
+
const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc2(limited)}</div>` : "";
|
|
3812
3990
|
this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
|
|
3813
|
-
this.tipEl.innerHTML = grid + `<div class="sl-tip-cat"><span class="sl-tip-dot" style="background:${details.categoryColor}"></span><span class="sl-tip-name">${esc2(details.categoryLabel)}</span><span class="sl-tip-amt">${price}</span></div>` + statusLine;
|
|
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;
|
|
3814
3992
|
this.tipEl.style.display = "block";
|
|
3815
3993
|
this.placeTooltip();
|
|
3816
3994
|
}
|
|
@@ -3830,7 +4008,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3830
4008
|
async removeHeldTicket(label) {
|
|
3831
4009
|
return this.removeHeldLabel(label);
|
|
3832
4010
|
}
|
|
3833
|
-
async bestAvailable(qty, categoryKey) {
|
|
4011
|
+
async bestAvailable(qty, categoryKey, opts = {}) {
|
|
3834
4012
|
if (this.salesClosed || this.bestAvailableBusy) return null;
|
|
3835
4013
|
qty = Math.max(1, Math.min(this.maxTickets, Math.floor(qty)));
|
|
3836
4014
|
if (this.confirmSeat) this.cancelConfirm();
|
|
@@ -3842,7 +4020,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3842
4020
|
button.innerHTML = '<span class="sl-ba-spin" aria-hidden="true"></span>Finding\u2026';
|
|
3843
4021
|
}
|
|
3844
4022
|
try {
|
|
3845
|
-
const h = await this.controller.bestAvailable(qty, categoryKey);
|
|
4023
|
+
const h = await this.controller.bestAvailable(qty, categoryKey, opts);
|
|
3846
4024
|
if (h) {
|
|
3847
4025
|
this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
|
|
3848
4026
|
this.handedOff = false;
|
|
@@ -3852,6 +4030,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
3852
4030
|
this.flashHeldSeats(this.hold);
|
|
3853
4031
|
this.syncTray();
|
|
3854
4032
|
this.emitHoldChange();
|
|
4033
|
+
if (opts.preferPremium && h.seats.length && !h.seats.every((s) => s.commercial?.premium)) {
|
|
4034
|
+
this.toast(t2("picker.premiumFallbackNote", { count: qty }), "neutral");
|
|
4035
|
+
}
|
|
3855
4036
|
return this.hold;
|
|
3856
4037
|
}
|
|
3857
4038
|
return null;
|