@seatlayer/js 0.20.0 → 0.21.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 +92 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +92 -16
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.d.cts
CHANGED
|
@@ -325,7 +325,7 @@ declare class EmbeddedDesigner {
|
|
|
325
325
|
* SeatPicker — the full buyer experience as a widget.
|
|
326
326
|
*
|
|
327
327
|
* Where `SeatingChart` is canvas-only, SeatPicker owns the complete chrome
|
|
328
|
-
* from the canonical UX
|
|
328
|
+
* from the canonical UX contract: branded header,
|
|
329
329
|
* live price panel, selection tray with GA steppers, hold countdown, snipe
|
|
330
330
|
* toasts and expiry recovery — all on top of the shared PickerController, so
|
|
331
331
|
* every host gets the whole experience with one mount.
|
|
@@ -565,6 +565,8 @@ declare class SeatPicker {
|
|
|
565
565
|
private miniBase;
|
|
566
566
|
private miniTf;
|
|
567
567
|
private priceBandKeys;
|
|
568
|
+
private focusedCatKey;
|
|
569
|
+
private pricesExpanded;
|
|
568
570
|
/** Last surfaced section summary (re-rendered when the price band changes). */
|
|
569
571
|
private lastSection;
|
|
570
572
|
/** Section card collapsed to its slim pill (seat-picking has begun). */
|
|
@@ -808,6 +810,10 @@ declare class SeatPicker {
|
|
|
808
810
|
*/
|
|
809
811
|
private paidPrice;
|
|
810
812
|
private syncPrices;
|
|
813
|
+
/** Tap a price row → filter + frame that category on the map; tap again to
|
|
814
|
+
* clear. Shares `priceBandKeys` with the band selector so the row-dim state
|
|
815
|
+
* has one source of truth (and each control resets the other). */
|
|
816
|
+
private focusCategory;
|
|
811
817
|
/**
|
|
812
818
|
* Live-activity strip: turn WS availability deltas into one quiet line of
|
|
813
819
|
* social proof ("2 seats just taken in VIP · 118 left"). Diffs per-category
|
|
@@ -816,6 +822,9 @@ declare class SeatPicker {
|
|
|
816
822
|
*/
|
|
817
823
|
private narrateAvailability;
|
|
818
824
|
private lastCatAvail;
|
|
825
|
+
private lastAvailFloorId;
|
|
826
|
+
private availQuietUntil;
|
|
827
|
+
private liveTimer;
|
|
819
828
|
/** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
|
|
820
829
|
private evictTakenSelections;
|
|
821
830
|
private syncTray;
|
package/dist/index.d.ts
CHANGED
|
@@ -325,7 +325,7 @@ declare class EmbeddedDesigner {
|
|
|
325
325
|
* SeatPicker — the full buyer experience as a widget.
|
|
326
326
|
*
|
|
327
327
|
* Where `SeatingChart` is canvas-only, SeatPicker owns the complete chrome
|
|
328
|
-
* from the canonical UX
|
|
328
|
+
* from the canonical UX contract: branded header,
|
|
329
329
|
* live price panel, selection tray with GA steppers, hold countdown, snipe
|
|
330
330
|
* toasts and expiry recovery — all on top of the shared PickerController, so
|
|
331
331
|
* every host gets the whole experience with one mount.
|
|
@@ -565,6 +565,8 @@ declare class SeatPicker {
|
|
|
565
565
|
private miniBase;
|
|
566
566
|
private miniTf;
|
|
567
567
|
private priceBandKeys;
|
|
568
|
+
private focusedCatKey;
|
|
569
|
+
private pricesExpanded;
|
|
568
570
|
/** Last surfaced section summary (re-rendered when the price band changes). */
|
|
569
571
|
private lastSection;
|
|
570
572
|
/** Section card collapsed to its slim pill (seat-picking has begun). */
|
|
@@ -808,6 +810,10 @@ declare class SeatPicker {
|
|
|
808
810
|
*/
|
|
809
811
|
private paidPrice;
|
|
810
812
|
private syncPrices;
|
|
813
|
+
/** Tap a price row → filter + frame that category on the map; tap again to
|
|
814
|
+
* clear. Shares `priceBandKeys` with the band selector so the row-dim state
|
|
815
|
+
* has one source of truth (and each control resets the other). */
|
|
816
|
+
private focusCategory;
|
|
811
817
|
/**
|
|
812
818
|
* Live-activity strip: turn WS availability deltas into one quiet line of
|
|
813
819
|
* social proof ("2 seats just taken in VIP · 118 left"). Diffs per-category
|
|
@@ -816,6 +822,9 @@ declare class SeatPicker {
|
|
|
816
822
|
*/
|
|
817
823
|
private narrateAvailability;
|
|
818
824
|
private lastCatAvail;
|
|
825
|
+
private lastAvailFloorId;
|
|
826
|
+
private availQuietUntil;
|
|
827
|
+
private liveTimer;
|
|
819
828
|
/** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
|
|
820
829
|
private evictTakenSelections;
|
|
821
830
|
private syncTray;
|
package/dist/index.js
CHANGED
|
@@ -885,20 +885,30 @@ var CSS = `
|
|
|
885
885
|
.sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
886
886
|
.sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
|
|
887
887
|
background:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
|
|
888
|
-
.sl-prices{padding:
|
|
888
|
+
.sl-prices{display:flex;flex-direction:column;padding:4px 14px 8px;border-bottom:1px solid var(--sl-line)}
|
|
889
889
|
.sl-prices-sec,.sl-prices,.sl-seats-sec{flex:none}
|
|
890
|
-
.sl-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px
|
|
890
|
+
.sl-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px;
|
|
891
|
+
padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
|
|
892
|
+
.sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
|
|
893
|
+
.sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
|
|
894
|
+
.sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
|
|
891
895
|
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
892
896
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
893
897
|
.sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
|
|
894
898
|
.sl-price-amt{font-weight:800;font-variant-numeric:tabular-nums}
|
|
895
|
-
|
|
896
|
-
.sl-
|
|
897
|
-
.sl-
|
|
899
|
+
/* long category lists: capped by default, scroll once expanded */
|
|
900
|
+
.sl-prices.sl-expanded{max-height:196px;overflow-y:auto;overscroll-behavior:contain;scrollbar-gutter:stable}
|
|
901
|
+
.sl-price-more{display:flex;align-items:center;min-height:26px;padding:0;
|
|
902
|
+
color:var(--sl-muted);font-size:11px;font-weight:750;transition:color .15s}
|
|
903
|
+
.sl-price-more:hover,.sl-price-more:focus-visible{color:var(--sl-text)}
|
|
904
|
+
/* held/sold key \u2014 one quiet caption line; the map itself teaches these states */
|
|
905
|
+
.sl-status-key{display:flex;gap:11px;flex-wrap:wrap;padding:5px 0 0;margin-top:4px;border-top:1px solid var(--sl-line);color:var(--sl-muted);font-size:10px}
|
|
906
|
+
.sl-status-item{display:inline-flex;align-items:center;gap:5px}
|
|
907
|
+
.sl-status-icon{width:13px;height:13px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;
|
|
898
908
|
color:#fff;background:#6b7280;line-height:1}
|
|
899
|
-
.sl-status-icon svg{width:
|
|
909
|
+
.sl-status-icon svg{width:8px;height:8px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
900
910
|
.sl-status-icon.sold{background:#8b93a0}
|
|
901
|
-
.sl-status-icon.sold svg{width:
|
|
911
|
+
.sl-status-icon.sold svg{width:9px;height:9px;stroke-width:2.4}
|
|
902
912
|
|
|
903
913
|
/* tray */
|
|
904
914
|
.sl-seats-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
@@ -937,12 +947,15 @@ var CSS = `
|
|
|
937
947
|
.sl-chip .view:hover,.sl-chip .view:focus-visible{color:var(--sl-text);background:color-mix(in srgb,var(--sl-accent) 10%,transparent)}
|
|
938
948
|
.sl-chip .rm svg{width:11px;height:11px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round}
|
|
939
949
|
.sl-chip .view svg{width:13px;height:13px;stroke:currentColor;stroke-width:1.8;fill:none}
|
|
940
|
-
/* live-activity strip \u2014 narrates WS availability deltas (social proof + urgency)
|
|
941
|
-
|
|
950
|
+
/* live-activity strip \u2014 narrates WS availability deltas (social proof + urgency).
|
|
951
|
+
Hidden until a delta actually happens: a static "seats update in real time"
|
|
952
|
+
banner is dead vertical space, a "2 seats just taken" flash is a signal. */
|
|
953
|
+
.sl-live{display:none;align-items:center;gap:7px;margin:10px 14px 0;padding:7px 9px;flex:none;
|
|
942
954
|
border:1px solid var(--sl-line);border-radius:8px;background:color-mix(in srgb,var(--sl-accent) 4%,var(--sl-surface));
|
|
943
955
|
font-size:11px;color:var(--sl-muted)}
|
|
944
956
|
.sl-live .dot{width:6px;height:6px;border-radius:999px;background:#22a06b;box-shadow:0 0 6px rgba(34,160,107,.75);flex:none}
|
|
945
957
|
.sl-live span:last-child{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
958
|
+
.sl-live.on{display:flex;animation:slNoticeIn .38s cubic-bezier(.2,.8,.2,1) both}
|
|
946
959
|
|
|
947
960
|
/* GA rows */
|
|
948
961
|
.sl-ga{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px dashed var(--sl-line);border-radius:var(--sl-r-sm)}
|
|
@@ -1128,13 +1141,18 @@ var CSS = `
|
|
|
1128
1141
|
border-radius:15px;box-shadow:0 24px 64px -18px rgba(0,0,0,.72);transform:translate(-50%,calc(-100% - 16px));
|
|
1129
1142
|
animation:slConfirmIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
1130
1143
|
.sl-confirm[data-placement="below"]{transform:translate(-50%,16px);animation:slConfirmBelowIn .24s cubic-bezier(.2,.8,.2,1) both}
|
|
1131
|
-
.sl-confirm-grid{display:grid;grid-template-columns:
|
|
1144
|
+
.sl-confirm-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(52px,auto) minmax(52px,auto);border-bottom:1px solid var(--sl-line)}
|
|
1132
1145
|
.sl-confirm-field{min-width:0;padding:12px 11px 10px;border-right:1px solid var(--sl-line)}
|
|
1133
1146
|
.sl-confirm-field:last-child{border-right:0;text-align:center}
|
|
1134
1147
|
.sl-confirm-field:nth-child(2){text-align:center}
|
|
1135
1148
|
.sl-confirm-key{display:block;font-size:8.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--sl-muted);font-weight:800}
|
|
1136
1149
|
.sl-confirm-value{display:block;margin-top:4px;color:var(--sl-text);font-size:17px;line-height:1.1;font-weight:850;
|
|
1137
1150
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1151
|
+
/* Long venue section names must read in full: smaller type + up to two lines
|
|
1152
|
+
beats an ellipsis at identity-confirmation time. Row/seat stay big \u2014 they're
|
|
1153
|
+
short and they're what the buyer double-checks against the map. */
|
|
1154
|
+
.sl-confirm-field:first-child .sl-confirm-value{font-size:13.5px;line-height:1.25;white-space:normal;
|
|
1155
|
+
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
|
|
1138
1156
|
.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))}
|
|
1139
1157
|
.sl-confirm-cat .sl-dot{border:2px solid rgba(255,255,255,.78);width:11px;height:11px}
|
|
1140
1158
|
.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}
|
|
@@ -1428,6 +1446,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
1428
1446
|
this.miniTf = null;
|
|
1429
1447
|
// F4 price-band filter — active band's category keys (null = all prices)
|
|
1430
1448
|
this.priceBandKeys = null;
|
|
1449
|
+
this.focusedCatKey = null;
|
|
1450
|
+
this.pricesExpanded = false;
|
|
1431
1451
|
/** Last surfaced section summary (re-rendered when the price band changes). */
|
|
1432
1452
|
this.lastSection = null;
|
|
1433
1453
|
/** Section card collapsed to its slim pill (seat-picking has begun). */
|
|
@@ -1462,6 +1482,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
1462
1482
|
/** Set by open(): closes the modal (scroll restore + destroy + onClose). */
|
|
1463
1483
|
this.closeModal = null;
|
|
1464
1484
|
this.lastCatAvail = null;
|
|
1485
|
+
this.lastAvailFloorId = "";
|
|
1486
|
+
this.availQuietUntil = 0;
|
|
1487
|
+
this.liveTimer = null;
|
|
1465
1488
|
if (!options || typeof options !== "object") throw new Error("seatmap: options object is required");
|
|
1466
1489
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
1467
1490
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
@@ -1554,7 +1577,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1554
1577
|
return "";
|
|
1555
1578
|
}
|
|
1556
1579
|
}
|
|
1557
|
-
const sight = distance != null ?
|
|
1580
|
+
const sight = distance != null ? t2("picker.sightline", { m: distance }) : this.tf("picker.sightlineClear", "Clear sightline");
|
|
1558
1581
|
return `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" src="${url}" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button><div class="sl-confirm-sight"><span aria-hidden="true">\u2713</span>${sight}</div>`;
|
|
1559
1582
|
}
|
|
1560
1583
|
/** True when the picker is rendered inside an iframe (snippet embed at /e/:key). */
|
|
@@ -2550,9 +2573,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
2550
2573
|
select.addEventListener("change", () => {
|
|
2551
2574
|
const band = bands.find((candidate) => candidate.id === select.value);
|
|
2552
2575
|
const keys = band?.keys ?? null;
|
|
2576
|
+
this.focusedCatKey = null;
|
|
2553
2577
|
this.priceBandKeys = keys ? new Set(keys) : null;
|
|
2554
2578
|
this.controller.setCategoryFilter(keys);
|
|
2555
2579
|
this.controller.focusCategoryFilter(keys);
|
|
2580
|
+
this.syncFloors();
|
|
2581
|
+
this.syncRung();
|
|
2582
|
+
this.refreshMinimap();
|
|
2556
2583
|
this.syncPrices();
|
|
2557
2584
|
if (this.lastSection) this.showSectionCard(this.lastSection);
|
|
2558
2585
|
});
|
|
@@ -2661,6 +2688,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2661
2688
|
if (narrow) {
|
|
2662
2689
|
card.className = "sl-seccard strip on";
|
|
2663
2690
|
card.setAttribute("role", "status");
|
|
2691
|
+
card.setAttribute("aria-label", t2("picker.sectionSummaryAria", { label: summary.label }));
|
|
2664
2692
|
card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn;
|
|
2665
2693
|
card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
|
|
2666
2694
|
(this.els.sheetHead ?? this.els.side ?? this.els.map).appendChild(card);
|
|
@@ -2971,16 +2999,52 @@ var SeatPicker = class _SeatPicker {
|
|
|
2971
2999
|
const left = this.controller.categoryAvailability();
|
|
2972
3000
|
this.narrateAvailability(doc.categories, left);
|
|
2973
3001
|
this.syncSoldout(doc.categories, left);
|
|
2974
|
-
|
|
3002
|
+
const PRICE_LIMIT = 5;
|
|
3003
|
+
const overflow = doc.categories.length - PRICE_LIMIT;
|
|
3004
|
+
const collapsed = overflow > 1 && !this.pricesExpanded;
|
|
3005
|
+
const shown = collapsed ? doc.categories.slice(0, PRICE_LIMIT) : doc.categories;
|
|
3006
|
+
this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
|
|
3007
|
+
this.els.prices.innerHTML = shown.map((c) => {
|
|
2975
3008
|
const price = this.catPrice(c);
|
|
3009
|
+
const active = this.focusedCatKey === c.key;
|
|
2976
3010
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
2977
|
-
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>`;
|
|
2978
|
-
}).join("") + `<div class="sl-status-key" aria-label="Seat status legend"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>Temporarily held</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>Sold</span></div>`;
|
|
3011
|
+
return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${c.key}" role="button" tabindex="0" aria-pressed="${active}" title="${active ? "Show all seats" : `Show ${c.label} seats on the map`}"><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>`;
|
|
3012
|
+
}).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? `Show all ${doc.categories.length} ticket types` : "Show fewer") + `</button>` : "") + `<div class="sl-status-key" aria-label="Seat status legend"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>Temporarily held</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>Sold</span></div>`;
|
|
2979
3013
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
2980
3014
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
2981
3015
|
row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
|
|
3016
|
+
const toggle = () => this.focusCategory(row.dataset.cat ?? "");
|
|
3017
|
+
row.addEventListener("click", toggle);
|
|
3018
|
+
row.addEventListener("keydown", (e) => {
|
|
3019
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3020
|
+
e.preventDefault();
|
|
3021
|
+
toggle();
|
|
3022
|
+
}
|
|
3023
|
+
});
|
|
3024
|
+
});
|
|
3025
|
+
this.els.prices.querySelector(".sl-price-more")?.addEventListener("click", () => {
|
|
3026
|
+
this.pricesExpanded = !this.pricesExpanded;
|
|
3027
|
+
this.syncPrices();
|
|
2982
3028
|
});
|
|
2983
3029
|
}
|
|
3030
|
+
/** Tap a price row → filter + frame that category on the map; tap again to
|
|
3031
|
+
* clear. Shares `priceBandKeys` with the band selector so the row-dim state
|
|
3032
|
+
* has one source of truth (and each control resets the other). */
|
|
3033
|
+
focusCategory(key) {
|
|
3034
|
+
if (!key) return;
|
|
3035
|
+
const next = this.focusedCatKey === key ? null : key;
|
|
3036
|
+
this.focusedCatKey = next;
|
|
3037
|
+
this.priceBandKeys = next ? /* @__PURE__ */ new Set([next]) : null;
|
|
3038
|
+
const select = this.els.pricesSec?.querySelector(".sl-price-select");
|
|
3039
|
+
if (select) select.value = "all";
|
|
3040
|
+
this.controller.setCategoryFilter(next ? [next] : null);
|
|
3041
|
+
this.controller.focusCategoryFilter(next ? [next] : null);
|
|
3042
|
+
this.syncFloors();
|
|
3043
|
+
this.syncRung();
|
|
3044
|
+
this.refreshMinimap();
|
|
3045
|
+
this.syncPrices();
|
|
3046
|
+
if (this.lastSection) this.showSectionCard(this.lastSection);
|
|
3047
|
+
}
|
|
2984
3048
|
/**
|
|
2985
3049
|
* Live-activity strip: turn WS availability deltas into one quiet line of
|
|
2986
3050
|
* social proof ("2 seats just taken in VIP · 118 left"). Diffs per-category
|
|
@@ -2991,13 +3055,23 @@ var SeatPicker = class _SeatPicker {
|
|
|
2991
3055
|
const textEl = this.els.liveText;
|
|
2992
3056
|
const prev = this.lastCatAvail;
|
|
2993
3057
|
this.lastCatAvail = { ...left };
|
|
2994
|
-
|
|
3058
|
+
const floorId = this.controller.getActiveFloorId();
|
|
3059
|
+
if (floorId !== this.lastAvailFloorId) {
|
|
3060
|
+
this.lastAvailFloorId = floorId;
|
|
3061
|
+
this.availQuietUntil = performance.now() + 2e3;
|
|
3062
|
+
}
|
|
3063
|
+
if (!textEl || !prev || performance.now() < this.availQuietUntil) return;
|
|
2995
3064
|
for (const cat of categories) {
|
|
2996
3065
|
const before = prev[cat.key];
|
|
2997
3066
|
const now = left[cat.key] ?? 0;
|
|
2998
3067
|
if (before === void 0 || now >= before) continue;
|
|
2999
3068
|
const taken = before - now;
|
|
3000
3069
|
textEl.textContent = `${taken} seat${taken === 1 ? "" : "s"} just taken in ${cat.label} \xB7 ${now} left`;
|
|
3070
|
+
this.els.live?.classList.remove("on");
|
|
3071
|
+
void this.els.live?.offsetWidth;
|
|
3072
|
+
this.els.live?.classList.add("on");
|
|
3073
|
+
if (this.liveTimer) clearTimeout(this.liveTimer);
|
|
3074
|
+
this.liveTimer = setTimeout(() => this.els.live?.classList.remove("on"), 8e3);
|
|
3001
3075
|
return;
|
|
3002
3076
|
}
|
|
3003
3077
|
}
|
|
@@ -3025,7 +3099,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
3025
3099
|
} else if (!seats.length && !heldItems.length) {
|
|
3026
3100
|
parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
|
|
3027
3101
|
}
|
|
3028
|
-
|
|
3102
|
+
const noPicks = !seats.length && !heldItems.length;
|
|
3103
|
+
if (!this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
|
|
3029
3104
|
const cats = this.controller.doc?.categories ?? [];
|
|
3030
3105
|
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>`);
|
|
3031
3106
|
}
|
|
@@ -3589,6 +3664,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3589
3664
|
this.closeSeatView();
|
|
3590
3665
|
this.stopHoldTimer();
|
|
3591
3666
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
3667
|
+
if (this.liveTimer) clearTimeout(this.liveTimer);
|
|
3592
3668
|
for (const timer of this.motionTimers) clearTimeout(timer);
|
|
3593
3669
|
this.motionTimers.clear();
|
|
3594
3670
|
this.ro?.disconnect();
|