@seatlayer/js 0.14.0 → 0.15.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/README.md +6 -2
- package/dist/index.cjs +326 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -3
- package/dist/index.d.ts +60 -3
- package/dist/index.js +326 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -84,6 +84,12 @@ var PubApi = class {
|
|
|
84
84
|
body: { qty, ...categoryKey ? { categoryKey } : {} }
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
+
resume(key, holdId) {
|
|
88
|
+
return request(this.base, `/pub/events/${encodeURIComponent(key)}/hold/resume`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: { holdId }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
87
93
|
release(key, labels, holdId) {
|
|
88
94
|
return request(this.base, `/pub/events/${encodeURIComponent(key)}/release`, {
|
|
89
95
|
method: "POST",
|
|
@@ -140,6 +146,7 @@ var SeatingChart = class {
|
|
|
140
146
|
currency: options.currency,
|
|
141
147
|
onSelectionChange: (seats) => this.opts.onSelectionChange?.(seats),
|
|
142
148
|
onHold: (h) => this.opts.onHold?.({ holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items }),
|
|
149
|
+
onHoldRestored: (h) => this.opts.onHoldRestored?.({ holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items }),
|
|
143
150
|
onHoldExpired: () => this.opts.onHoldExpired?.(),
|
|
144
151
|
onGAClick: (areaId) => {
|
|
145
152
|
const area = this.controller.getGAAreas().find((candidate) => candidate.id === areaId);
|
|
@@ -243,6 +250,21 @@ var SeatingChart = class {
|
|
|
243
250
|
return null;
|
|
244
251
|
}
|
|
245
252
|
}
|
|
253
|
+
/** Restore an active hold by its opaque id without extending its expiry. */
|
|
254
|
+
async resumeHold(holdId) {
|
|
255
|
+
try {
|
|
256
|
+
const h = await this.controller.resumeHold(holdId);
|
|
257
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
this.opts.onError?.(err);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/** Current active hold known to this chart, if any. */
|
|
264
|
+
getCurrentHold() {
|
|
265
|
+
const h = this.controller.currentHold();
|
|
266
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
267
|
+
}
|
|
246
268
|
getGAAreas() {
|
|
247
269
|
return this.controller.getGAAreas();
|
|
248
270
|
}
|
|
@@ -311,6 +333,10 @@ var SeatingChart = class {
|
|
|
311
333
|
async release() {
|
|
312
334
|
await this.controller.release();
|
|
313
335
|
}
|
|
336
|
+
/** Release selected labels from the current hold while keeping the remainder. */
|
|
337
|
+
async releaseLabels(labels) {
|
|
338
|
+
return this.controller.releaseLabels(labels);
|
|
339
|
+
}
|
|
314
340
|
/** Tear everything down: close the socket, stop timers, drop the canvas. */
|
|
315
341
|
destroy() {
|
|
316
342
|
if (this.hostEl && this.onTipMove) this.hostEl.removeEventListener("mousemove", this.onTipMove);
|
|
@@ -490,6 +516,15 @@ var CSS = `
|
|
|
490
516
|
.sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side > :not(.sl-sheet-head){display:none}
|
|
491
517
|
.sl-picker[data-layout="narrow"] .sl-tray{flex:none}
|
|
492
518
|
.sl-picker[data-layout="narrow"] .sl-foot{position:sticky;bottom:0;background:var(--sl-bg)}
|
|
519
|
+
.sl-picker[data-layout="narrow"] .sl-sheet-head{order:0}
|
|
520
|
+
.sl-picker[data-layout="narrow"] .sl-seats-sec{order:1}
|
|
521
|
+
.sl-picker[data-layout="narrow"] .sl-tray{order:2}
|
|
522
|
+
.sl-picker[data-layout="narrow"] .sl-filtersec{order:3}
|
|
523
|
+
.sl-picker[data-layout="narrow"] .sl-filters{order:4}
|
|
524
|
+
.sl-picker[data-layout="narrow"] .sl-prices-sec{order:5}
|
|
525
|
+
.sl-picker[data-layout="narrow"] .sl-pricef{order:6}
|
|
526
|
+
.sl-picker[data-layout="narrow"] .sl-prices{order:7}
|
|
527
|
+
.sl-picker[data-layout="narrow"] .sl-foot{order:8}
|
|
493
528
|
/* touch chrome: pinch-zoom exists \u2014 hide +/\u2212 on the sheet layout (keep fit) */
|
|
494
529
|
.sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zin"],
|
|
495
530
|
.sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zout"]{display:none}
|
|
@@ -536,6 +571,11 @@ var CSS = `
|
|
|
536
571
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
537
572
|
.sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
|
|
538
573
|
.sl-price-amt{font-weight:800;font-variant-numeric:tabular-nums}
|
|
574
|
+
.sl-status-key{display:flex;gap:12px;flex-wrap:wrap;padding:8px 16px 12px;border-bottom:1px solid var(--sl-line);color:var(--sl-muted);font-size:11px}
|
|
575
|
+
.sl-status-item{display:inline-flex;align-items:center;gap:6px}
|
|
576
|
+
.sl-status-icon{width:17px;height:17px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;
|
|
577
|
+
color:#fff;background:#6b7280;font-size:9px;font-weight:900;line-height:1}
|
|
578
|
+
.sl-status-icon.sold{background:#374151;font-size:14px}
|
|
539
579
|
|
|
540
580
|
/* tray */
|
|
541
581
|
.sl-tray{flex:1;padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}
|
|
@@ -686,29 +726,47 @@ var CSS = `
|
|
|
686
726
|
.sl-chip-f:hover{color:var(--sl-text)}
|
|
687
727
|
.sl-chip-f.on{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
688
728
|
|
|
689
|
-
/* confirm
|
|
690
|
-
.
|
|
691
|
-
|
|
729
|
+
/* confirm card: a candidate is not in the tray until Select. Map gestures and
|
|
730
|
+
floating chrome pause while the card owns focus, keeping the camera stable. */
|
|
731
|
+
.sl-picker[data-confirming="true"] .sl-map-host>:not(.sl-confirm){pointer-events:none}
|
|
732
|
+
.sl-picker[data-confirming="true"] .sl-anchor{pointer-events:none;opacity:.28;transition:opacity .16s}
|
|
733
|
+
.sl-picker[data-confirming="true"] .sl-side{pointer-events:none;opacity:.58;transition:opacity .16s}
|
|
734
|
+
.sl-confirm{position:absolute;z-index:10;width:276px;max-width:calc(100% - 24px);overflow:hidden;pointer-events:auto;
|
|
735
|
+
background:var(--sl-surface);border:1px solid color-mix(in srgb,var(--sl-line) 70%,var(--sl-text));
|
|
736
|
+
border-radius:15px;box-shadow:0 24px 64px -18px rgba(0,0,0,.72);transform:translate(-50%,calc(-100% - 16px));
|
|
692
737
|
animation:slConfirmIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
693
|
-
.sl-confirm-
|
|
694
|
-
.sl-confirm-
|
|
695
|
-
.sl-confirm-
|
|
738
|
+
.sl-confirm[data-placement="below"]{transform:translate(-50%,16px);animation:slConfirmBelowIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
739
|
+
.sl-confirm-grid{display:grid;grid-template-columns:1.2fr .8fr .8fr;border-bottom:1px solid var(--sl-line)}
|
|
740
|
+
.sl-confirm-field{min-width:0;padding:12px 11px 10px;border-right:1px solid var(--sl-line)}
|
|
741
|
+
.sl-confirm-field:last-child{border-right:0;text-align:center}
|
|
742
|
+
.sl-confirm-field:nth-child(2){text-align:center}
|
|
743
|
+
.sl-confirm-key{display:block;font-size:8.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--sl-muted);font-weight:800}
|
|
744
|
+
.sl-confirm-value{display:block;margin-top:4px;color:var(--sl-text);font-size:17px;line-height:1.1;font-weight:850;
|
|
745
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
746
|
+
.sl-confirm-cat{display:flex;align-items:center;gap:8px;padding:10px 12px;background:color-mix(in srgb,var(--sl-cat) 76%,var(--sl-surface))}
|
|
747
|
+
.sl-confirm-cat .sl-dot{border:2px solid rgba(255,255,255,.78);width:11px;height:11px}
|
|
748
|
+
.sl-confirm-cat-name{font-size:13.5px;font-weight:800;color:#fff;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
749
|
+
.sl-confirm-price{font-size:17px;font-weight:850;color:#fff;font-variant-numeric:tabular-nums}
|
|
750
|
+
.sl-confirm-body{padding:11px 12px 12px}
|
|
696
751
|
.sl-confirm-row{display:flex;gap:8px;margin-top:10px}
|
|
697
|
-
.sl-confirm-row button{flex:1;padding:
|
|
698
|
-
.sl-confirm-add{background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
699
|
-
.sl-confirm-
|
|
752
|
+
.sl-confirm-row button{flex:1;min-height:44px;padding:9px 12px;border-radius:9px;font-weight:800;font-size:13px}
|
|
753
|
+
.sl-confirm-add{background:var(--sl-accent)!important;color:var(--sl-accent-ink)!important;display:flex;align-items:center;justify-content:center;gap:7px}
|
|
754
|
+
.sl-confirm-add svg{width:16px;height:16px;stroke:currentColor;stroke-width:2.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
755
|
+
.sl-confirm-cancel{background:color-mix(in srgb,var(--sl-line) 44%,transparent)!important;border:1px solid var(--sl-line)!important;color:var(--sl-muted)!important}
|
|
700
756
|
.sl-confirm-cancel:hover{color:var(--sl-text)}
|
|
757
|
+
.sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
|
|
758
|
+
transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
701
759
|
|
|
702
760
|
/* best-available row */
|
|
703
|
-
.sl-ba{display:
|
|
761
|
+
.sl-ba{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;padding:9px 11px;border:1px solid var(--sl-line);border-radius:var(--sl-r-sm)}
|
|
704
762
|
.sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
|
|
705
|
-
font:inherit;font-size:12px;padding:
|
|
763
|
+
font:inherit;font-size:12px;padding:7px 8px;min-width:0;width:100%;max-width:none}
|
|
706
764
|
.sl-ba-qty{display:flex;align-items:center;gap:7px}
|
|
707
765
|
.sl-ba-qty button{width:24px;height:24px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
708
766
|
font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center}
|
|
709
767
|
.sl-ba-qty span{min-width:14px;text-align:center;font-weight:800}
|
|
710
|
-
.sl-ba-go{
|
|
711
|
-
transition:border-color .15s,opacity .15s;display:flex;align-items:center;gap:6px}
|
|
768
|
+
.sl-ba-go{grid-column:1/-1;width:100%;min-height:38px;padding:7px 12px;border-radius:9px;border:1px solid var(--sl-line);font-weight:800;font-size:12px;
|
|
769
|
+
transition:border-color .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:6px}
|
|
712
770
|
.sl-ba-go:hover{border-color:var(--sl-muted)}
|
|
713
771
|
.sl-ba-go:disabled{opacity:.62;cursor:wait}
|
|
714
772
|
|
|
@@ -831,6 +889,8 @@ var CSS = `
|
|
|
831
889
|
@keyframes slCheckDraw{to{stroke-dashoffset:0}}
|
|
832
890
|
@keyframes slCopyRise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
833
891
|
@keyframes slConfirmIn{from{opacity:0;transform:translate(-50%,calc(-100% - 8px)) scale(.96)}to{opacity:1;transform:translate(-50%,calc(-100% - 14px)) scale(1)}}
|
|
892
|
+
@keyframes slConfirmBelowIn{from{opacity:0;transform:translate(-50%,8px) scale(.96)}to{opacity:1;transform:translate(-50%,16px) scale(1)}}
|
|
893
|
+
@keyframes slConfirmMobileIn{from{opacity:0;transform:translate(-50%,10px) scale(.97)}to{opacity:1;transform:translate(-50%,0) scale(1)}}
|
|
834
894
|
|
|
835
895
|
@media(prefers-reduced-motion:reduce){
|
|
836
896
|
.sl-picker *,.sl-modal-scrim *{animation-duration:.001ms!important;animation-iteration-count:1!important;
|
|
@@ -927,6 +987,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
927
987
|
/** Stable item keys prevent tray chips re-animating on unrelated realtime syncs. */
|
|
928
988
|
this.lastTrayKeys = /* @__PURE__ */ new Set();
|
|
929
989
|
this.bestAvailableBusy = false;
|
|
990
|
+
this.releasingLabels = /* @__PURE__ */ new Set();
|
|
991
|
+
/** Selected labels awaiting the hold response; their own realtime echo can arrive first. */
|
|
992
|
+
this.holdingLabels = /* @__PURE__ */ new Set();
|
|
930
993
|
this.ctaPhase = "idle";
|
|
931
994
|
// narrow-layout chrome that docks into the sheet's Filters row on mobile
|
|
932
995
|
this.a11yChipsEl = null;
|
|
@@ -940,8 +1003,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
940
1003
|
if (!options || typeof options !== "object") throw new Error("seatmap: options object is required");
|
|
941
1004
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
942
1005
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
943
|
-
this.opts = options;
|
|
944
|
-
this.
|
|
1006
|
+
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
1007
|
+
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
1008
|
+
this.api = new PubApi(this.apiBase);
|
|
945
1009
|
this.controller = new import_core2.PickerController({
|
|
946
1010
|
transport: this.api,
|
|
947
1011
|
eventKey: options.event,
|
|
@@ -951,7 +1015,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
951
1015
|
colorblindSafe: options.colorblindSafe,
|
|
952
1016
|
onSelectionChange: () => {
|
|
953
1017
|
this.syncTray();
|
|
954
|
-
if (this.
|
|
1018
|
+
if (this.committedSelection().length) this.collapseSectionCard();
|
|
955
1019
|
},
|
|
956
1020
|
onStatusChange: () => {
|
|
957
1021
|
this.syncPrices();
|
|
@@ -961,6 +1025,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
961
1025
|
},
|
|
962
1026
|
onHoldExpired: () => {
|
|
963
1027
|
this.hold = null;
|
|
1028
|
+
this.forgetHold();
|
|
964
1029
|
this.handedOff = false;
|
|
965
1030
|
this.bookedShown = false;
|
|
966
1031
|
this.ctaPhase = "idle";
|
|
@@ -968,13 +1033,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
968
1033
|
this.gaQty.clear();
|
|
969
1034
|
this.toast((0, import_core2.t)("picker.holdExpired", void 0) || "Your hold expired \u2014 seats released. Pick again.", "warning");
|
|
970
1035
|
this.syncTray();
|
|
1036
|
+
this.emitHoldChange();
|
|
971
1037
|
this.opts.onHoldExpired?.();
|
|
972
1038
|
},
|
|
973
|
-
confirmSelection:
|
|
1039
|
+
confirmSelection: this.opts.confirmSelection,
|
|
974
1040
|
onSelect: (seat) => {
|
|
975
1041
|
this.flashPickedSeat(seat.id);
|
|
976
1042
|
if (this.opts.confirmSelection) this.showConfirm(seat);
|
|
977
1043
|
},
|
|
1044
|
+
onDeselect: (seat) => {
|
|
1045
|
+
if (this.confirmSeat?.id === seat.id) this.dismissConfirm();
|
|
1046
|
+
},
|
|
978
1047
|
onViewChange: () => {
|
|
979
1048
|
this.reanchorConfirm();
|
|
980
1049
|
this.syncRung();
|
|
@@ -1033,7 +1102,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
1033
1102
|
if (e.target === scrim) close();
|
|
1034
1103
|
});
|
|
1035
1104
|
picker.escHandler = (e) => {
|
|
1036
|
-
if (e.key
|
|
1105
|
+
if (e.key !== "Escape") return;
|
|
1106
|
+
if (picker.confirmSeat) {
|
|
1107
|
+
e.preventDefault();
|
|
1108
|
+
picker.cancelConfirm();
|
|
1109
|
+
} else {
|
|
1110
|
+
close();
|
|
1111
|
+
}
|
|
1037
1112
|
};
|
|
1038
1113
|
document.addEventListener("keydown", picker.escHandler);
|
|
1039
1114
|
await picker.render();
|
|
@@ -1050,8 +1125,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
1050
1125
|
const mount = resolveContainer3(this.opts.container);
|
|
1051
1126
|
const root = document.createElement("div");
|
|
1052
1127
|
root.className = "sl-picker";
|
|
1128
|
+
root.tabIndex = -1;
|
|
1053
1129
|
this.root = root;
|
|
1054
1130
|
mount.appendChild(root);
|
|
1131
|
+
root.addEventListener("keydown", (e) => {
|
|
1132
|
+
if (e.key !== "Escape" || !this.confirmSeat) return;
|
|
1133
|
+
e.preventDefault();
|
|
1134
|
+
e.stopPropagation();
|
|
1135
|
+
this.cancelConfirm();
|
|
1136
|
+
});
|
|
1055
1137
|
Object.entries(resolveTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
1056
1138
|
root.innerHTML = `
|
|
1057
1139
|
<div class="sl-head">
|
|
@@ -1090,9 +1172,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
1090
1172
|
</div>
|
|
1091
1173
|
<div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
|
|
1092
1174
|
<div class="sl-filters" data-ref="filters"></div>
|
|
1093
|
-
<div class="sl-sec" data-ref="pricesSec">Prices</div>
|
|
1175
|
+
<div class="sl-sec sl-prices-sec" data-ref="pricesSec">Prices</div>
|
|
1094
1176
|
<div class="sl-prices" data-ref="prices"></div>
|
|
1095
|
-
<div class="sl-sec">Your seats</div>
|
|
1177
|
+
<div class="sl-sec sl-seats-sec">Your seats</div>
|
|
1096
1178
|
<div class="sl-tray" data-ref="tray"></div>
|
|
1097
1179
|
<div class="sl-foot">
|
|
1098
1180
|
<div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
|
|
@@ -1250,6 +1332,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
1250
1332
|
this.buildExtendPrompt();
|
|
1251
1333
|
this.buildBookedOverlay();
|
|
1252
1334
|
this.dockLayoutChrome();
|
|
1335
|
+
await this.restoreRememberedHold();
|
|
1336
|
+
if (this.destroyed) return this;
|
|
1253
1337
|
this.syncPrices();
|
|
1254
1338
|
this.syncTray();
|
|
1255
1339
|
return this;
|
|
@@ -1363,10 +1447,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
1363
1447
|
});
|
|
1364
1448
|
}
|
|
1365
1449
|
/** Update only the action affordance; selection callbacks must not refire. */
|
|
1450
|
+
committedSelection() {
|
|
1451
|
+
const candidateId = this.confirmSeat?.id;
|
|
1452
|
+
return this.controller.getSelection().filter((seat) => seat.id !== candidateId);
|
|
1453
|
+
}
|
|
1366
1454
|
pendingSelectionCount() {
|
|
1367
1455
|
const heldItems = this.hold?.items ?? [];
|
|
1368
1456
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
1369
|
-
const pendingSeats = this.
|
|
1457
|
+
const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
|
|
1370
1458
|
const heldGA = /* @__PURE__ */ new Map();
|
|
1371
1459
|
for (const item of heldItems.filter((candidate) => candidate.objectType === "ga")) {
|
|
1372
1460
|
heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
|
|
@@ -1380,6 +1468,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
1380
1468
|
syncCta(count = this.lastTrayCount, pending = this.pendingSelectionCount()) {
|
|
1381
1469
|
const cta = this.els.cta;
|
|
1382
1470
|
if (!cta) return;
|
|
1471
|
+
if (this.confirmSeat) {
|
|
1472
|
+
cta.disabled = true;
|
|
1473
|
+
cta.textContent = "Confirm or cancel this seat";
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1383
1476
|
if (this.ctaPhase === "holding") {
|
|
1384
1477
|
cta.disabled = true;
|
|
1385
1478
|
cta.innerHTML = '<span class="sl-cta-spin" aria-hidden="true"></span>Securing seats\u2026';
|
|
@@ -1404,6 +1497,68 @@ var SeatPicker = class _SeatPicker {
|
|
|
1404
1497
|
}, 1100);
|
|
1405
1498
|
}
|
|
1406
1499
|
}
|
|
1500
|
+
/** Session-scoped capability key: isolated by API origin and event. */
|
|
1501
|
+
holdStorageKey() {
|
|
1502
|
+
return `@seatlayer/hold/v1/${encodeURIComponent(this.apiBase)}/${encodeURIComponent(this.opts.event)}`;
|
|
1503
|
+
}
|
|
1504
|
+
rememberedHoldId() {
|
|
1505
|
+
if (this.opts.initialHoldId) return this.opts.initialHoldId;
|
|
1506
|
+
if (this.opts.restoreHold === false || typeof window === "undefined") return null;
|
|
1507
|
+
try {
|
|
1508
|
+
return window.sessionStorage.getItem(this.holdStorageKey());
|
|
1509
|
+
} catch {
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
rememberHold(hold) {
|
|
1514
|
+
if (this.opts.restoreHold === false || typeof window === "undefined") return;
|
|
1515
|
+
try {
|
|
1516
|
+
window.sessionStorage.setItem(this.holdStorageKey(), hold.holdId);
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
forgetHold() {
|
|
1521
|
+
if (typeof window === "undefined") return;
|
|
1522
|
+
try {
|
|
1523
|
+
window.sessionStorage.removeItem(this.holdStorageKey());
|
|
1524
|
+
} catch {
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
async resumeHoldFromServer(holdId, automatic) {
|
|
1528
|
+
try {
|
|
1529
|
+
const h = await this.controller.resumeHold(holdId);
|
|
1530
|
+
if (!h) return null;
|
|
1531
|
+
const restored = {
|
|
1532
|
+
holdId: h.holdId,
|
|
1533
|
+
expiresAt: h.expiresAt,
|
|
1534
|
+
seats: h.seats,
|
|
1535
|
+
items: h.items
|
|
1536
|
+
};
|
|
1537
|
+
this.hold = restored;
|
|
1538
|
+
this.handedOff = true;
|
|
1539
|
+
this.bookedShown = false;
|
|
1540
|
+
this.ctaPhase = "idle";
|
|
1541
|
+
this.startHoldTimer(restored.expiresAt);
|
|
1542
|
+
this.rememberHold(restored);
|
|
1543
|
+
this.syncTray();
|
|
1544
|
+
this.emitHoldChange();
|
|
1545
|
+
this.opts.onHoldRestored?.(restored, restored.seats ?? [], this.buildHandoff(restored));
|
|
1546
|
+
if (automatic) this.toast("Your held tickets have been restored.", "success");
|
|
1547
|
+
return restored;
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
const status = error?.status;
|
|
1550
|
+
if (status === 404 || status === 409) {
|
|
1551
|
+
this.forgetHold();
|
|
1552
|
+
} else {
|
|
1553
|
+
this.opts.onError?.(error);
|
|
1554
|
+
}
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async restoreRememberedHold() {
|
|
1559
|
+
const holdId = this.rememberedHoldId();
|
|
1560
|
+
if (holdId) await this.resumeHoldFromServer(holdId, true);
|
|
1561
|
+
}
|
|
1407
1562
|
/** Section-bearing objects on the active floor (single-floor → doc.objects). */
|
|
1408
1563
|
activeFloorObjects() {
|
|
1409
1564
|
const doc = this.controller.doc;
|
|
@@ -1825,36 +1980,78 @@ var SeatPicker = class _SeatPicker {
|
|
|
1825
1980
|
const price = cat?.tiers?.length ? cat.tiers[0].price : cat?.price;
|
|
1826
1981
|
this.srEl.textContent = `Seat ${seat.label}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
|
|
1827
1982
|
}
|
|
1828
|
-
// ----
|
|
1983
|
+
// ---- seat candidate confirmation ------------------------------------------
|
|
1829
1984
|
showConfirm(seat) {
|
|
1830
|
-
this.
|
|
1985
|
+
const previousId = this.confirmSeat?.id;
|
|
1986
|
+
this.confirmEl?.remove();
|
|
1987
|
+
this.confirmEl = null;
|
|
1988
|
+
this.confirmSeat = seat;
|
|
1989
|
+
this.root?.setAttribute("data-confirming", "true");
|
|
1990
|
+
this.controller.setSelectionFocus(seat.id);
|
|
1991
|
+
if (previousId && previousId !== seat.id) this.controller.deselect([previousId]);
|
|
1831
1992
|
if (this.tipEl) this.tipEl.style.display = "none";
|
|
1993
|
+
const details = this.controller.seatDetails(seat.id);
|
|
1832
1994
|
const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
|
|
1833
|
-
const price = cat?.tiers?.length ? cat.tiers[0].price : cat?.price;
|
|
1995
|
+
const price = details?.price ?? (cat?.tiers?.length ? cat.tiers[0].price : cat?.price);
|
|
1996
|
+
const safe = (value) => String(value ?? "\u2014").replace(/[&<>"]/g, (char) => ({
|
|
1997
|
+
"&": "&",
|
|
1998
|
+
"<": "<",
|
|
1999
|
+
">": ">",
|
|
2000
|
+
'"': """
|
|
2001
|
+
})[char]);
|
|
1834
2002
|
const el = document.createElement("div");
|
|
1835
2003
|
el.className = "sl-confirm";
|
|
1836
|
-
el.
|
|
2004
|
+
el.setAttribute("role", "dialog");
|
|
2005
|
+
el.setAttribute("aria-modal", "true");
|
|
2006
|
+
el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
|
|
2007
|
+
el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
|
|
2008
|
+
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">Row</span><span class="sl-confirm-value">${safe(details?.rowLabel)}</span></div><div class="sl-confirm-field"><span class="sl-confirm-key">Seat</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? 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.seatViewEnabled() ? `<button type="button" class="sl-confirm-view"><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>${(0, import_core2.t)("picker.open360")}</button>` : "") + `<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>`;
|
|
1837
2009
|
this.els.map.appendChild(el);
|
|
1838
2010
|
this.confirmEl = el;
|
|
1839
|
-
this.confirmSeat = seat;
|
|
1840
2011
|
this.reanchorConfirm();
|
|
1841
2012
|
el.querySelector(".sl-confirm-view")?.addEventListener("click", () => this.openSeatView(seat));
|
|
1842
|
-
el.querySelector(".sl-confirm-add").addEventListener("click", () => this.
|
|
1843
|
-
el.querySelector(".sl-confirm-cancel").addEventListener("click", () =>
|
|
1844
|
-
|
|
1845
|
-
this.closeConfirm();
|
|
1846
|
-
});
|
|
2013
|
+
el.querySelector(".sl-confirm-add").addEventListener("click", () => this.commitConfirm());
|
|
2014
|
+
el.querySelector(".sl-confirm-cancel").addEventListener("click", () => this.cancelConfirm());
|
|
2015
|
+
requestAnimationFrame(() => el.querySelector(".sl-confirm-add")?.focus());
|
|
1847
2016
|
}
|
|
1848
2017
|
reanchorConfirm() {
|
|
1849
2018
|
if (!this.confirmEl || !this.confirmSeat) return;
|
|
1850
2019
|
const p = this.controller.worldToScreen({ x: this.confirmSeat.x, y: this.confirmSeat.y });
|
|
1851
|
-
this.
|
|
1852
|
-
this.
|
|
1853
|
-
|
|
1854
|
-
|
|
2020
|
+
if (this.root?.dataset.layout === "narrow") return;
|
|
2021
|
+
const mapWidth = this.els.map.clientWidth;
|
|
2022
|
+
const mapHeight = this.els.map.clientHeight;
|
|
2023
|
+
const cardWidth = this.confirmEl.offsetWidth || 276;
|
|
2024
|
+
const cardHeight = this.confirmEl.offsetHeight || 230;
|
|
2025
|
+
const half = cardWidth / 2 + 12;
|
|
2026
|
+
const x = Math.max(half, Math.min(mapWidth - half, p.x));
|
|
2027
|
+
const belowFits = p.y + cardHeight + 24 <= mapHeight;
|
|
2028
|
+
const placeBelow = p.y < cardHeight + 24 && belowFits;
|
|
2029
|
+
this.confirmEl.dataset.placement = placeBelow ? "below" : "above";
|
|
2030
|
+
this.confirmEl.style.left = `${x}px`;
|
|
2031
|
+
this.confirmEl.style.top = `${Math.max(8, Math.min(mapHeight - 8, p.y))}px`;
|
|
2032
|
+
}
|
|
2033
|
+
dismissConfirm() {
|
|
1855
2034
|
this.confirmEl?.remove();
|
|
1856
2035
|
this.confirmEl = null;
|
|
1857
2036
|
this.confirmSeat = null;
|
|
2037
|
+
this.root?.removeAttribute("data-confirming");
|
|
2038
|
+
this.controller.setSelectionFocus(null);
|
|
2039
|
+
}
|
|
2040
|
+
commitConfirm() {
|
|
2041
|
+
if (!this.confirmSeat) return;
|
|
2042
|
+
this.dismissConfirm();
|
|
2043
|
+
this.collapseSectionCard();
|
|
2044
|
+
this.syncTray();
|
|
2045
|
+
}
|
|
2046
|
+
cancelConfirm() {
|
|
2047
|
+
const seat = this.confirmSeat;
|
|
2048
|
+
if (!seat) return;
|
|
2049
|
+
this.controller.deselect([seat.id]);
|
|
2050
|
+
if (this.confirmSeat) this.dismissConfirm();
|
|
2051
|
+
this.root?.focus({ preventScroll: true });
|
|
2052
|
+
}
|
|
2053
|
+
closeConfirm() {
|
|
2054
|
+
this.dismissConfirm();
|
|
1858
2055
|
}
|
|
1859
2056
|
// ---- 360° view-from-seat modal --------------------------------------------
|
|
1860
2057
|
seatViewEnabled() {
|
|
@@ -1877,7 +2074,6 @@ var SeatPicker = class _SeatPicker {
|
|
|
1877
2074
|
openSeatView(seat) {
|
|
1878
2075
|
if (!this.root || !this.seatViewEnabled()) return;
|
|
1879
2076
|
this.closeSeatView();
|
|
1880
|
-
this.closeConfirm();
|
|
1881
2077
|
const doc = this.controller.doc;
|
|
1882
2078
|
const activeId = this.controller.getActiveFloorId();
|
|
1883
2079
|
const focal = doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
@@ -1988,7 +2184,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1988
2184
|
const price = c.tiers?.length ? c.tiers[0].price : c.price;
|
|
1989
2185
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
1990
2186
|
return `<div class="sl-price-row${dim ? " sl-dim" : ""}" data-cat="${c.key}"><span class="sl-dot" style="background:${c.color}"></span><span class="sl-price-label">${c.label}</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
1991
|
-
}).join("")
|
|
2187
|
+
}).join("") + `<div class="sl-status-key" aria-label="Seat status legend"><span class="sl-status-item"><i class="sl-status-icon">H</i>Held by another buyer</span><span class="sl-status-item"><i class="sl-status-icon sold">\xD7</i>Sold</span></div>`;
|
|
1992
2188
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
1993
2189
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
1994
2190
|
row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
|
|
@@ -1996,7 +2192,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
1996
2192
|
}
|
|
1997
2193
|
/** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
|
|
1998
2194
|
evictTakenSelections() {
|
|
1999
|
-
const ownLabels = new Set(
|
|
2195
|
+
const ownLabels = /* @__PURE__ */ new Set([
|
|
2196
|
+
...this.controller.currentHold()?.labels ?? [],
|
|
2197
|
+
...this.holdingLabels
|
|
2198
|
+
]);
|
|
2000
2199
|
const gone = this.controller.getSelection().filter((s) => !ownLabels.has(s.label) && (this.controller.getStatus(s.id) ?? "free") !== "free");
|
|
2001
2200
|
if (!gone.length) return;
|
|
2002
2201
|
this.controller.deselect(gone.map((s) => s.id));
|
|
@@ -2004,7 +2203,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2004
2203
|
}
|
|
2005
2204
|
syncTray() {
|
|
2006
2205
|
if (!this.els.tray) return;
|
|
2007
|
-
const seats = this.
|
|
2206
|
+
const seats = this.committedSelection();
|
|
2008
2207
|
const gaAreas = this.controller.getGAAreas();
|
|
2009
2208
|
const heldItems = this.hold?.items ?? [];
|
|
2010
2209
|
const parts = [];
|
|
@@ -2021,7 +2220,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2021
2220
|
const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
|
|
2022
2221
|
const canView2 = this.seatViewEnabled() && item.objectType !== "ga" && !!this.controller.seatByLabel(item.label);
|
|
2023
2222
|
parts.push(
|
|
2024
|
-
`<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}"><b>${item.label}</b><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span><span class="sl-chip-state">Held</span><span class="amt">${this.money(item.unitPrice * (item.quantity ?? 1))}</span>` + (canView2 ? `<button type="button" class="view" data-view-label="${item.label}" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: item.label })}"><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>` : "") +
|
|
2223
|
+
`<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"><b>${item.label}</b><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span><span class="sl-chip-state">Held by you</span><span class="amt">${this.money(item.unitPrice * (item.quantity ?? 1))}</span>` + (canView2 ? `<button type="button" class="view" data-view-label="${item.label}" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: item.label })}"><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>` : "") + `<button type="button" class="rm" aria-label="Remove held ticket ${item.label}"><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></div>`
|
|
2025
2224
|
);
|
|
2026
2225
|
}
|
|
2027
2226
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
@@ -2065,6 +2264,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
2065
2264
|
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
2066
2265
|
btn.addEventListener("click", () => {
|
|
2067
2266
|
const chip = btn.closest(".sl-chip");
|
|
2267
|
+
if (chip.dataset.held) {
|
|
2268
|
+
void this.removeHeldLabel(decodeURIComponent(chip.dataset.held), chip);
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2068
2271
|
const id = chip.dataset.seat;
|
|
2069
2272
|
if (this.reducedMotion()) {
|
|
2070
2273
|
this.controller.deselect([id]);
|
|
@@ -2126,26 +2329,59 @@ var SeatPicker = class _SeatPicker {
|
|
|
2126
2329
|
this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<span class="sub">\xB7 Best available</span>`;
|
|
2127
2330
|
}
|
|
2128
2331
|
}
|
|
2129
|
-
if (this.root?.dataset.layout === "narrow" && count > 0 && this.lastTrayCount === 0) {
|
|
2130
|
-
this.root.dataset.sheet = "open";
|
|
2131
|
-
}
|
|
2132
2332
|
this.lastTrayCount = count;
|
|
2133
2333
|
this.lastTrayTotal = total;
|
|
2134
2334
|
this.opts.onSelectionChange?.(seats);
|
|
2135
2335
|
}
|
|
2336
|
+
async removeHeldLabel(label, chip) {
|
|
2337
|
+
if (!label || this.releasingLabels.has(label)) return false;
|
|
2338
|
+
this.releasingLabels.add(label);
|
|
2339
|
+
chip?.setAttribute("aria-busy", "true");
|
|
2340
|
+
const button = chip?.querySelector(".rm");
|
|
2341
|
+
if (button) button.disabled = true;
|
|
2342
|
+
try {
|
|
2343
|
+
const preserveAcrossNavigation = this.handedOff;
|
|
2344
|
+
const released = await this.controller.releaseLabels([label]);
|
|
2345
|
+
if (!released) {
|
|
2346
|
+
this.toast(`Couldn't remove ${label}. Your hold is unchanged.`, "error");
|
|
2347
|
+
return false;
|
|
2348
|
+
}
|
|
2349
|
+
const remaining = this.controller.currentHold();
|
|
2350
|
+
this.hold = remaining ? { holdId: remaining.holdId, expiresAt: remaining.expiresAt, seats: remaining.seats, items: remaining.items } : null;
|
|
2351
|
+
this.handedOff = !!this.hold && preserveAcrossNavigation;
|
|
2352
|
+
this.bookedShown = false;
|
|
2353
|
+
this.ctaPhase = "idle";
|
|
2354
|
+
if (this.hold) {
|
|
2355
|
+
this.startHoldTimer(this.hold.expiresAt);
|
|
2356
|
+
} else {
|
|
2357
|
+
this.stopHoldTimer();
|
|
2358
|
+
this.forgetHold();
|
|
2359
|
+
}
|
|
2360
|
+
this.syncTray();
|
|
2361
|
+
this.emitHoldChange();
|
|
2362
|
+
this.toast(`${label} removed from your hold.`, "success");
|
|
2363
|
+
return true;
|
|
2364
|
+
} finally {
|
|
2365
|
+
this.releasingLabels.delete(label);
|
|
2366
|
+
chip?.removeAttribute("aria-busy");
|
|
2367
|
+
if (button?.isConnected) button.disabled = false;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2136
2370
|
async handleCta() {
|
|
2137
|
-
|
|
2138
|
-
|
|
2371
|
+
const committed = this.committedSelection();
|
|
2372
|
+
if (this.hold && !committed.some((s) => !(this.hold.items ?? []).some((i) => i.label === s.label))) {
|
|
2373
|
+
const seats = this.hold.seats ?? committed;
|
|
2139
2374
|
this.handedOff = true;
|
|
2140
2375
|
this.setCtaPhase("checkout");
|
|
2141
2376
|
this.opts.onCheckout?.(this.hold, seats, this.buildHandoff(this.hold));
|
|
2142
2377
|
return;
|
|
2143
2378
|
}
|
|
2379
|
+
this.holdingLabels = new Set(committed.map((seat) => seat.label));
|
|
2144
2380
|
this.setCtaPhase("holding");
|
|
2145
2381
|
try {
|
|
2146
2382
|
let hold = null;
|
|
2147
2383
|
const gaEntries = [...this.gaQty.entries()].filter(([, q]) => q > 0);
|
|
2148
|
-
const chosenSeats = this.
|
|
2384
|
+
const chosenSeats = this.committedSelection();
|
|
2149
2385
|
if (chosenSeats.length) {
|
|
2150
2386
|
const h = await this.controller.hold(void 0, this.opts.holdTtlMs);
|
|
2151
2387
|
hold = h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
@@ -2165,12 +2401,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
2165
2401
|
this.startHoldTimer(hold.expiresAt);
|
|
2166
2402
|
this.flashHeldSeats(hold);
|
|
2167
2403
|
this.setCtaPhase("checkout");
|
|
2404
|
+
this.emitHoldChange();
|
|
2168
2405
|
this.opts.onCheckout?.(hold, hold.seats ?? chosenSeats, this.buildHandoff(hold));
|
|
2169
2406
|
} catch (err) {
|
|
2170
2407
|
this.opts.onError?.(err);
|
|
2171
2408
|
this.toast("One or more seats were just taken. Please pick again.", "error");
|
|
2172
2409
|
this.setCtaPhase("idle");
|
|
2173
2410
|
} finally {
|
|
2411
|
+
this.holdingLabels.clear();
|
|
2174
2412
|
if (this.ctaPhase === "holding") this.ctaPhase = "idle";
|
|
2175
2413
|
this.syncTray();
|
|
2176
2414
|
}
|
|
@@ -2178,6 +2416,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2178
2416
|
startHoldTimer(expiresAt) {
|
|
2179
2417
|
this.stopHoldTimer();
|
|
2180
2418
|
this.holdExpiresAt = expiresAt;
|
|
2419
|
+
if (this.hold) this.rememberHold(this.hold);
|
|
2181
2420
|
const pill = this.els.hold;
|
|
2182
2421
|
pill.innerHTML = '<span class="sl-hold-dot" aria-hidden="true"></span><span>Held</span><span class="sl-hold-time" data-ref="holdTime"></span>';
|
|
2183
2422
|
const time = pill.querySelector('[data-ref="holdTime"]');
|
|
@@ -2224,6 +2463,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
2224
2463
|
this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
|
|
2225
2464
|
this.holdExpiresAt = h.expiresAt;
|
|
2226
2465
|
this.extendEl?.classList.remove("on");
|
|
2466
|
+
this.rememberHold(this.hold);
|
|
2467
|
+
this.emitHoldChange();
|
|
2227
2468
|
this.toast("More time added \u2014 your seats are still held.", "success");
|
|
2228
2469
|
} else {
|
|
2229
2470
|
this.toast("Couldn't add more time \u2014 please head to checkout now.", "warning");
|
|
@@ -2254,6 +2495,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2254
2495
|
this.bookedShown = true;
|
|
2255
2496
|
const handoff = this.buildHandoff(this.hold);
|
|
2256
2497
|
this.stopHoldTimer();
|
|
2498
|
+
this.forgetHold();
|
|
2257
2499
|
const n = handoff.lineItems.reduce((sum, i) => sum + i.quantity, 0);
|
|
2258
2500
|
if (this.els.bookedSub) {
|
|
2259
2501
|
this.els.bookedSub.innerHTML = `<span class="sl-booked-seats">${n} ${n === 1 ? "ticket" : "tickets"}</span> confirmed. A confirmation is on its way.`;
|
|
@@ -2278,6 +2520,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
2278
2520
|
const total = lineItems.reduce((sum, i) => sum + i.unitPrice * i.quantity, 0);
|
|
2279
2521
|
return { holdId: hold.holdId, expiresAt: hold.expiresAt, currency, lineItems, total };
|
|
2280
2522
|
}
|
|
2523
|
+
emitHoldChange() {
|
|
2524
|
+
const hold = this.hold;
|
|
2525
|
+
this.opts.onHoldChange?.(
|
|
2526
|
+
hold,
|
|
2527
|
+
hold?.seats ?? [],
|
|
2528
|
+
hold ? this.buildHandoff(hold) : null
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2281
2531
|
toast(msg, tone = "neutral") {
|
|
2282
2532
|
const el = this.els.toast;
|
|
2283
2533
|
if (!el) return;
|
|
@@ -2309,16 +2559,34 @@ var SeatPicker = class _SeatPicker {
|
|
|
2309
2559
|
return;
|
|
2310
2560
|
}
|
|
2311
2561
|
const statusLine = details.status === "free" ? "" : `<div style="margin-top:5px;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;font-weight:700">${details.status === "held" ? (0, import_core2.t)("map.statusHeld") : (0, import_core2.t)("map.statusTaken")}</div>`;
|
|
2312
|
-
|
|
2562
|
+
const location = [
|
|
2563
|
+
details.sectionLabel ? `Section ${details.sectionLabel}` : "",
|
|
2564
|
+
details.rowLabel ? `Row ${details.rowLabel}` : "",
|
|
2565
|
+
details.seatNumber ? `Seat ${details.seatNumber}` : details.label
|
|
2566
|
+
].filter(Boolean).join(" \xB7 ");
|
|
2567
|
+
this.tipEl.innerHTML = `<div style="font-weight:800;font-size:13px">${location}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span style="opacity:.75">${details.categoryLabel}</span><span style="margin-left:auto;font-weight:800">${this.money(details.price)}</span></div>` + statusLine;
|
|
2313
2568
|
this.tipEl.style.display = "block";
|
|
2314
2569
|
this.placeTooltip();
|
|
2315
2570
|
}
|
|
2316
2571
|
// ---- public conveniences ----------------------------------------------------
|
|
2317
2572
|
getSelection() {
|
|
2318
|
-
return this.
|
|
2573
|
+
return this.committedSelection();
|
|
2574
|
+
}
|
|
2575
|
+
/** Current active/restored hold reflected in the tray. */
|
|
2576
|
+
getCurrentHold() {
|
|
2577
|
+
return this.hold;
|
|
2578
|
+
}
|
|
2579
|
+
/** Explicit host-driven hold restore (automatic session restore is on by default). */
|
|
2580
|
+
async resumeHold(holdId) {
|
|
2581
|
+
return this.resumeHoldFromServer(holdId, false);
|
|
2582
|
+
}
|
|
2583
|
+
/** Remove one server-held ticket while keeping the rest of the hold active. */
|
|
2584
|
+
async removeHeldTicket(label) {
|
|
2585
|
+
return this.removeHeldLabel(label);
|
|
2319
2586
|
}
|
|
2320
2587
|
async bestAvailable(qty, categoryKey) {
|
|
2321
2588
|
if (this.bestAvailableBusy) return null;
|
|
2589
|
+
if (this.confirmSeat) this.cancelConfirm();
|
|
2322
2590
|
this.bestAvailableBusy = true;
|
|
2323
2591
|
const button = this.els.tray?.querySelector(".sl-ba-go");
|
|
2324
2592
|
if (button) {
|
|
@@ -2334,6 +2602,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2334
2602
|
this.startHoldTimer(h.expiresAt);
|
|
2335
2603
|
this.flashHeldSeats(this.hold);
|
|
2336
2604
|
this.syncTray();
|
|
2605
|
+
this.emitHoldChange();
|
|
2337
2606
|
return this.hold;
|
|
2338
2607
|
}
|
|
2339
2608
|
return null;
|
|
@@ -2352,8 +2621,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
2352
2621
|
async release() {
|
|
2353
2622
|
const tracked = this.hold;
|
|
2354
2623
|
const controllerHold = this.controller.currentHold();
|
|
2624
|
+
let released = true;
|
|
2355
2625
|
if (controllerHold) {
|
|
2356
|
-
await this.controller.release();
|
|
2626
|
+
released = await this.controller.release();
|
|
2357
2627
|
} else if (tracked) {
|
|
2358
2628
|
const labels = [.../* @__PURE__ */ new Set([
|
|
2359
2629
|
...(tracked.items ?? []).map((item) => item.label),
|
|
@@ -2364,16 +2634,23 @@ var SeatPicker = class _SeatPicker {
|
|
|
2364
2634
|
await this.api.release(this.opts.event, labels, tracked.holdId);
|
|
2365
2635
|
} catch (error) {
|
|
2366
2636
|
this.opts.onError?.(error);
|
|
2637
|
+
released = false;
|
|
2367
2638
|
}
|
|
2368
2639
|
}
|
|
2369
2640
|
}
|
|
2641
|
+
if (!released) {
|
|
2642
|
+
this.toast("Couldn't release your tickets. Your hold is unchanged.", "error");
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2370
2645
|
this.hold = null;
|
|
2646
|
+
this.forgetHold();
|
|
2371
2647
|
this.handedOff = false;
|
|
2372
2648
|
this.bookedShown = false;
|
|
2373
2649
|
this.ctaPhase = "idle";
|
|
2374
2650
|
this.stopHoldTimer();
|
|
2375
2651
|
this.gaQty.clear();
|
|
2376
2652
|
this.syncTray();
|
|
2653
|
+
this.emitHoldChange();
|
|
2377
2654
|
}
|
|
2378
2655
|
destroy() {
|
|
2379
2656
|
this.destroyed = true;
|