@seatlayer/js 0.16.0 → 0.16.1
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 +181 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +181 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -425,6 +425,7 @@ declare class SeatPicker {
|
|
|
425
425
|
private readonly api;
|
|
426
426
|
private readonly apiBase;
|
|
427
427
|
private readonly controller;
|
|
428
|
+
private readonly maxTickets;
|
|
428
429
|
private root;
|
|
429
430
|
private mapHost;
|
|
430
431
|
private rendered;
|
|
@@ -456,6 +457,8 @@ declare class SeatPicker {
|
|
|
456
457
|
private a11yFilter;
|
|
457
458
|
private baQty;
|
|
458
459
|
private baCat;
|
|
460
|
+
private bestAvailableConfirm;
|
|
461
|
+
private releasingHold;
|
|
459
462
|
private rungsEl;
|
|
460
463
|
private floorsEl;
|
|
461
464
|
private secCardEl;
|
|
@@ -535,6 +538,14 @@ declare class SeatPicker {
|
|
|
535
538
|
/** Update only the action affordance; selection callbacks must not refire. */
|
|
536
539
|
private committedSelection;
|
|
537
540
|
private pendingSelectionCount;
|
|
541
|
+
private heldGACounts;
|
|
542
|
+
private pendingGACount;
|
|
543
|
+
private heldTicketCount;
|
|
544
|
+
private totalTicketCount;
|
|
545
|
+
/** Held tickets and standing quantities consume the same order-wide cap. */
|
|
546
|
+
private updateSelectionCapacity;
|
|
547
|
+
private canAddTicket;
|
|
548
|
+
private pendingGATotal;
|
|
538
549
|
private syncCta;
|
|
539
550
|
private setCtaPhase;
|
|
540
551
|
/** Session-scoped capability key: isolated by API origin and event. */
|
|
@@ -616,6 +627,7 @@ declare class SeatPicker {
|
|
|
616
627
|
private evictTakenSelections;
|
|
617
628
|
private syncTray;
|
|
618
629
|
private removeHeldLabel;
|
|
630
|
+
private handleChangeSeats;
|
|
619
631
|
private handleCta;
|
|
620
632
|
private startHoldTimer;
|
|
621
633
|
private stopHoldTimer;
|
package/dist/index.d.ts
CHANGED
|
@@ -425,6 +425,7 @@ declare class SeatPicker {
|
|
|
425
425
|
private readonly api;
|
|
426
426
|
private readonly apiBase;
|
|
427
427
|
private readonly controller;
|
|
428
|
+
private readonly maxTickets;
|
|
428
429
|
private root;
|
|
429
430
|
private mapHost;
|
|
430
431
|
private rendered;
|
|
@@ -456,6 +457,8 @@ declare class SeatPicker {
|
|
|
456
457
|
private a11yFilter;
|
|
457
458
|
private baQty;
|
|
458
459
|
private baCat;
|
|
460
|
+
private bestAvailableConfirm;
|
|
461
|
+
private releasingHold;
|
|
459
462
|
private rungsEl;
|
|
460
463
|
private floorsEl;
|
|
461
464
|
private secCardEl;
|
|
@@ -535,6 +538,14 @@ declare class SeatPicker {
|
|
|
535
538
|
/** Update only the action affordance; selection callbacks must not refire. */
|
|
536
539
|
private committedSelection;
|
|
537
540
|
private pendingSelectionCount;
|
|
541
|
+
private heldGACounts;
|
|
542
|
+
private pendingGACount;
|
|
543
|
+
private heldTicketCount;
|
|
544
|
+
private totalTicketCount;
|
|
545
|
+
/** Held tickets and standing quantities consume the same order-wide cap. */
|
|
546
|
+
private updateSelectionCapacity;
|
|
547
|
+
private canAddTicket;
|
|
548
|
+
private pendingGATotal;
|
|
538
549
|
private syncCta;
|
|
539
550
|
private setCtaPhase;
|
|
540
551
|
/** Session-scoped capability key: isolated by API origin and event. */
|
|
@@ -616,6 +627,7 @@ declare class SeatPicker {
|
|
|
616
627
|
private evictTakenSelections;
|
|
617
628
|
private syncTray;
|
|
618
629
|
private removeHeldLabel;
|
|
630
|
+
private handleChangeSeats;
|
|
619
631
|
private handleCta;
|
|
620
632
|
private startHoldTimer;
|
|
621
633
|
private stopHoldTimer;
|
package/dist/index.js
CHANGED
|
@@ -476,7 +476,7 @@ var CSS = `
|
|
|
476
476
|
.sl-body{display:flex;flex:1;min-height:0}
|
|
477
477
|
.sl-map{position:relative;flex:1;min-width:0}
|
|
478
478
|
.sl-map-host{position:absolute;inset:0}
|
|
479
|
-
.sl-side{width:300px;flex:none;border-left:1px solid var(--sl-line);display:flex;flex-direction:column;min-height:0;overflow
|
|
479
|
+
.sl-side{width:300px;flex:none;border-left:1px solid var(--sl-line);display:flex;flex-direction:column;min-height:0;overflow:hidden}
|
|
480
480
|
|
|
481
481
|
/* narrow (container < 640px): map-first \u2014 the map claims ~80-85% of the
|
|
482
482
|
container and the side panel becomes a PEEKING bottom sheet (AXS/Ticketmaster
|
|
@@ -559,6 +559,7 @@ var CSS = `
|
|
|
559
559
|
.sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
|
|
560
560
|
background:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
|
|
561
561
|
.sl-prices{padding:5px 14px 10px;border-bottom:1px solid var(--sl-line)}
|
|
562
|
+
.sl-prices-sec,.sl-prices,.sl-seats-sec{flex:none}
|
|
562
563
|
.sl-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px}
|
|
563
564
|
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
564
565
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
@@ -575,7 +576,8 @@ var CSS = `
|
|
|
575
576
|
/* tray */
|
|
576
577
|
.sl-seats-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
577
578
|
.sl-seat-summary{font-size:10px;letter-spacing:0;text-transform:none;white-space:nowrap}
|
|
578
|
-
.sl-tray{flex:1;padding:10px 14px;display:flex;flex-direction:column;gap:7px;min-height:0
|
|
579
|
+
.sl-tray{flex:1;padding:10px 14px 14px;display:flex;flex-direction:column;gap:7px;min-height:0;overflow-y:auto;
|
|
580
|
+
overscroll-behavior:contain;scrollbar-gutter:stable}
|
|
579
581
|
.sl-tray-hint{font-size:12.5px;color:var(--sl-muted);line-height:1.5}
|
|
580
582
|
.sl-chip{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto 30px;align-items:center;gap:8px;
|
|
581
583
|
min-height:53px;padding:7px 7px 7px 9px;border:1px solid var(--sl-line);border-radius:var(--sl-r-sm);
|
|
@@ -610,16 +612,22 @@ var CSS = `
|
|
|
610
612
|
.sl-ga-qty span{min-width:16px;text-align:center;font-weight:800;font-variant-numeric:tabular-nums}
|
|
611
613
|
|
|
612
614
|
/* footer */
|
|
613
|
-
.sl-foot{padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none
|
|
614
|
-
|
|
615
|
+
.sl-foot{position:relative;z-index:2;padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none;
|
|
616
|
+
background:var(--sl-bg);box-shadow:0 -10px 24px -22px rgba(0,0,0,.72)}
|
|
617
|
+
.sl-hold-note{display:none;align-items:center;gap:7px;margin-bottom:8px;padding:7px 8px;border-radius:var(--sl-r-sm);
|
|
615
618
|
border:1px solid var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
|
|
616
619
|
box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent);
|
|
617
620
|
font-size:11.5px;line-height:1.35;color:var(--sl-muted)}
|
|
618
621
|
.sl-hold-note.on{display:flex;animation:slNoticeIn .38s cubic-bezier(.2,.8,.2,1) both}
|
|
619
622
|
.sl-hold-note svg{width:16px;height:16px;flex:none;stroke:var(--sl-accent);stroke-width:2.4;fill:none;
|
|
620
623
|
stroke-linecap:round;stroke-linejoin:round}
|
|
621
|
-
.sl-hold-note b{display:block;color:var(--sl-text);font-size:
|
|
622
|
-
.sl-hold-copy{display:block}
|
|
624
|
+
.sl-hold-note b{display:block;color:var(--sl-text);font-size:11.5px;white-space:nowrap}
|
|
625
|
+
.sl-hold-copy{display:block;white-space:nowrap;font-size:10.5px}
|
|
626
|
+
.sl-hold-note>span{flex:1;min-width:0}
|
|
627
|
+
.sl-hold-change{flex:none;min-height:30px;padding:5px 8px;border-radius:8px;border:1px solid var(--sl-line);
|
|
628
|
+
color:var(--sl-text);font-size:10.5px;font-weight:750;white-space:nowrap}
|
|
629
|
+
.sl-hold-change:hover,.sl-hold-change:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent)}
|
|
630
|
+
.sl-hold-change:disabled{opacity:.58;cursor:wait}
|
|
623
631
|
.sl-total{display:flex;justify-content:space-between;align-items:center;font-size:13px;margin-bottom:10px}
|
|
624
632
|
.sl-total b{font-size:17px;font-variant-numeric:tabular-nums}
|
|
625
633
|
.sl-value-pop{animation:slValuePop .32s cubic-bezier(.2,.8,.2,1)}
|
|
@@ -677,6 +685,9 @@ var CSS = `
|
|
|
677
685
|
font-size:12.5px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .22s,transform .22s;white-space:nowrap;
|
|
678
686
|
overflow:hidden;text-overflow:ellipsis}
|
|
679
687
|
.sl-toast.on{opacity:1;transform:translateY(0) scale(1)}
|
|
688
|
+
.sl-toast.has-action{pointer-events:auto;display:flex;align-items:center;gap:12px;padding-right:8px}
|
|
689
|
+
.sl-toast-action{min-height:30px;padding:5px 10px;border-radius:999px;background:var(--sl-accent);color:var(--sl-accent-ink);
|
|
690
|
+
font:inherit;font-weight:800}
|
|
680
691
|
.sl-toast[data-tone="error"]{border-color:#ef4444}
|
|
681
692
|
.sl-toast[data-tone="warning"]{border-color:var(--sl-accent)}
|
|
682
693
|
.sl-toast[data-tone="success"]{border-color:#22c55e}
|
|
@@ -780,6 +791,12 @@ var CSS = `
|
|
|
780
791
|
box-shadow:0 8px 18px color-mix(in srgb,var(--sl-accent) 18%,transparent)}
|
|
781
792
|
.sl-picker .sl-ba-go:hover{filter:brightness(1.06)}
|
|
782
793
|
.sl-picker .sl-ba-go:disabled{opacity:.62;cursor:wait}
|
|
794
|
+
.sl-ba-replace{grid-column:1/-1;padding:3px 0 1px}
|
|
795
|
+
.sl-ba-replace b{display:block;font-size:12.5px}
|
|
796
|
+
.sl-ba-replace span{display:block;margin-top:3px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
|
|
797
|
+
.sl-ba-actions{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:7px}
|
|
798
|
+
.sl-ba-actions button{min-height:36px;border-radius:9px;border:1px solid var(--sl-line);font-size:11.5px;font-weight:800}
|
|
799
|
+
.sl-ba-actions .replace{border-color:var(--sl-accent);background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
783
800
|
.sl-picker[data-layout="narrow"] .sl-ba{padding:11px}
|
|
784
801
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
|
|
785
802
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
|
|
@@ -971,6 +988,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
971
988
|
this.a11yFilter = "all";
|
|
972
989
|
this.baQty = 2;
|
|
973
990
|
this.baCat = "";
|
|
991
|
+
this.bestAvailableConfirm = false;
|
|
992
|
+
this.releasingHold = false;
|
|
974
993
|
// arena / multi-floor / seat-view chrome
|
|
975
994
|
this.rungsEl = null;
|
|
976
995
|
this.floorsEl = null;
|
|
@@ -1018,10 +1037,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
1018
1037
|
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
1019
1038
|
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
1020
1039
|
this.api = new PubApi(this.apiBase);
|
|
1040
|
+
this.maxTickets = Math.max(1, Math.floor(options.maxSelection ?? DEFAULT_MAX_SELECTION2));
|
|
1021
1041
|
this.controller = new PickerController2({
|
|
1022
1042
|
transport: this.api,
|
|
1023
1043
|
eventKey: options.event,
|
|
1024
|
-
maxSelection:
|
|
1044
|
+
maxSelection: this.maxTickets,
|
|
1025
1045
|
currency: options.currency,
|
|
1026
1046
|
flashOnLiveChange: true,
|
|
1027
1047
|
colorblindSafe: options.colorblindSafe,
|
|
@@ -1056,6 +1076,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
1056
1076
|
onDeselect: (seat) => {
|
|
1057
1077
|
if (this.confirmSeat?.id === seat.id) this.dismissConfirm();
|
|
1058
1078
|
},
|
|
1079
|
+
onSelectionLimit: () => {
|
|
1080
|
+
this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
|
|
1081
|
+
},
|
|
1059
1082
|
onViewChange: () => {
|
|
1060
1083
|
this.reanchorConfirm();
|
|
1061
1084
|
this.syncRung();
|
|
@@ -1118,6 +1141,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
1118
1141
|
if (picker.confirmSeat) {
|
|
1119
1142
|
e.preventDefault();
|
|
1120
1143
|
picker.cancelConfirm();
|
|
1144
|
+
} else if (picker.bestAvailableConfirm) {
|
|
1145
|
+
e.preventDefault();
|
|
1146
|
+
picker.bestAvailableConfirm = false;
|
|
1147
|
+
picker.syncTray();
|
|
1121
1148
|
} else {
|
|
1122
1149
|
close();
|
|
1123
1150
|
}
|
|
@@ -1141,10 +1168,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
1141
1168
|
this.root = root;
|
|
1142
1169
|
mount.appendChild(root);
|
|
1143
1170
|
root.addEventListener("keydown", (e) => {
|
|
1144
|
-
if (e.key !== "Escape"
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1171
|
+
if (e.key !== "Escape") return;
|
|
1172
|
+
if (this.confirmSeat) {
|
|
1173
|
+
e.preventDefault();
|
|
1174
|
+
e.stopPropagation();
|
|
1175
|
+
this.cancelConfirm();
|
|
1176
|
+
} else if (this.bestAvailableConfirm) {
|
|
1177
|
+
e.preventDefault();
|
|
1178
|
+
e.stopPropagation();
|
|
1179
|
+
this.bestAvailableConfirm = false;
|
|
1180
|
+
this.syncTray();
|
|
1181
|
+
}
|
|
1148
1182
|
});
|
|
1149
1183
|
Object.entries(resolveTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
1150
1184
|
root.innerHTML = `
|
|
@@ -1192,6 +1226,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1192
1226
|
<div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
|
|
1193
1227
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
|
|
1194
1228
|
<span><b data-ref="holdTitle">Seats secured</b><span class="sl-hold-copy" data-ref="holdCopy">Checkout timer is running.</span></span>
|
|
1229
|
+
<button type="button" class="sl-hold-change" data-ref="holdChange" aria-label="Release held tickets and choose different seats">Change</button>
|
|
1195
1230
|
</div>
|
|
1196
1231
|
<div class="sl-total"><span data-ref="count"></span><b data-ref="total"></b></div>
|
|
1197
1232
|
<button type="button" class="sl-cta" data-ref="cta" disabled></button>
|
|
@@ -1269,6 +1304,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1269
1304
|
if (this.tipEl && this.tipEl.style.display !== "none") this.placeTooltip();
|
|
1270
1305
|
});
|
|
1271
1306
|
this.els.cta.addEventListener("click", () => void this.handleCta());
|
|
1307
|
+
this.els.holdChange?.addEventListener("click", () => void this.handleChangeSeats());
|
|
1272
1308
|
const canvasHost = document.createElement("div");
|
|
1273
1309
|
canvasHost.style.cssText = "position:absolute;inset:0";
|
|
1274
1310
|
this.mapHost.appendChild(canvasHost);
|
|
@@ -1475,15 +1511,51 @@ var SeatPicker = class _SeatPicker {
|
|
|
1475
1511
|
const heldItems = this.hold?.items ?? [];
|
|
1476
1512
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
1477
1513
|
const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
|
|
1514
|
+
return pendingSeats + this.pendingGACount();
|
|
1515
|
+
}
|
|
1516
|
+
heldGACounts() {
|
|
1478
1517
|
const heldGA = /* @__PURE__ */ new Map();
|
|
1479
|
-
for (const item of
|
|
1518
|
+
for (const item of (this.hold?.items ?? []).filter((candidate) => candidate.objectType === "ga")) {
|
|
1480
1519
|
heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
|
|
1481
1520
|
}
|
|
1482
|
-
|
|
1521
|
+
return heldGA;
|
|
1522
|
+
}
|
|
1523
|
+
pendingGACount() {
|
|
1524
|
+
const heldGA = this.heldGACounts();
|
|
1525
|
+
return [...this.gaQty.entries()].reduce(
|
|
1483
1526
|
(sum, [areaId, qty]) => sum + Math.max(0, qty - (heldGA.get(areaId) ?? 0)),
|
|
1484
1527
|
0
|
|
1485
1528
|
);
|
|
1486
|
-
|
|
1529
|
+
}
|
|
1530
|
+
heldTicketCount() {
|
|
1531
|
+
return (this.hold?.items ?? []).reduce((sum, item) => sum + (item.quantity ?? 1), 0);
|
|
1532
|
+
}
|
|
1533
|
+
totalTicketCount() {
|
|
1534
|
+
const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
|
|
1535
|
+
const freshSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
|
|
1536
|
+
return this.heldTicketCount() + freshSeats + this.pendingGACount();
|
|
1537
|
+
}
|
|
1538
|
+
/** Held tickets and standing quantities consume the same order-wide cap. */
|
|
1539
|
+
updateSelectionCapacity() {
|
|
1540
|
+
const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
|
|
1541
|
+
const selectedHeld = this.committedSelection().filter((seat) => heldLabels.has(seat.label)).length;
|
|
1542
|
+
const remaining = Math.max(0, this.maxTickets - this.heldTicketCount() - this.pendingGACount());
|
|
1543
|
+
this.controller.setMaxSelection(selectedHeld + remaining);
|
|
1544
|
+
}
|
|
1545
|
+
canAddTicket() {
|
|
1546
|
+
if (this.totalTicketCount() < this.maxTickets) return true;
|
|
1547
|
+
this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
|
|
1548
|
+
return false;
|
|
1549
|
+
}
|
|
1550
|
+
pendingGATotal(gaAreas) {
|
|
1551
|
+
const heldGA = /* @__PURE__ */ new Map();
|
|
1552
|
+
for (const item of (this.hold?.items ?? []).filter((candidate) => candidate.objectType === "ga")) {
|
|
1553
|
+
heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
|
|
1554
|
+
}
|
|
1555
|
+
return gaAreas.reduce(
|
|
1556
|
+
(sum, area) => sum + area.price * Math.max(0, (this.gaQty.get(area.id) ?? 0) - (heldGA.get(area.id) ?? 0)),
|
|
1557
|
+
0
|
|
1558
|
+
);
|
|
1487
1559
|
}
|
|
1488
1560
|
syncCta(count = this.lastTrayCount, pending = this.pendingSelectionCount()) {
|
|
1489
1561
|
const cta = this.els.cta;
|
|
@@ -2210,6 +2282,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2210
2282
|
}
|
|
2211
2283
|
syncTray() {
|
|
2212
2284
|
if (!this.els.tray) return;
|
|
2285
|
+
this.updateSelectionCapacity();
|
|
2213
2286
|
const seats = this.committedSelection();
|
|
2214
2287
|
const gaAreas = this.controller.getGAAreas();
|
|
2215
2288
|
const heldItems = this.hold?.items ?? [];
|
|
@@ -2222,9 +2295,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2222
2295
|
}
|
|
2223
2296
|
if (!this.hold) {
|
|
2224
2297
|
const cats = this.controller.doc?.categories ?? [];
|
|
2225
|
-
parts.push(
|
|
2226
|
-
`<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Find the best seats together</div><div class="sl-ba-copy"><span class="wide">We\u2019ll choose the closest available group for you.</span><span class="narrow">Closest available group, chosen instantly.</span></div>` + (cats.length > 1 ? `<select aria-label="Preferred ticket type" data-ba-cat><option value="">Any ticket type</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : `<span aria-hidden="true"></span>`) + `<div class="sl-ba-qty"><button type="button" data-ba="-1" aria-label="Fewer seats">\u2212</button><span>${this.baQty}</span><button type="button" data-ba="1" aria-label="More seats">+</button></div><button type="button" class="sl-ba-go"${this.bestAvailableBusy ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>Finding the best seats\u2026` : `Find ${this.baQty} best ${this.baQty === 1 ? "seat" : "seats"}`) + `</button></div>`
|
|
2227
|
-
);
|
|
2298
|
+
parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Replace your current choices?</div><div class="sl-ba-replace"><b>We\u2019ll find ${this.baQty} seats together.</b><span>Your manually selected tickets will be removed only after a new group is secured.</span></div><div class="sl-ba-actions"><button type="button" data-ba-cancel>Keep mine</button><button type="button" class="replace" data-ba-replace>Find new seats</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Find the best seats together</div><div class="sl-ba-copy"><span class="wide">We\u2019ll choose the closest available group for you.</span><span class="narrow">Closest available group, chosen instantly.</span></div>` + (cats.length > 1 ? `<select aria-label="Preferred ticket type" data-ba-cat><option value="">Any ticket type</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : `<span aria-hidden="true"></span>`) + `<div class="sl-ba-qty"><button type="button" data-ba="-1" aria-label="Fewer seats">\u2212</button><span>${this.baQty}</span><button type="button" data-ba="1" aria-label="More seats">+</button></div><button type="button" class="sl-ba-go"${this.bestAvailableBusy ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>Finding the best seats\u2026` : `Find ${this.baQty} best ${this.baQty === 1 ? "seat" : "seats"}`) + `</button></div>`);
|
|
2228
2299
|
}
|
|
2229
2300
|
for (const item of heldItems) {
|
|
2230
2301
|
const itemKey = `held:${item.label}`;
|
|
@@ -2259,7 +2330,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2259
2330
|
this.lastTrayKeys = nextTrayKeys;
|
|
2260
2331
|
this.els.tray.querySelectorAll("[data-ba]").forEach((btn) => {
|
|
2261
2332
|
btn.addEventListener("click", () => {
|
|
2262
|
-
this.baQty = Math.max(1, Math.min(
|
|
2333
|
+
this.baQty = Math.max(1, Math.min(this.maxTickets, this.baQty + Number(btn.dataset.ba)));
|
|
2263
2334
|
this.syncTray();
|
|
2264
2335
|
});
|
|
2265
2336
|
});
|
|
@@ -2267,6 +2338,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
2267
2338
|
this.baCat = e.target.value;
|
|
2268
2339
|
});
|
|
2269
2340
|
this.els.tray.querySelector(".sl-ba-go")?.addEventListener("click", () => {
|
|
2341
|
+
if (this.pendingSelectionCount() > 0) {
|
|
2342
|
+
this.bestAvailableConfirm = true;
|
|
2343
|
+
this.syncTray();
|
|
2344
|
+
this.els.tray.querySelector("[data-ba-replace]")?.focus();
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0);
|
|
2348
|
+
});
|
|
2349
|
+
this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
|
|
2350
|
+
this.bestAvailableConfirm = false;
|
|
2351
|
+
this.syncTray();
|
|
2352
|
+
this.els.tray.querySelector(".sl-ba-go")?.focus();
|
|
2353
|
+
});
|
|
2354
|
+
this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
|
|
2355
|
+
this.bestAvailableConfirm = false;
|
|
2270
2356
|
void this.bestAvailable(this.baQty, this.baCat || void 0);
|
|
2271
2357
|
});
|
|
2272
2358
|
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
@@ -2277,12 +2363,26 @@ var SeatPicker = class _SeatPicker {
|
|
|
2277
2363
|
return;
|
|
2278
2364
|
}
|
|
2279
2365
|
const id = chip.dataset.seat;
|
|
2280
|
-
|
|
2366
|
+
const label = chip.querySelector("b")?.textContent ?? "Seat";
|
|
2367
|
+
const remove = () => {
|
|
2281
2368
|
this.controller.deselect([id]);
|
|
2369
|
+
this.toast(`${label} removed.`, "neutral", {
|
|
2370
|
+
label: "Undo",
|
|
2371
|
+
onClick: () => {
|
|
2372
|
+
const restored = this.controller.select([id]);
|
|
2373
|
+
this.toast(
|
|
2374
|
+
restored.length ? `${label} restored.` : `${label} is no longer available.`,
|
|
2375
|
+
restored.length ? "success" : "warning"
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
};
|
|
2380
|
+
if (this.reducedMotion()) {
|
|
2381
|
+
remove();
|
|
2282
2382
|
return;
|
|
2283
2383
|
}
|
|
2284
2384
|
chip.classList.add("sl-leave");
|
|
2285
|
-
this.scheduleMotion(
|
|
2385
|
+
this.scheduleMotion(remove, 150);
|
|
2286
2386
|
});
|
|
2287
2387
|
});
|
|
2288
2388
|
this.els.tray.querySelectorAll(".sl-chip .tier").forEach((sel) => {
|
|
@@ -2299,13 +2399,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
2299
2399
|
const areaEl = btn.closest(".sl-ga");
|
|
2300
2400
|
const id = areaEl.dataset.ga;
|
|
2301
2401
|
const area = gaAreas.find((a) => a.id === id);
|
|
2302
|
-
const
|
|
2402
|
+
const delta = Number(btn.dataset.d);
|
|
2403
|
+
if (delta > 0 && !this.canAddTicket()) return;
|
|
2404
|
+
const next = Math.max(0, Math.min(area?.available ?? 0, (this.gaQty.get(id) ?? 0) + delta));
|
|
2303
2405
|
this.gaQty.set(id, next);
|
|
2304
2406
|
this.syncTray();
|
|
2305
2407
|
});
|
|
2306
2408
|
});
|
|
2307
|
-
const gaTotal =
|
|
2308
|
-
const gaCount =
|
|
2409
|
+
const gaTotal = this.pendingGATotal(gaAreas);
|
|
2410
|
+
const gaCount = this.pendingGACount();
|
|
2309
2411
|
const heldTotal = heldItems.reduce((sum, item) => sum + item.unitPrice * (item.quantity ?? 1), 0);
|
|
2310
2412
|
const heldCount = heldItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
|
|
2311
2413
|
const freshSeats = seats.filter((seat) => !heldLabels.has(seat.label));
|
|
@@ -2325,10 +2427,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
2325
2427
|
if (this.hold) {
|
|
2326
2428
|
const securedCount = heldCount || this.hold.seats?.length || 0;
|
|
2327
2429
|
if (this.els.holdTitle) {
|
|
2328
|
-
this.els.holdTitle.textContent = `${securedCount}
|
|
2430
|
+
this.els.holdTitle.textContent = `${securedCount} secured`;
|
|
2329
2431
|
}
|
|
2330
2432
|
if (this.els.holdCopy) {
|
|
2331
|
-
this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected
|
|
2433
|
+
this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected` : "Checkout timer running";
|
|
2434
|
+
}
|
|
2435
|
+
const change = this.els.holdChange;
|
|
2436
|
+
if (change) {
|
|
2437
|
+
change.disabled = this.releasingHold;
|
|
2438
|
+
change.textContent = this.releasingHold ? "Releasing\u2026" : "Change";
|
|
2332
2439
|
}
|
|
2333
2440
|
}
|
|
2334
2441
|
if (count !== previousCount) this.animateOnce(this.els.count, "sl-value-pop", 380);
|
|
@@ -2380,7 +2487,30 @@ var SeatPicker = class _SeatPicker {
|
|
|
2380
2487
|
if (button?.isConnected) button.disabled = false;
|
|
2381
2488
|
}
|
|
2382
2489
|
}
|
|
2490
|
+
async handleChangeSeats() {
|
|
2491
|
+
if (!this.hold || this.releasingHold) return;
|
|
2492
|
+
this.releasingHold = true;
|
|
2493
|
+
const button = this.els.holdChange;
|
|
2494
|
+
if (button) {
|
|
2495
|
+
button.disabled = true;
|
|
2496
|
+
button.textContent = "Releasing\u2026";
|
|
2497
|
+
}
|
|
2498
|
+
try {
|
|
2499
|
+
await this.release();
|
|
2500
|
+
if (!this.hold) this.toast("Held tickets released. Choose your new seats.", "success");
|
|
2501
|
+
} finally {
|
|
2502
|
+
this.releasingHold = false;
|
|
2503
|
+
if (button?.isConnected) {
|
|
2504
|
+
button.disabled = false;
|
|
2505
|
+
button.textContent = "Change";
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2383
2509
|
async handleCta() {
|
|
2510
|
+
if (this.totalTicketCount() > this.maxTickets) {
|
|
2511
|
+
this.toast(`Remove tickets until your order has ${this.maxTickets} or fewer.`, "warning");
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2384
2514
|
const committed = this.committedSelection();
|
|
2385
2515
|
if (this.hold && !committed.some((s) => !(this.hold.items ?? []).some((i) => i.label === s.label))) {
|
|
2386
2516
|
const seats = this.hold.seats ?? committed;
|
|
@@ -2418,7 +2548,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
2418
2548
|
this.opts.onCheckout?.(hold, hold.seats ?? chosenSeats, this.buildHandoff(hold));
|
|
2419
2549
|
} catch (err) {
|
|
2420
2550
|
this.opts.onError?.(err);
|
|
2421
|
-
|
|
2551
|
+
const problem = err;
|
|
2552
|
+
const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
|
|
2553
|
+
const message = problem.reason === "event_closed" ? "Seat sales have closed for this event." : labels.length ? `${labels.join(", ")} ${labels.length === 1 ? "is" : "are"} no longer available. Choose another ${labels.length === 1 ? "seat" : "group"}.` : "One or more seats were just taken. Please pick again.";
|
|
2554
|
+
this.toast(message, "error");
|
|
2422
2555
|
this.setCtaPhase("idle");
|
|
2423
2556
|
} finally {
|
|
2424
2557
|
this.holdingLabels.clear();
|
|
@@ -2541,15 +2674,28 @@ var SeatPicker = class _SeatPicker {
|
|
|
2541
2674
|
hold ? this.buildHandoff(hold) : null
|
|
2542
2675
|
);
|
|
2543
2676
|
}
|
|
2544
|
-
toast(msg, tone = "neutral") {
|
|
2677
|
+
toast(msg, tone = "neutral", action) {
|
|
2545
2678
|
const el = this.els.toast;
|
|
2546
2679
|
if (!el) return;
|
|
2547
|
-
el.
|
|
2680
|
+
el.replaceChildren();
|
|
2681
|
+
const copy = document.createElement("span");
|
|
2682
|
+
copy.textContent = msg;
|
|
2683
|
+
el.appendChild(copy);
|
|
2684
|
+
el.classList.toggle("has-action", !!action);
|
|
2685
|
+
if (action) {
|
|
2686
|
+
const button = document.createElement("button");
|
|
2687
|
+
button.type = "button";
|
|
2688
|
+
button.className = "sl-toast-action";
|
|
2689
|
+
button.textContent = action.label;
|
|
2690
|
+
button.addEventListener("click", action.onClick, { once: true });
|
|
2691
|
+
el.appendChild(button);
|
|
2692
|
+
}
|
|
2548
2693
|
el.dataset.tone = tone;
|
|
2549
2694
|
el.classList.add("on");
|
|
2550
2695
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
2551
2696
|
this.toastTimer = setTimeout(() => {
|
|
2552
2697
|
el.classList.remove("on");
|
|
2698
|
+
el.classList.remove("has-action");
|
|
2553
2699
|
el.dataset.tone = "neutral";
|
|
2554
2700
|
}, 4200);
|
|
2555
2701
|
}
|
|
@@ -2599,7 +2745,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
2599
2745
|
}
|
|
2600
2746
|
async bestAvailable(qty, categoryKey) {
|
|
2601
2747
|
if (this.bestAvailableBusy) return null;
|
|
2748
|
+
qty = Math.max(1, Math.min(this.maxTickets, Math.floor(qty)));
|
|
2602
2749
|
if (this.confirmSeat) this.cancelConfirm();
|
|
2750
|
+
this.bestAvailableConfirm = false;
|
|
2603
2751
|
this.bestAvailableBusy = true;
|
|
2604
2752
|
const button = this.els.tray?.querySelector(".sl-ba-go");
|
|
2605
2753
|
if (button) {
|
|
@@ -2612,6 +2760,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2612
2760
|
this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
|
|
2613
2761
|
this.handedOff = false;
|
|
2614
2762
|
this.bookedShown = false;
|
|
2763
|
+
this.gaQty.clear();
|
|
2615
2764
|
this.startHoldTimer(h.expiresAt);
|
|
2616
2765
|
this.flashHeldSeats(this.hold);
|
|
2617
2766
|
this.syncTray();
|
|
@@ -2621,14 +2770,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
2621
2770
|
return null;
|
|
2622
2771
|
} catch (err) {
|
|
2623
2772
|
this.opts.onError?.(err);
|
|
2624
|
-
|
|
2773
|
+
const reason = err?.reason;
|
|
2774
|
+
const message = reason === "not_enough_together" ? `We couldn't find ${qty} seats together. Try fewer seats or another ticket type.` : reason === "sold_out" ? "That ticket type is sold out. Try another ticket type." : reason === "event_closed" ? "Seat sales have closed for this event." : "Those seats are no longer available. Try another quantity or ticket type.";
|
|
2775
|
+
this.toast(message, "error");
|
|
2625
2776
|
return null;
|
|
2626
2777
|
} finally {
|
|
2627
2778
|
this.bestAvailableBusy = false;
|
|
2628
|
-
|
|
2629
|
-
button.disabled = false;
|
|
2630
|
-
button.textContent = "Best available";
|
|
2631
|
-
}
|
|
2779
|
+
this.syncTray();
|
|
2632
2780
|
}
|
|
2633
2781
|
}
|
|
2634
2782
|
async release() {
|