@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.js
CHANGED
|
@@ -52,6 +52,12 @@ var PubApi = class {
|
|
|
52
52
|
body: { qty, ...categoryKey ? { categoryKey } : {} }
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
+
resume(key, holdId) {
|
|
56
|
+
return request(this.base, `/pub/events/${encodeURIComponent(key)}/hold/resume`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
body: { holdId }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
55
61
|
release(key, labels, holdId) {
|
|
56
62
|
return request(this.base, `/pub/events/${encodeURIComponent(key)}/release`, {
|
|
57
63
|
method: "POST",
|
|
@@ -108,6 +114,7 @@ var SeatingChart = class {
|
|
|
108
114
|
currency: options.currency,
|
|
109
115
|
onSelectionChange: (seats) => this.opts.onSelectionChange?.(seats),
|
|
110
116
|
onHold: (h) => this.opts.onHold?.({ holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items }),
|
|
117
|
+
onHoldRestored: (h) => this.opts.onHoldRestored?.({ holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items }),
|
|
111
118
|
onHoldExpired: () => this.opts.onHoldExpired?.(),
|
|
112
119
|
onGAClick: (areaId) => {
|
|
113
120
|
const area = this.controller.getGAAreas().find((candidate) => candidate.id === areaId);
|
|
@@ -211,6 +218,21 @@ var SeatingChart = class {
|
|
|
211
218
|
return null;
|
|
212
219
|
}
|
|
213
220
|
}
|
|
221
|
+
/** Restore an active hold by its opaque id without extending its expiry. */
|
|
222
|
+
async resumeHold(holdId) {
|
|
223
|
+
try {
|
|
224
|
+
const h = await this.controller.resumeHold(holdId);
|
|
225
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
226
|
+
} catch (err) {
|
|
227
|
+
this.opts.onError?.(err);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** Current active hold known to this chart, if any. */
|
|
232
|
+
getCurrentHold() {
|
|
233
|
+
const h = this.controller.currentHold();
|
|
234
|
+
return h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
235
|
+
}
|
|
214
236
|
getGAAreas() {
|
|
215
237
|
return this.controller.getGAAreas();
|
|
216
238
|
}
|
|
@@ -279,6 +301,10 @@ var SeatingChart = class {
|
|
|
279
301
|
async release() {
|
|
280
302
|
await this.controller.release();
|
|
281
303
|
}
|
|
304
|
+
/** Release selected labels from the current hold while keeping the remainder. */
|
|
305
|
+
async releaseLabels(labels) {
|
|
306
|
+
return this.controller.releaseLabels(labels);
|
|
307
|
+
}
|
|
282
308
|
/** Tear everything down: close the socket, stop timers, drop the canvas. */
|
|
283
309
|
destroy() {
|
|
284
310
|
if (this.hostEl && this.onTipMove) this.hostEl.removeEventListener("mousemove", this.onTipMove);
|
|
@@ -466,6 +492,15 @@ var CSS = `
|
|
|
466
492
|
.sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side > :not(.sl-sheet-head){display:none}
|
|
467
493
|
.sl-picker[data-layout="narrow"] .sl-tray{flex:none}
|
|
468
494
|
.sl-picker[data-layout="narrow"] .sl-foot{position:sticky;bottom:0;background:var(--sl-bg)}
|
|
495
|
+
.sl-picker[data-layout="narrow"] .sl-sheet-head{order:0}
|
|
496
|
+
.sl-picker[data-layout="narrow"] .sl-seats-sec{order:1}
|
|
497
|
+
.sl-picker[data-layout="narrow"] .sl-tray{order:2}
|
|
498
|
+
.sl-picker[data-layout="narrow"] .sl-filtersec{order:3}
|
|
499
|
+
.sl-picker[data-layout="narrow"] .sl-filters{order:4}
|
|
500
|
+
.sl-picker[data-layout="narrow"] .sl-prices-sec{order:5}
|
|
501
|
+
.sl-picker[data-layout="narrow"] .sl-pricef{order:6}
|
|
502
|
+
.sl-picker[data-layout="narrow"] .sl-prices{order:7}
|
|
503
|
+
.sl-picker[data-layout="narrow"] .sl-foot{order:8}
|
|
469
504
|
/* touch chrome: pinch-zoom exists \u2014 hide +/\u2212 on the sheet layout (keep fit) */
|
|
470
505
|
.sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zin"],
|
|
471
506
|
.sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zout"]{display:none}
|
|
@@ -512,6 +547,11 @@ var CSS = `
|
|
|
512
547
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
513
548
|
.sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
|
|
514
549
|
.sl-price-amt{font-weight:800;font-variant-numeric:tabular-nums}
|
|
550
|
+
.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}
|
|
551
|
+
.sl-status-item{display:inline-flex;align-items:center;gap:6px}
|
|
552
|
+
.sl-status-icon{width:17px;height:17px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;
|
|
553
|
+
color:#fff;background:#6b7280;font-size:9px;font-weight:900;line-height:1}
|
|
554
|
+
.sl-status-icon.sold{background:#374151;font-size:14px}
|
|
515
555
|
|
|
516
556
|
/* tray */
|
|
517
557
|
.sl-tray{flex:1;padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}
|
|
@@ -662,29 +702,47 @@ var CSS = `
|
|
|
662
702
|
.sl-chip-f:hover{color:var(--sl-text)}
|
|
663
703
|
.sl-chip-f.on{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
664
704
|
|
|
665
|
-
/* confirm
|
|
666
|
-
.
|
|
667
|
-
|
|
705
|
+
/* confirm card: a candidate is not in the tray until Select. Map gestures and
|
|
706
|
+
floating chrome pause while the card owns focus, keeping the camera stable. */
|
|
707
|
+
.sl-picker[data-confirming="true"] .sl-map-host>:not(.sl-confirm){pointer-events:none}
|
|
708
|
+
.sl-picker[data-confirming="true"] .sl-anchor{pointer-events:none;opacity:.28;transition:opacity .16s}
|
|
709
|
+
.sl-picker[data-confirming="true"] .sl-side{pointer-events:none;opacity:.58;transition:opacity .16s}
|
|
710
|
+
.sl-confirm{position:absolute;z-index:10;width:276px;max-width:calc(100% - 24px);overflow:hidden;pointer-events:auto;
|
|
711
|
+
background:var(--sl-surface);border:1px solid color-mix(in srgb,var(--sl-line) 70%,var(--sl-text));
|
|
712
|
+
border-radius:15px;box-shadow:0 24px 64px -18px rgba(0,0,0,.72);transform:translate(-50%,calc(-100% - 16px));
|
|
668
713
|
animation:slConfirmIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
669
|
-
.sl-confirm-
|
|
670
|
-
.sl-confirm-
|
|
671
|
-
.sl-confirm-
|
|
714
|
+
.sl-confirm[data-placement="below"]{transform:translate(-50%,16px);animation:slConfirmBelowIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
715
|
+
.sl-confirm-grid{display:grid;grid-template-columns:1.2fr .8fr .8fr;border-bottom:1px solid var(--sl-line)}
|
|
716
|
+
.sl-confirm-field{min-width:0;padding:12px 11px 10px;border-right:1px solid var(--sl-line)}
|
|
717
|
+
.sl-confirm-field:last-child{border-right:0;text-align:center}
|
|
718
|
+
.sl-confirm-field:nth-child(2){text-align:center}
|
|
719
|
+
.sl-confirm-key{display:block;font-size:8.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--sl-muted);font-weight:800}
|
|
720
|
+
.sl-confirm-value{display:block;margin-top:4px;color:var(--sl-text);font-size:17px;line-height:1.1;font-weight:850;
|
|
721
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
722
|
+
.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))}
|
|
723
|
+
.sl-confirm-cat .sl-dot{border:2px solid rgba(255,255,255,.78);width:11px;height:11px}
|
|
724
|
+
.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}
|
|
725
|
+
.sl-confirm-price{font-size:17px;font-weight:850;color:#fff;font-variant-numeric:tabular-nums}
|
|
726
|
+
.sl-confirm-body{padding:11px 12px 12px}
|
|
672
727
|
.sl-confirm-row{display:flex;gap:8px;margin-top:10px}
|
|
673
|
-
.sl-confirm-row button{flex:1;padding:
|
|
674
|
-
.sl-confirm-add{background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
675
|
-
.sl-confirm-
|
|
728
|
+
.sl-confirm-row button{flex:1;min-height:44px;padding:9px 12px;border-radius:9px;font-weight:800;font-size:13px}
|
|
729
|
+
.sl-confirm-add{background:var(--sl-accent)!important;color:var(--sl-accent-ink)!important;display:flex;align-items:center;justify-content:center;gap:7px}
|
|
730
|
+
.sl-confirm-add svg{width:16px;height:16px;stroke:currentColor;stroke-width:2.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
731
|
+
.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}
|
|
676
732
|
.sl-confirm-cancel:hover{color:var(--sl-text)}
|
|
733
|
+
.sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
|
|
734
|
+
transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
677
735
|
|
|
678
736
|
/* best-available row */
|
|
679
|
-
.sl-ba{display:
|
|
737
|
+
.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)}
|
|
680
738
|
.sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
|
|
681
|
-
font:inherit;font-size:12px;padding:
|
|
739
|
+
font:inherit;font-size:12px;padding:7px 8px;min-width:0;width:100%;max-width:none}
|
|
682
740
|
.sl-ba-qty{display:flex;align-items:center;gap:7px}
|
|
683
741
|
.sl-ba-qty button{width:24px;height:24px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
684
742
|
font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center}
|
|
685
743
|
.sl-ba-qty span{min-width:14px;text-align:center;font-weight:800}
|
|
686
|
-
.sl-ba-go{
|
|
687
|
-
transition:border-color .15s,opacity .15s;display:flex;align-items:center;gap:6px}
|
|
744
|
+
.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;
|
|
745
|
+
transition:border-color .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:6px}
|
|
688
746
|
.sl-ba-go:hover{border-color:var(--sl-muted)}
|
|
689
747
|
.sl-ba-go:disabled{opacity:.62;cursor:wait}
|
|
690
748
|
|
|
@@ -807,6 +865,8 @@ var CSS = `
|
|
|
807
865
|
@keyframes slCheckDraw{to{stroke-dashoffset:0}}
|
|
808
866
|
@keyframes slCopyRise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
809
867
|
@keyframes slConfirmIn{from{opacity:0;transform:translate(-50%,calc(-100% - 8px)) scale(.96)}to{opacity:1;transform:translate(-50%,calc(-100% - 14px)) scale(1)}}
|
|
868
|
+
@keyframes slConfirmBelowIn{from{opacity:0;transform:translate(-50%,8px) scale(.96)}to{opacity:1;transform:translate(-50%,16px) scale(1)}}
|
|
869
|
+
@keyframes slConfirmMobileIn{from{opacity:0;transform:translate(-50%,10px) scale(.97)}to{opacity:1;transform:translate(-50%,0) scale(1)}}
|
|
810
870
|
|
|
811
871
|
@media(prefers-reduced-motion:reduce){
|
|
812
872
|
.sl-picker *,.sl-modal-scrim *{animation-duration:.001ms!important;animation-iteration-count:1!important;
|
|
@@ -903,6 +963,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
903
963
|
/** Stable item keys prevent tray chips re-animating on unrelated realtime syncs. */
|
|
904
964
|
this.lastTrayKeys = /* @__PURE__ */ new Set();
|
|
905
965
|
this.bestAvailableBusy = false;
|
|
966
|
+
this.releasingLabels = /* @__PURE__ */ new Set();
|
|
967
|
+
/** Selected labels awaiting the hold response; their own realtime echo can arrive first. */
|
|
968
|
+
this.holdingLabels = /* @__PURE__ */ new Set();
|
|
906
969
|
this.ctaPhase = "idle";
|
|
907
970
|
// narrow-layout chrome that docks into the sheet's Filters row on mobile
|
|
908
971
|
this.a11yChipsEl = null;
|
|
@@ -916,8 +979,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
916
979
|
if (!options || typeof options !== "object") throw new Error("seatmap: options object is required");
|
|
917
980
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
918
981
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
919
|
-
this.opts = options;
|
|
920
|
-
this.
|
|
982
|
+
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
983
|
+
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
984
|
+
this.api = new PubApi(this.apiBase);
|
|
921
985
|
this.controller = new PickerController2({
|
|
922
986
|
transport: this.api,
|
|
923
987
|
eventKey: options.event,
|
|
@@ -927,7 +991,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
927
991
|
colorblindSafe: options.colorblindSafe,
|
|
928
992
|
onSelectionChange: () => {
|
|
929
993
|
this.syncTray();
|
|
930
|
-
if (this.
|
|
994
|
+
if (this.committedSelection().length) this.collapseSectionCard();
|
|
931
995
|
},
|
|
932
996
|
onStatusChange: () => {
|
|
933
997
|
this.syncPrices();
|
|
@@ -937,6 +1001,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
937
1001
|
},
|
|
938
1002
|
onHoldExpired: () => {
|
|
939
1003
|
this.hold = null;
|
|
1004
|
+
this.forgetHold();
|
|
940
1005
|
this.handedOff = false;
|
|
941
1006
|
this.bookedShown = false;
|
|
942
1007
|
this.ctaPhase = "idle";
|
|
@@ -944,13 +1009,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
944
1009
|
this.gaQty.clear();
|
|
945
1010
|
this.toast(t2("picker.holdExpired", void 0) || "Your hold expired \u2014 seats released. Pick again.", "warning");
|
|
946
1011
|
this.syncTray();
|
|
1012
|
+
this.emitHoldChange();
|
|
947
1013
|
this.opts.onHoldExpired?.();
|
|
948
1014
|
},
|
|
949
|
-
confirmSelection:
|
|
1015
|
+
confirmSelection: this.opts.confirmSelection,
|
|
950
1016
|
onSelect: (seat) => {
|
|
951
1017
|
this.flashPickedSeat(seat.id);
|
|
952
1018
|
if (this.opts.confirmSelection) this.showConfirm(seat);
|
|
953
1019
|
},
|
|
1020
|
+
onDeselect: (seat) => {
|
|
1021
|
+
if (this.confirmSeat?.id === seat.id) this.dismissConfirm();
|
|
1022
|
+
},
|
|
954
1023
|
onViewChange: () => {
|
|
955
1024
|
this.reanchorConfirm();
|
|
956
1025
|
this.syncRung();
|
|
@@ -1009,7 +1078,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
1009
1078
|
if (e.target === scrim) close();
|
|
1010
1079
|
});
|
|
1011
1080
|
picker.escHandler = (e) => {
|
|
1012
|
-
if (e.key
|
|
1081
|
+
if (e.key !== "Escape") return;
|
|
1082
|
+
if (picker.confirmSeat) {
|
|
1083
|
+
e.preventDefault();
|
|
1084
|
+
picker.cancelConfirm();
|
|
1085
|
+
} else {
|
|
1086
|
+
close();
|
|
1087
|
+
}
|
|
1013
1088
|
};
|
|
1014
1089
|
document.addEventListener("keydown", picker.escHandler);
|
|
1015
1090
|
await picker.render();
|
|
@@ -1026,8 +1101,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
1026
1101
|
const mount = resolveContainer3(this.opts.container);
|
|
1027
1102
|
const root = document.createElement("div");
|
|
1028
1103
|
root.className = "sl-picker";
|
|
1104
|
+
root.tabIndex = -1;
|
|
1029
1105
|
this.root = root;
|
|
1030
1106
|
mount.appendChild(root);
|
|
1107
|
+
root.addEventListener("keydown", (e) => {
|
|
1108
|
+
if (e.key !== "Escape" || !this.confirmSeat) return;
|
|
1109
|
+
e.preventDefault();
|
|
1110
|
+
e.stopPropagation();
|
|
1111
|
+
this.cancelConfirm();
|
|
1112
|
+
});
|
|
1031
1113
|
Object.entries(resolveTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
1032
1114
|
root.innerHTML = `
|
|
1033
1115
|
<div class="sl-head">
|
|
@@ -1066,9 +1148,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
1066
1148
|
</div>
|
|
1067
1149
|
<div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
|
|
1068
1150
|
<div class="sl-filters" data-ref="filters"></div>
|
|
1069
|
-
<div class="sl-sec" data-ref="pricesSec">Prices</div>
|
|
1151
|
+
<div class="sl-sec sl-prices-sec" data-ref="pricesSec">Prices</div>
|
|
1070
1152
|
<div class="sl-prices" data-ref="prices"></div>
|
|
1071
|
-
<div class="sl-sec">Your seats</div>
|
|
1153
|
+
<div class="sl-sec sl-seats-sec">Your seats</div>
|
|
1072
1154
|
<div class="sl-tray" data-ref="tray"></div>
|
|
1073
1155
|
<div class="sl-foot">
|
|
1074
1156
|
<div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
|
|
@@ -1226,6 +1308,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
1226
1308
|
this.buildExtendPrompt();
|
|
1227
1309
|
this.buildBookedOverlay();
|
|
1228
1310
|
this.dockLayoutChrome();
|
|
1311
|
+
await this.restoreRememberedHold();
|
|
1312
|
+
if (this.destroyed) return this;
|
|
1229
1313
|
this.syncPrices();
|
|
1230
1314
|
this.syncTray();
|
|
1231
1315
|
return this;
|
|
@@ -1339,10 +1423,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
1339
1423
|
});
|
|
1340
1424
|
}
|
|
1341
1425
|
/** Update only the action affordance; selection callbacks must not refire. */
|
|
1426
|
+
committedSelection() {
|
|
1427
|
+
const candidateId = this.confirmSeat?.id;
|
|
1428
|
+
return this.controller.getSelection().filter((seat) => seat.id !== candidateId);
|
|
1429
|
+
}
|
|
1342
1430
|
pendingSelectionCount() {
|
|
1343
1431
|
const heldItems = this.hold?.items ?? [];
|
|
1344
1432
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
1345
|
-
const pendingSeats = this.
|
|
1433
|
+
const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
|
|
1346
1434
|
const heldGA = /* @__PURE__ */ new Map();
|
|
1347
1435
|
for (const item of heldItems.filter((candidate) => candidate.objectType === "ga")) {
|
|
1348
1436
|
heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
|
|
@@ -1356,6 +1444,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
1356
1444
|
syncCta(count = this.lastTrayCount, pending = this.pendingSelectionCount()) {
|
|
1357
1445
|
const cta = this.els.cta;
|
|
1358
1446
|
if (!cta) return;
|
|
1447
|
+
if (this.confirmSeat) {
|
|
1448
|
+
cta.disabled = true;
|
|
1449
|
+
cta.textContent = "Confirm or cancel this seat";
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1359
1452
|
if (this.ctaPhase === "holding") {
|
|
1360
1453
|
cta.disabled = true;
|
|
1361
1454
|
cta.innerHTML = '<span class="sl-cta-spin" aria-hidden="true"></span>Securing seats\u2026';
|
|
@@ -1380,6 +1473,68 @@ var SeatPicker = class _SeatPicker {
|
|
|
1380
1473
|
}, 1100);
|
|
1381
1474
|
}
|
|
1382
1475
|
}
|
|
1476
|
+
/** Session-scoped capability key: isolated by API origin and event. */
|
|
1477
|
+
holdStorageKey() {
|
|
1478
|
+
return `@seatlayer/hold/v1/${encodeURIComponent(this.apiBase)}/${encodeURIComponent(this.opts.event)}`;
|
|
1479
|
+
}
|
|
1480
|
+
rememberedHoldId() {
|
|
1481
|
+
if (this.opts.initialHoldId) return this.opts.initialHoldId;
|
|
1482
|
+
if (this.opts.restoreHold === false || typeof window === "undefined") return null;
|
|
1483
|
+
try {
|
|
1484
|
+
return window.sessionStorage.getItem(this.holdStorageKey());
|
|
1485
|
+
} catch {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
rememberHold(hold) {
|
|
1490
|
+
if (this.opts.restoreHold === false || typeof window === "undefined") return;
|
|
1491
|
+
try {
|
|
1492
|
+
window.sessionStorage.setItem(this.holdStorageKey(), hold.holdId);
|
|
1493
|
+
} catch {
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
forgetHold() {
|
|
1497
|
+
if (typeof window === "undefined") return;
|
|
1498
|
+
try {
|
|
1499
|
+
window.sessionStorage.removeItem(this.holdStorageKey());
|
|
1500
|
+
} catch {
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
async resumeHoldFromServer(holdId, automatic) {
|
|
1504
|
+
try {
|
|
1505
|
+
const h = await this.controller.resumeHold(holdId);
|
|
1506
|
+
if (!h) return null;
|
|
1507
|
+
const restored = {
|
|
1508
|
+
holdId: h.holdId,
|
|
1509
|
+
expiresAt: h.expiresAt,
|
|
1510
|
+
seats: h.seats,
|
|
1511
|
+
items: h.items
|
|
1512
|
+
};
|
|
1513
|
+
this.hold = restored;
|
|
1514
|
+
this.handedOff = true;
|
|
1515
|
+
this.bookedShown = false;
|
|
1516
|
+
this.ctaPhase = "idle";
|
|
1517
|
+
this.startHoldTimer(restored.expiresAt);
|
|
1518
|
+
this.rememberHold(restored);
|
|
1519
|
+
this.syncTray();
|
|
1520
|
+
this.emitHoldChange();
|
|
1521
|
+
this.opts.onHoldRestored?.(restored, restored.seats ?? [], this.buildHandoff(restored));
|
|
1522
|
+
if (automatic) this.toast("Your held tickets have been restored.", "success");
|
|
1523
|
+
return restored;
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
const status = error?.status;
|
|
1526
|
+
if (status === 404 || status === 409) {
|
|
1527
|
+
this.forgetHold();
|
|
1528
|
+
} else {
|
|
1529
|
+
this.opts.onError?.(error);
|
|
1530
|
+
}
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
async restoreRememberedHold() {
|
|
1535
|
+
const holdId = this.rememberedHoldId();
|
|
1536
|
+
if (holdId) await this.resumeHoldFromServer(holdId, true);
|
|
1537
|
+
}
|
|
1383
1538
|
/** Section-bearing objects on the active floor (single-floor → doc.objects). */
|
|
1384
1539
|
activeFloorObjects() {
|
|
1385
1540
|
const doc = this.controller.doc;
|
|
@@ -1801,36 +1956,78 @@ var SeatPicker = class _SeatPicker {
|
|
|
1801
1956
|
const price = cat?.tiers?.length ? cat.tiers[0].price : cat?.price;
|
|
1802
1957
|
this.srEl.textContent = `Seat ${seat.label}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
|
|
1803
1958
|
}
|
|
1804
|
-
// ----
|
|
1959
|
+
// ---- seat candidate confirmation ------------------------------------------
|
|
1805
1960
|
showConfirm(seat) {
|
|
1806
|
-
this.
|
|
1961
|
+
const previousId = this.confirmSeat?.id;
|
|
1962
|
+
this.confirmEl?.remove();
|
|
1963
|
+
this.confirmEl = null;
|
|
1964
|
+
this.confirmSeat = seat;
|
|
1965
|
+
this.root?.setAttribute("data-confirming", "true");
|
|
1966
|
+
this.controller.setSelectionFocus(seat.id);
|
|
1967
|
+
if (previousId && previousId !== seat.id) this.controller.deselect([previousId]);
|
|
1807
1968
|
if (this.tipEl) this.tipEl.style.display = "none";
|
|
1969
|
+
const details = this.controller.seatDetails(seat.id);
|
|
1808
1970
|
const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
|
|
1809
|
-
const price = cat?.tiers?.length ? cat.tiers[0].price : cat?.price;
|
|
1971
|
+
const price = details?.price ?? (cat?.tiers?.length ? cat.tiers[0].price : cat?.price);
|
|
1972
|
+
const safe = (value) => String(value ?? "\u2014").replace(/[&<>"]/g, (char) => ({
|
|
1973
|
+
"&": "&",
|
|
1974
|
+
"<": "<",
|
|
1975
|
+
">": ">",
|
|
1976
|
+
'"': """
|
|
1977
|
+
})[char]);
|
|
1810
1978
|
const el = document.createElement("div");
|
|
1811
1979
|
el.className = "sl-confirm";
|
|
1812
|
-
el.
|
|
1980
|
+
el.setAttribute("role", "dialog");
|
|
1981
|
+
el.setAttribute("aria-modal", "true");
|
|
1982
|
+
el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
|
|
1983
|
+
el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
|
|
1984
|
+
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>${t2("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>`;
|
|
1813
1985
|
this.els.map.appendChild(el);
|
|
1814
1986
|
this.confirmEl = el;
|
|
1815
|
-
this.confirmSeat = seat;
|
|
1816
1987
|
this.reanchorConfirm();
|
|
1817
1988
|
el.querySelector(".sl-confirm-view")?.addEventListener("click", () => this.openSeatView(seat));
|
|
1818
|
-
el.querySelector(".sl-confirm-add").addEventListener("click", () => this.
|
|
1819
|
-
el.querySelector(".sl-confirm-cancel").addEventListener("click", () =>
|
|
1820
|
-
|
|
1821
|
-
this.closeConfirm();
|
|
1822
|
-
});
|
|
1989
|
+
el.querySelector(".sl-confirm-add").addEventListener("click", () => this.commitConfirm());
|
|
1990
|
+
el.querySelector(".sl-confirm-cancel").addEventListener("click", () => this.cancelConfirm());
|
|
1991
|
+
requestAnimationFrame(() => el.querySelector(".sl-confirm-add")?.focus());
|
|
1823
1992
|
}
|
|
1824
1993
|
reanchorConfirm() {
|
|
1825
1994
|
if (!this.confirmEl || !this.confirmSeat) return;
|
|
1826
1995
|
const p = this.controller.worldToScreen({ x: this.confirmSeat.x, y: this.confirmSeat.y });
|
|
1827
|
-
this.
|
|
1828
|
-
this.
|
|
1829
|
-
|
|
1830
|
-
|
|
1996
|
+
if (this.root?.dataset.layout === "narrow") return;
|
|
1997
|
+
const mapWidth = this.els.map.clientWidth;
|
|
1998
|
+
const mapHeight = this.els.map.clientHeight;
|
|
1999
|
+
const cardWidth = this.confirmEl.offsetWidth || 276;
|
|
2000
|
+
const cardHeight = this.confirmEl.offsetHeight || 230;
|
|
2001
|
+
const half = cardWidth / 2 + 12;
|
|
2002
|
+
const x = Math.max(half, Math.min(mapWidth - half, p.x));
|
|
2003
|
+
const belowFits = p.y + cardHeight + 24 <= mapHeight;
|
|
2004
|
+
const placeBelow = p.y < cardHeight + 24 && belowFits;
|
|
2005
|
+
this.confirmEl.dataset.placement = placeBelow ? "below" : "above";
|
|
2006
|
+
this.confirmEl.style.left = `${x}px`;
|
|
2007
|
+
this.confirmEl.style.top = `${Math.max(8, Math.min(mapHeight - 8, p.y))}px`;
|
|
2008
|
+
}
|
|
2009
|
+
dismissConfirm() {
|
|
1831
2010
|
this.confirmEl?.remove();
|
|
1832
2011
|
this.confirmEl = null;
|
|
1833
2012
|
this.confirmSeat = null;
|
|
2013
|
+
this.root?.removeAttribute("data-confirming");
|
|
2014
|
+
this.controller.setSelectionFocus(null);
|
|
2015
|
+
}
|
|
2016
|
+
commitConfirm() {
|
|
2017
|
+
if (!this.confirmSeat) return;
|
|
2018
|
+
this.dismissConfirm();
|
|
2019
|
+
this.collapseSectionCard();
|
|
2020
|
+
this.syncTray();
|
|
2021
|
+
}
|
|
2022
|
+
cancelConfirm() {
|
|
2023
|
+
const seat = this.confirmSeat;
|
|
2024
|
+
if (!seat) return;
|
|
2025
|
+
this.controller.deselect([seat.id]);
|
|
2026
|
+
if (this.confirmSeat) this.dismissConfirm();
|
|
2027
|
+
this.root?.focus({ preventScroll: true });
|
|
2028
|
+
}
|
|
2029
|
+
closeConfirm() {
|
|
2030
|
+
this.dismissConfirm();
|
|
1834
2031
|
}
|
|
1835
2032
|
// ---- 360° view-from-seat modal --------------------------------------------
|
|
1836
2033
|
seatViewEnabled() {
|
|
@@ -1853,7 +2050,6 @@ var SeatPicker = class _SeatPicker {
|
|
|
1853
2050
|
openSeatView(seat) {
|
|
1854
2051
|
if (!this.root || !this.seatViewEnabled()) return;
|
|
1855
2052
|
this.closeSeatView();
|
|
1856
|
-
this.closeConfirm();
|
|
1857
2053
|
const doc = this.controller.doc;
|
|
1858
2054
|
const activeId = this.controller.getActiveFloorId();
|
|
1859
2055
|
const focal = doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
@@ -1964,7 +2160,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1964
2160
|
const price = c.tiers?.length ? c.tiers[0].price : c.price;
|
|
1965
2161
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
1966
2162
|
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>`;
|
|
1967
|
-
}).join("")
|
|
2163
|
+
}).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>`;
|
|
1968
2164
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
1969
2165
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
1970
2166
|
row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
|
|
@@ -1972,7 +2168,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
1972
2168
|
}
|
|
1973
2169
|
/** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
|
|
1974
2170
|
evictTakenSelections() {
|
|
1975
|
-
const ownLabels = new Set(
|
|
2171
|
+
const ownLabels = /* @__PURE__ */ new Set([
|
|
2172
|
+
...this.controller.currentHold()?.labels ?? [],
|
|
2173
|
+
...this.holdingLabels
|
|
2174
|
+
]);
|
|
1976
2175
|
const gone = this.controller.getSelection().filter((s) => !ownLabels.has(s.label) && (this.controller.getStatus(s.id) ?? "free") !== "free");
|
|
1977
2176
|
if (!gone.length) return;
|
|
1978
2177
|
this.controller.deselect(gone.map((s) => s.id));
|
|
@@ -1980,7 +2179,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1980
2179
|
}
|
|
1981
2180
|
syncTray() {
|
|
1982
2181
|
if (!this.els.tray) return;
|
|
1983
|
-
const seats = this.
|
|
2182
|
+
const seats = this.committedSelection();
|
|
1984
2183
|
const gaAreas = this.controller.getGAAreas();
|
|
1985
2184
|
const heldItems = this.hold?.items ?? [];
|
|
1986
2185
|
const parts = [];
|
|
@@ -1997,7 +2196,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1997
2196
|
const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
|
|
1998
2197
|
const canView2 = this.seatViewEnabled() && item.objectType !== "ga" && !!this.controller.seatByLabel(item.label);
|
|
1999
2198
|
parts.push(
|
|
2000
|
-
`<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="${t2("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>` : "") +
|
|
2199
|
+
`<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="${t2("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>`
|
|
2001
2200
|
);
|
|
2002
2201
|
}
|
|
2003
2202
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
@@ -2041,6 +2240,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
2041
2240
|
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
2042
2241
|
btn.addEventListener("click", () => {
|
|
2043
2242
|
const chip = btn.closest(".sl-chip");
|
|
2243
|
+
if (chip.dataset.held) {
|
|
2244
|
+
void this.removeHeldLabel(decodeURIComponent(chip.dataset.held), chip);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2044
2247
|
const id = chip.dataset.seat;
|
|
2045
2248
|
if (this.reducedMotion()) {
|
|
2046
2249
|
this.controller.deselect([id]);
|
|
@@ -2102,26 +2305,59 @@ var SeatPicker = class _SeatPicker {
|
|
|
2102
2305
|
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>`;
|
|
2103
2306
|
}
|
|
2104
2307
|
}
|
|
2105
|
-
if (this.root?.dataset.layout === "narrow" && count > 0 && this.lastTrayCount === 0) {
|
|
2106
|
-
this.root.dataset.sheet = "open";
|
|
2107
|
-
}
|
|
2108
2308
|
this.lastTrayCount = count;
|
|
2109
2309
|
this.lastTrayTotal = total;
|
|
2110
2310
|
this.opts.onSelectionChange?.(seats);
|
|
2111
2311
|
}
|
|
2312
|
+
async removeHeldLabel(label, chip) {
|
|
2313
|
+
if (!label || this.releasingLabels.has(label)) return false;
|
|
2314
|
+
this.releasingLabels.add(label);
|
|
2315
|
+
chip?.setAttribute("aria-busy", "true");
|
|
2316
|
+
const button = chip?.querySelector(".rm");
|
|
2317
|
+
if (button) button.disabled = true;
|
|
2318
|
+
try {
|
|
2319
|
+
const preserveAcrossNavigation = this.handedOff;
|
|
2320
|
+
const released = await this.controller.releaseLabels([label]);
|
|
2321
|
+
if (!released) {
|
|
2322
|
+
this.toast(`Couldn't remove ${label}. Your hold is unchanged.`, "error");
|
|
2323
|
+
return false;
|
|
2324
|
+
}
|
|
2325
|
+
const remaining = this.controller.currentHold();
|
|
2326
|
+
this.hold = remaining ? { holdId: remaining.holdId, expiresAt: remaining.expiresAt, seats: remaining.seats, items: remaining.items } : null;
|
|
2327
|
+
this.handedOff = !!this.hold && preserveAcrossNavigation;
|
|
2328
|
+
this.bookedShown = false;
|
|
2329
|
+
this.ctaPhase = "idle";
|
|
2330
|
+
if (this.hold) {
|
|
2331
|
+
this.startHoldTimer(this.hold.expiresAt);
|
|
2332
|
+
} else {
|
|
2333
|
+
this.stopHoldTimer();
|
|
2334
|
+
this.forgetHold();
|
|
2335
|
+
}
|
|
2336
|
+
this.syncTray();
|
|
2337
|
+
this.emitHoldChange();
|
|
2338
|
+
this.toast(`${label} removed from your hold.`, "success");
|
|
2339
|
+
return true;
|
|
2340
|
+
} finally {
|
|
2341
|
+
this.releasingLabels.delete(label);
|
|
2342
|
+
chip?.removeAttribute("aria-busy");
|
|
2343
|
+
if (button?.isConnected) button.disabled = false;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2112
2346
|
async handleCta() {
|
|
2113
|
-
|
|
2114
|
-
|
|
2347
|
+
const committed = this.committedSelection();
|
|
2348
|
+
if (this.hold && !committed.some((s) => !(this.hold.items ?? []).some((i) => i.label === s.label))) {
|
|
2349
|
+
const seats = this.hold.seats ?? committed;
|
|
2115
2350
|
this.handedOff = true;
|
|
2116
2351
|
this.setCtaPhase("checkout");
|
|
2117
2352
|
this.opts.onCheckout?.(this.hold, seats, this.buildHandoff(this.hold));
|
|
2118
2353
|
return;
|
|
2119
2354
|
}
|
|
2355
|
+
this.holdingLabels = new Set(committed.map((seat) => seat.label));
|
|
2120
2356
|
this.setCtaPhase("holding");
|
|
2121
2357
|
try {
|
|
2122
2358
|
let hold = null;
|
|
2123
2359
|
const gaEntries = [...this.gaQty.entries()].filter(([, q]) => q > 0);
|
|
2124
|
-
const chosenSeats = this.
|
|
2360
|
+
const chosenSeats = this.committedSelection();
|
|
2125
2361
|
if (chosenSeats.length) {
|
|
2126
2362
|
const h = await this.controller.hold(void 0, this.opts.holdTtlMs);
|
|
2127
2363
|
hold = h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
@@ -2141,12 +2377,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
2141
2377
|
this.startHoldTimer(hold.expiresAt);
|
|
2142
2378
|
this.flashHeldSeats(hold);
|
|
2143
2379
|
this.setCtaPhase("checkout");
|
|
2380
|
+
this.emitHoldChange();
|
|
2144
2381
|
this.opts.onCheckout?.(hold, hold.seats ?? chosenSeats, this.buildHandoff(hold));
|
|
2145
2382
|
} catch (err) {
|
|
2146
2383
|
this.opts.onError?.(err);
|
|
2147
2384
|
this.toast("One or more seats were just taken. Please pick again.", "error");
|
|
2148
2385
|
this.setCtaPhase("idle");
|
|
2149
2386
|
} finally {
|
|
2387
|
+
this.holdingLabels.clear();
|
|
2150
2388
|
if (this.ctaPhase === "holding") this.ctaPhase = "idle";
|
|
2151
2389
|
this.syncTray();
|
|
2152
2390
|
}
|
|
@@ -2154,6 +2392,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2154
2392
|
startHoldTimer(expiresAt) {
|
|
2155
2393
|
this.stopHoldTimer();
|
|
2156
2394
|
this.holdExpiresAt = expiresAt;
|
|
2395
|
+
if (this.hold) this.rememberHold(this.hold);
|
|
2157
2396
|
const pill = this.els.hold;
|
|
2158
2397
|
pill.innerHTML = '<span class="sl-hold-dot" aria-hidden="true"></span><span>Held</span><span class="sl-hold-time" data-ref="holdTime"></span>';
|
|
2159
2398
|
const time = pill.querySelector('[data-ref="holdTime"]');
|
|
@@ -2200,6 +2439,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
2200
2439
|
this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
|
|
2201
2440
|
this.holdExpiresAt = h.expiresAt;
|
|
2202
2441
|
this.extendEl?.classList.remove("on");
|
|
2442
|
+
this.rememberHold(this.hold);
|
|
2443
|
+
this.emitHoldChange();
|
|
2203
2444
|
this.toast("More time added \u2014 your seats are still held.", "success");
|
|
2204
2445
|
} else {
|
|
2205
2446
|
this.toast("Couldn't add more time \u2014 please head to checkout now.", "warning");
|
|
@@ -2230,6 +2471,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2230
2471
|
this.bookedShown = true;
|
|
2231
2472
|
const handoff = this.buildHandoff(this.hold);
|
|
2232
2473
|
this.stopHoldTimer();
|
|
2474
|
+
this.forgetHold();
|
|
2233
2475
|
const n = handoff.lineItems.reduce((sum, i) => sum + i.quantity, 0);
|
|
2234
2476
|
if (this.els.bookedSub) {
|
|
2235
2477
|
this.els.bookedSub.innerHTML = `<span class="sl-booked-seats">${n} ${n === 1 ? "ticket" : "tickets"}</span> confirmed. A confirmation is on its way.`;
|
|
@@ -2254,6 +2496,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
2254
2496
|
const total = lineItems.reduce((sum, i) => sum + i.unitPrice * i.quantity, 0);
|
|
2255
2497
|
return { holdId: hold.holdId, expiresAt: hold.expiresAt, currency, lineItems, total };
|
|
2256
2498
|
}
|
|
2499
|
+
emitHoldChange() {
|
|
2500
|
+
const hold = this.hold;
|
|
2501
|
+
this.opts.onHoldChange?.(
|
|
2502
|
+
hold,
|
|
2503
|
+
hold?.seats ?? [],
|
|
2504
|
+
hold ? this.buildHandoff(hold) : null
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2257
2507
|
toast(msg, tone = "neutral") {
|
|
2258
2508
|
const el = this.els.toast;
|
|
2259
2509
|
if (!el) return;
|
|
@@ -2285,16 +2535,34 @@ var SeatPicker = class _SeatPicker {
|
|
|
2285
2535
|
return;
|
|
2286
2536
|
}
|
|
2287
2537
|
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" ? t2("map.statusHeld") : t2("map.statusTaken")}</div>`;
|
|
2288
|
-
|
|
2538
|
+
const location = [
|
|
2539
|
+
details.sectionLabel ? `Section ${details.sectionLabel}` : "",
|
|
2540
|
+
details.rowLabel ? `Row ${details.rowLabel}` : "",
|
|
2541
|
+
details.seatNumber ? `Seat ${details.seatNumber}` : details.label
|
|
2542
|
+
].filter(Boolean).join(" \xB7 ");
|
|
2543
|
+
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;
|
|
2289
2544
|
this.tipEl.style.display = "block";
|
|
2290
2545
|
this.placeTooltip();
|
|
2291
2546
|
}
|
|
2292
2547
|
// ---- public conveniences ----------------------------------------------------
|
|
2293
2548
|
getSelection() {
|
|
2294
|
-
return this.
|
|
2549
|
+
return this.committedSelection();
|
|
2550
|
+
}
|
|
2551
|
+
/** Current active/restored hold reflected in the tray. */
|
|
2552
|
+
getCurrentHold() {
|
|
2553
|
+
return this.hold;
|
|
2554
|
+
}
|
|
2555
|
+
/** Explicit host-driven hold restore (automatic session restore is on by default). */
|
|
2556
|
+
async resumeHold(holdId) {
|
|
2557
|
+
return this.resumeHoldFromServer(holdId, false);
|
|
2558
|
+
}
|
|
2559
|
+
/** Remove one server-held ticket while keeping the rest of the hold active. */
|
|
2560
|
+
async removeHeldTicket(label) {
|
|
2561
|
+
return this.removeHeldLabel(label);
|
|
2295
2562
|
}
|
|
2296
2563
|
async bestAvailable(qty, categoryKey) {
|
|
2297
2564
|
if (this.bestAvailableBusy) return null;
|
|
2565
|
+
if (this.confirmSeat) this.cancelConfirm();
|
|
2298
2566
|
this.bestAvailableBusy = true;
|
|
2299
2567
|
const button = this.els.tray?.querySelector(".sl-ba-go");
|
|
2300
2568
|
if (button) {
|
|
@@ -2310,6 +2578,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2310
2578
|
this.startHoldTimer(h.expiresAt);
|
|
2311
2579
|
this.flashHeldSeats(this.hold);
|
|
2312
2580
|
this.syncTray();
|
|
2581
|
+
this.emitHoldChange();
|
|
2313
2582
|
return this.hold;
|
|
2314
2583
|
}
|
|
2315
2584
|
return null;
|
|
@@ -2328,8 +2597,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
2328
2597
|
async release() {
|
|
2329
2598
|
const tracked = this.hold;
|
|
2330
2599
|
const controllerHold = this.controller.currentHold();
|
|
2600
|
+
let released = true;
|
|
2331
2601
|
if (controllerHold) {
|
|
2332
|
-
await this.controller.release();
|
|
2602
|
+
released = await this.controller.release();
|
|
2333
2603
|
} else if (tracked) {
|
|
2334
2604
|
const labels = [.../* @__PURE__ */ new Set([
|
|
2335
2605
|
...(tracked.items ?? []).map((item) => item.label),
|
|
@@ -2340,16 +2610,23 @@ var SeatPicker = class _SeatPicker {
|
|
|
2340
2610
|
await this.api.release(this.opts.event, labels, tracked.holdId);
|
|
2341
2611
|
} catch (error) {
|
|
2342
2612
|
this.opts.onError?.(error);
|
|
2613
|
+
released = false;
|
|
2343
2614
|
}
|
|
2344
2615
|
}
|
|
2345
2616
|
}
|
|
2617
|
+
if (!released) {
|
|
2618
|
+
this.toast("Couldn't release your tickets. Your hold is unchanged.", "error");
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2346
2621
|
this.hold = null;
|
|
2622
|
+
this.forgetHold();
|
|
2347
2623
|
this.handedOff = false;
|
|
2348
2624
|
this.bookedShown = false;
|
|
2349
2625
|
this.ctaPhase = "idle";
|
|
2350
2626
|
this.stopHoldTimer();
|
|
2351
2627
|
this.gaQty.clear();
|
|
2352
2628
|
this.syncTray();
|
|
2629
|
+
this.emitHoldChange();
|
|
2353
2630
|
}
|
|
2354
2631
|
destroy() {
|
|
2355
2632
|
this.destroyed = true;
|