@seatlayer/js 0.46.0 → 0.47.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/{hostedCheckout-PLHKPT6W.js → hostedCheckout-SHQCWN5Y.js} +27 -4
- package/dist/hostedCheckout-SHQCWN5Y.js.map +1 -0
- package/dist/index.cjs +420 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -11
- package/dist/index.d.ts +97 -11
- package/dist/index.js +400 -54
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/hostedCheckout-PLHKPT6W.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -270,8 +270,12 @@ var BuyerRealtimeClient = class {
|
|
|
270
270
|
const next = projectionFromSnapshot(frame);
|
|
271
271
|
const changes = diffProjections(this.projection, next);
|
|
272
272
|
this.projection = next;
|
|
273
|
-
if (changes === null)
|
|
274
|
-
|
|
273
|
+
if (changes === null) {
|
|
274
|
+
if (this.opts.sink.applyProjection) this.opts.sink.applyProjection(next);
|
|
275
|
+
else void this.opts.sink.resync();
|
|
276
|
+
} else if (changes.length) {
|
|
277
|
+
this.opts.sink.applyStatuses(changes);
|
|
278
|
+
}
|
|
275
279
|
return;
|
|
276
280
|
}
|
|
277
281
|
if (type === "delta" && Array.isArray(frame.changes)) {
|
|
@@ -524,7 +528,7 @@ var PubApi = class {
|
|
|
524
528
|
return this.request(`/pub/events/${encodeURIComponent(key)}/chart`);
|
|
525
529
|
}
|
|
526
530
|
objects(key) {
|
|
527
|
-
return this.request(`/pub/events/${encodeURIComponent(key)}/objects`);
|
|
531
|
+
return this.request(`/pub/events/${encodeURIComponent(key)}/objects?compact=1`);
|
|
528
532
|
}
|
|
529
533
|
hold(key, selections, ttlMs, replaceHoldId) {
|
|
530
534
|
return this.request(`/pub/events/${encodeURIComponent(key)}/hold`, {
|
|
@@ -941,7 +945,6 @@ var SeatingChart = class {
|
|
|
941
945
|
this.mount = null;
|
|
942
946
|
this.hostEl = null;
|
|
943
947
|
this.rendered = false;
|
|
944
|
-
this.retryBtn = null;
|
|
945
948
|
this.mode_ = null;
|
|
946
949
|
this.tipEl = null;
|
|
947
950
|
this.tipPos = { x: 0, y: 0 };
|
|
@@ -1316,14 +1319,12 @@ var SeatingChart = class {
|
|
|
1316
1319
|
void this.render().catch((err) => this.opts.onError?.(err));
|
|
1317
1320
|
});
|
|
1318
1321
|
box.appendChild(btn);
|
|
1319
|
-
this.retryBtn = btn;
|
|
1320
1322
|
host.appendChild(box);
|
|
1321
1323
|
}
|
|
1322
1324
|
destroy() {
|
|
1323
1325
|
this.realtime?.stop();
|
|
1324
1326
|
this.realtime = null;
|
|
1325
1327
|
this.access?.clear();
|
|
1326
|
-
this.retryBtn = null;
|
|
1327
1328
|
if (this.hostEl && this.onTipMove) this.hostEl.removeEventListener("mousemove", this.onTipMove);
|
|
1328
1329
|
this.tipEl = null;
|
|
1329
1330
|
this.onTipMove = null;
|
|
@@ -1981,10 +1982,17 @@ import {
|
|
|
1981
1982
|
t as t2,
|
|
1982
1983
|
tCount
|
|
1983
1984
|
} from "@seatlayer/core";
|
|
1985
|
+
import { isAuthoredSeatView, seatViewDisclosure } from "@seatlayer/core/view3d/crossfade/panorama";
|
|
1986
|
+
import {
|
|
1987
|
+
browserPanoramaConstraints,
|
|
1988
|
+
loadPanoramaImage,
|
|
1989
|
+
planPanoramaDelivery,
|
|
1990
|
+
schedulePanoramaUpgrade
|
|
1991
|
+
} from "@seatlayer/core/view/panoramaDelivery";
|
|
1984
1992
|
var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
|
|
1985
1993
|
var DEFAULT_MAX_SELECTION2 = 10;
|
|
1986
1994
|
var EXTEND_PROMPT_MS = 6e4;
|
|
1987
|
-
var MAX_3D_SEATS_DEFAULT =
|
|
1995
|
+
var MAX_3D_SEATS_DEFAULT = 6e4;
|
|
1988
1996
|
var MAX_3D_SECTION_PILLS = 12;
|
|
1989
1997
|
function pointInPolygon(x, y, poly) {
|
|
1990
1998
|
let inside = false;
|
|
@@ -2067,7 +2075,7 @@ async function loadHostedCheckout() {
|
|
|
2067
2075
|
cdnChunkUrl("seatlayer-checkout.mjs")
|
|
2068
2076
|
);
|
|
2069
2077
|
}
|
|
2070
|
-
return import("./hostedCheckout-
|
|
2078
|
+
return import("./hostedCheckout-SHQCWN5Y.js");
|
|
2071
2079
|
}
|
|
2072
2080
|
function paymentsOffReason(reason) {
|
|
2073
2081
|
return reason === "unavailable_for_event" || reason === "payments_off_for_event" ? reason : "not_configured";
|
|
@@ -2328,7 +2336,12 @@ var CSS = (
|
|
|
2328
2336
|
INTO one of these positioned flex containers and flows/stacks within it, so no
|
|
2329
2337
|
two pieces of chrome free-float on top of each other. Regions never overlap:
|
|
2330
2338
|
the top strip splits into left/center/right; rails + corners own their edge. */
|
|
2331
|
-
|
|
2339
|
+
/* align-items:center, not the flex default of stretch. Without it a short pill
|
|
2340
|
+
beside a taller control (TEST MODE next to the Map/3D toggle) is stretched to
|
|
2341
|
+
the row's height, so two pills that should read as a matched pair end up
|
|
2342
|
+
different shapes on different baselines. Column regions set their own
|
|
2343
|
+
cross-axis alignment below and are unaffected. */
|
|
2344
|
+
.sl-anchor{position:absolute;z-index:5;display:flex;align-items:center;gap:8px;pointer-events:none}
|
|
2332
2345
|
.sl-anchor > *{pointer-events:auto}
|
|
2333
2346
|
.sl-anchor[data-region="top-left"]{top:12px;left:12px;flex-wrap:wrap;max-width:38%}
|
|
2334
2347
|
.sl-anchor[data-region="top-center"]{top:12px;left:50%;transform:translateX(-50%);flex-direction:column;
|
|
@@ -2344,10 +2357,17 @@ var CSS = (
|
|
|
2344
2357
|
.sl-picker[data-layout="narrow"] .sl-anchor[data-region="top-center"]{max-width:44%}
|
|
2345
2358
|
|
|
2346
2359
|
/* TEST MODE badge \u2014 a small pill in the top-right region (shrinks on narrow) */
|
|
2347
|
-
|
|
2360
|
+
/* align-self:stretch, not a hardcoded height: the badge sits beside the Map/3D
|
|
2361
|
+
toggle, whose height comes from its own border + padding + button metrics.
|
|
2362
|
+
Stretching matches that row exactly and keeps matching if the toggle ever
|
|
2363
|
+
changes, while the badge's own inline-flex keeps the label centred inside
|
|
2364
|
+
whatever height it gets. Alone in the region it simply takes its natural
|
|
2365
|
+
size. */
|
|
2366
|
+
.sl-testbadge{display:inline-flex;align-items:center;align-self:stretch;padding:0 12px;border-radius:999px;
|
|
2367
|
+
font-size:10px;font-weight:800;letter-spacing:.1em;line-height:1;
|
|
2348
2368
|
text-transform:uppercase;white-space:nowrap;background:var(--sl-accent);color:var(--sl-accent-ink);
|
|
2349
2369
|
box-shadow:0 2px 8px rgba(0,0,0,.25)}
|
|
2350
|
-
.sl-picker[data-layout="narrow"] .sl-testbadge{padding:
|
|
2370
|
+
.sl-picker[data-layout="narrow"] .sl-testbadge{padding:0 8px;font-size:8.5px;letter-spacing:.06em}
|
|
2351
2371
|
|
|
2352
2372
|
/* zoom column (flows within the bottom-right region) */
|
|
2353
2373
|
.sl-zoom{display:flex;flex-direction:column;gap:6px}
|
|
@@ -2439,7 +2459,11 @@ var CSS = (
|
|
|
2439
2459
|
Layered Rows mark + wordmark. Hidden when the host opts out or the org's paid
|
|
2440
2460
|
theme sets hideBadge. */
|
|
2441
2461
|
.sl-powered{display:flex;align-items:center;justify-content:center;gap:6px;margin-top:10px;
|
|
2442
|
-
font-size:
|
|
2462
|
+
font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--sl-text);opacity:.72;
|
|
2463
|
+
text-decoration:none;padding:6px 10px;border-radius:999px;width:fit-content;margin-inline:auto;
|
|
2464
|
+
transition:opacity .15s ease,background-color .15s ease}
|
|
2465
|
+
.sl-powered:hover{opacity:1;background:color-mix(in srgb,var(--sl-text) 8%,transparent)}
|
|
2466
|
+
.sl-powered:focus-visible{opacity:1;outline:2px solid var(--sl-accent);outline-offset:2px}
|
|
2443
2467
|
.sl-powered-mark{width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;
|
|
2444
2468
|
background:#0c1220;color:#fcf7ee}
|
|
2445
2469
|
.sl-powered-mark svg{width:12px;height:11px}
|
|
@@ -2618,18 +2642,41 @@ var CSS = (
|
|
|
2618
2642
|
.sl-view3d{position:absolute;inset:0;z-index:4;opacity:0;touch-action:none;
|
|
2619
2643
|
transition:opacity .3s ease;background:radial-gradient(120% 120% at 50% 0%,#191f28 0%,#0d1014 70%)}
|
|
2620
2644
|
.sl-view3d canvas{display:block;width:100%;height:100%}
|
|
2645
|
+
.sl-view3d canvas:focus-visible{outline:2px solid var(--sl-accent);outline-offset:-3px}
|
|
2646
|
+
.sl-view3d-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;
|
|
2647
|
+
z-index:1;color:#d9e2f2;font-size:13px;font-weight:700;letter-spacing:.02em;pointer-events:none}
|
|
2648
|
+
.sl-view3d-loading::before{content:"";width:18px;height:18px;border-radius:50%;
|
|
2649
|
+
border:2px solid rgba(217,226,242,.28);border-top-color:#d9e2f2;animation:slSpin .8s linear infinite}
|
|
2621
2650
|
[data-view3d=on] .sl-chips,[data-view3d=on] .sl-rungs{display:none}
|
|
2622
2651
|
.sl-view3d-back{position:absolute;top:12px;left:12px;z-index:2;display:inline-flex;align-items:center;gap:6px;
|
|
2623
2652
|
padding:7px 13px 7px 9px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.03em;
|
|
2624
2653
|
color:#e6edf3;background:rgba(10,14,20,.62);border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px)}
|
|
2625
2654
|
.sl-view3d-back:hover,.sl-view3d-back:focus-visible{background:rgba(16,22,30,.82);border-color:rgba(255,255,255,.4)}
|
|
2626
2655
|
.sl-view3d-back svg{width:15px;height:15px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
2656
|
+
.sl-view3d-fs{position:absolute;top:12px;right:12px;z-index:2;min-width:44px;min-height:44px;padding:8px 12px;
|
|
2657
|
+
border-radius:999px;font-size:16px;color:#e6edf3;background:rgba(10,14,20,.62);
|
|
2658
|
+
border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px)}
|
|
2659
|
+
.sl-view3d-fs:hover,.sl-view3d-fs:focus-visible{background:rgba(16,22,30,.82);border-color:rgba(255,255,255,.4)}
|
|
2627
2660
|
/* Venue navigation inside 3D: levels (isolate a floor) and areas (fly to a zone).
|
|
2628
2661
|
Bottom-LEFT, clear of the module's own chips (Overview bottom-right, 360
|
|
2629
2662
|
bottom-centre) and of the bottom-sheeted confirm card. Horizontally scrollable
|
|
2630
|
-
so a venue with many zones never pushes the rail off a phone screen.
|
|
2663
|
+
so a venue with many zones never pushes the rail off a phone screen.
|
|
2664
|
+
|
|
2665
|
+
EVERY WIDTH HERE IS RELATIVE TO THE RAIL, NEVER TO THE WINDOW. These three
|
|
2666
|
+
rules used to size off 100vw, which contradicts the one contract this widget
|
|
2667
|
+
states about itself: it adapts to a full-screen takeover, an inline div in a
|
|
2668
|
+
content page, or a popup, and its breakpoints key off the CONTAINER, never the
|
|
2669
|
+
viewport. A 390 px picker embedded in a 1400 px page asked for
|
|
2670
|
+
calc(100vw - 180px) = 1220 inside a 390 px rail.
|
|
2671
|
+
|
|
2672
|
+
MEASURED, IT DID NOT OVERFLOW: the scroll parent is a flex container, so the
|
|
2673
|
+
over-large declaration was shrunk back to the rail and both the old and new
|
|
2674
|
+
rules resolve to 364 px in situ. So this is a latent correctness fix, not a
|
|
2675
|
+
visible bug -- the numbers were wrong and were being covered for by a
|
|
2676
|
+
flex-shrink one level up. Left as 100% because the next person to change that
|
|
2677
|
+
parent's display should not inherit a rule that only works by accident. */
|
|
2631
2678
|
.sl-view3d-nav{position:absolute;left:12px;bottom:16px;z-index:3;display:flex;flex-direction:column;gap:6px;
|
|
2632
|
-
max-width:calc(100% -
|
|
2679
|
+
max-width:calc(100% - 150px);pointer-events:none}
|
|
2633
2680
|
.sl-view3d-nav > div{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;pointer-events:auto;
|
|
2634
2681
|
padding:1px;-webkit-overflow-scrolling:touch}
|
|
2635
2682
|
.sl-view3d-nav > div::-webkit-scrollbar{display:none}
|
|
@@ -2638,6 +2685,21 @@ var CSS = (
|
|
|
2638
2685
|
border:1px solid rgba(150,165,205,.35);backdrop-filter:blur(6px);cursor:pointer}
|
|
2639
2686
|
.sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
|
|
2640
2687
|
.sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
2688
|
+
.sl-view3d-nav button:disabled{opacity:.45;cursor:not-allowed}
|
|
2689
|
+
.sl-view3d-nav select{min-height:38px;max-width:100%;padding:7px 34px 7px 12px;
|
|
2690
|
+
border-radius:999px;font:700 11.5px/1 inherit;color:#eef1f8;background:rgba(12,18,32,.86);
|
|
2691
|
+
border:1px solid rgba(150,165,205,.45);backdrop-filter:blur(6px);cursor:pointer}
|
|
2692
|
+
.sl-view3d-nav select:focus-visible{outline:2px solid var(--sl-accent);outline-offset:2px}
|
|
2693
|
+
.sl-view3d-nav .sl-view3d-locator{display:grid;grid-template-columns:minmax(150px,1.25fr) minmax(110px,.8fr) minmax(105px,.7fr) auto;
|
|
2694
|
+
width:min(720px,100%);overflow:visible}
|
|
2695
|
+
.sl-view3d-nav.is-seat-focused .sl-view3d-locator{display:none}
|
|
2696
|
+
.sl-view3d-locator select{width:100%;min-width:0}
|
|
2697
|
+
.sl-picker[data-layout="narrow"] .sl-view3d-nav .sl-view3d-locator{display:flex;width:100%;overflow-x:auto}
|
|
2698
|
+
.sl-picker[data-layout="narrow"] .sl-view3d-locator select{flex:0 0 155px}
|
|
2699
|
+
/* On phones the library owns the bottom edge for seat/panorama/overview actions.
|
|
2700
|
+
Keep venue navigation in a separate top rail so those control families never
|
|
2701
|
+
stack over one another. */
|
|
2702
|
+
.sl-picker[data-layout="narrow"] .sl-view3d-nav{top:68px;bottom:auto;max-width:calc(100% - 24px)}
|
|
2641
2703
|
/* While immersed, the 2D-only chrome is meaningless \u2014 hide it, keep Map|3D. */
|
|
2642
2704
|
.sl-picker[data-view3d="on"] .sl-rungs,
|
|
2643
2705
|
.sl-picker[data-view3d="on"] .sl-floors,
|
|
@@ -2716,7 +2778,6 @@ var CSS = (
|
|
|
2716
2778
|
.sl-confirm-thumb-badge{position:absolute;right:7px;top:7px;display:inline-flex;align-items:center;gap:5px;
|
|
2717
2779
|
font-size:10px;font-weight:700;color:#fff;background:rgba(10,14,22,0.72);border-radius:12px;padding:4px 9px;backdrop-filter:blur(3px)}
|
|
2718
2780
|
.sl-confirm-sight{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--sl-muted);margin-bottom:2px}
|
|
2719
|
-
.sl-confirm-sight span{color:#22a06b;font-weight:800}
|
|
2720
2781
|
.sl-confirm-view{width:100%;margin-top:9px;padding:8px;border-radius:8px;border:1px solid var(--sl-line);
|
|
2721
2782
|
color:var(--sl-text);font-weight:700;font-size:12px;display:flex;align-items:center;justify-content:center;gap:7px}
|
|
2722
2783
|
.sl-confirm-view:hover{border-color:var(--sl-muted)}
|
|
@@ -2811,6 +2872,11 @@ var CSS = (
|
|
|
2811
2872
|
/* modal host */
|
|
2812
2873
|
.sl-modal-scrim{position:fixed;inset:0;z-index:2147483000;background:rgba(5,7,12,.66);display:flex;align-items:center;justify-content:center;padding:18px}
|
|
2813
2874
|
.sl-modal-frame{width:min(1200px,100%);height:min(820px,100%);border-radius:16px;overflow:hidden;box-shadow:0 40px 120px -30px rgba(0,0,0,.8)}
|
|
2875
|
+
/* The frame already clips to 16px. A picker rounding itself to --sl-radius
|
|
2876
|
+
(14px) inside it leaves a sliver of scrim showing at each corner, which
|
|
2877
|
+
reads as a rendering fault rather than as a rounded card. One owner of the
|
|
2878
|
+
corners, and it is the frame. */
|
|
2879
|
+
.sl-modal-frame > .sl-picker{border-radius:0}
|
|
2814
2880
|
@media(max-width:640px){.sl-modal-scrim{padding:0}.sl-modal-frame{width:100%;height:100%;border-radius:0}}
|
|
2815
2881
|
`
|
|
2816
2882
|
);
|
|
@@ -2821,6 +2887,17 @@ function ensureStyle() {
|
|
|
2821
2887
|
el.textContent = CSS;
|
|
2822
2888
|
document.head.appendChild(el);
|
|
2823
2889
|
}
|
|
2890
|
+
function formatWhen(startsAt, timezone, locale) {
|
|
2891
|
+
const options = { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" };
|
|
2892
|
+
const at = new Date(startsAt);
|
|
2893
|
+
if (timezone) {
|
|
2894
|
+
try {
|
|
2895
|
+
return at.toLocaleString(locale, { ...options, timeZone: timezone });
|
|
2896
|
+
} catch {
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
return at.toLocaleString(locale, options);
|
|
2900
|
+
}
|
|
2824
2901
|
function resolveTokens(chart, host) {
|
|
2825
2902
|
const accent = host?.accent ?? chart?.accent ?? "#f4b740";
|
|
2826
2903
|
const accentInk = host?.accentInk ?? chart?.accentInk ?? "#1a1200";
|
|
@@ -3015,6 +3092,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
3015
3092
|
currency: options.currency,
|
|
3016
3093
|
flashOnLiveChange: true,
|
|
3017
3094
|
colorblindSafe: this.cbSafe,
|
|
3095
|
+
// The drawn map's own overrides, when the host supplied them up front.
|
|
3096
|
+
// Everything else on `theme` is CSS and lands on the root instead.
|
|
3097
|
+
mapTheme: options.theme?.map ?? null,
|
|
3018
3098
|
onSelectionChange: () => {
|
|
3019
3099
|
this.syncTray();
|
|
3020
3100
|
if (this.committedSelection().length) this.collapseSectionCard();
|
|
@@ -3103,8 +3183,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
3103
3183
|
/**
|
|
3104
3184
|
* Eager sightline preview for the confirm card: the organizer's real view
|
|
3105
3185
|
* photo, or — only when the chart has an actual stage to look at — a generated
|
|
3106
|
-
* forward view plus an
|
|
3107
|
-
* chart both the distance
|
|
3186
|
+
* forward view plus an approximate distance-to-stage line. On a stageless
|
|
3187
|
+
* chart both the distance claim and the generic stage silhouette are
|
|
3108
3188
|
* invented promises, so we suppress them; a real attached photo always shows.
|
|
3109
3189
|
* (OV-52)
|
|
3110
3190
|
*/
|
|
@@ -3123,7 +3203,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3123
3203
|
return "";
|
|
3124
3204
|
}
|
|
3125
3205
|
}
|
|
3126
|
-
const sightHtml = hasStage ? `<div class="sl-confirm-sight"
|
|
3206
|
+
const sightHtml = hasStage && distance != null ? `<div class="sl-confirm-sight">${t2("picker.sightline", { m: distance })}</div>` : "";
|
|
3127
3207
|
const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" src="${realPhoto}" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button>` : `<button type="button" class="sl-confirm-view sl-confirm-viewbtn" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><span aria-hidden="true">\u{1F52D}</span><span>${this.tf("picker.viewFromHere", "View from here")}</span></button>`;
|
|
3128
3208
|
return viewBtn + sightHtml;
|
|
3129
3209
|
}
|
|
@@ -3257,6 +3337,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3257
3337
|
this.setFsFallback(false);
|
|
3258
3338
|
}
|
|
3259
3339
|
}
|
|
3340
|
+
syncFullscreenButtons() {
|
|
3341
|
+
const active = !!document.fullscreenElement || this.fsFallback || this.framedFs;
|
|
3342
|
+
this.els.zfs?.setAttribute("aria-pressed", String(active));
|
|
3343
|
+
this.view3dEl?.querySelector(".sl-view3d-fs")?.setAttribute("aria-pressed", String(active));
|
|
3344
|
+
}
|
|
3260
3345
|
/**
|
|
3261
3346
|
* Native element-fullscreen was unavailable or rejected. When framed, a CSS
|
|
3262
3347
|
* `.sl-fs` overlay can't escape the iframe, so we ask the host page to pin us
|
|
@@ -3271,7 +3356,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3271
3356
|
setFramedFs(on) {
|
|
3272
3357
|
if (this.framedFs === on) return;
|
|
3273
3358
|
this.framedFs = on;
|
|
3274
|
-
this.
|
|
3359
|
+
this.syncFullscreenButtons();
|
|
3275
3360
|
this.postToHost({ type: "seatlayer:fullscreen", on });
|
|
3276
3361
|
if (on && !this.fsEscHandler) {
|
|
3277
3362
|
this.fsEscHandler = (e) => {
|
|
@@ -3288,7 +3373,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3288
3373
|
if (this.fsFallback === on) return;
|
|
3289
3374
|
this.fsFallback = on;
|
|
3290
3375
|
this.root?.classList.toggle("sl-fs", on);
|
|
3291
|
-
this.
|
|
3376
|
+
this.syncFullscreenButtons();
|
|
3292
3377
|
if (on && !this.fsEscHandler) {
|
|
3293
3378
|
this.fsEscHandler = (e) => {
|
|
3294
3379
|
if (e.key === "Escape" && !document.fullscreenElement) this.setFsFallback(false);
|
|
@@ -3478,7 +3563,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3478
3563
|
this.els.zfs.addEventListener("click", () => this.toggleFullscreen());
|
|
3479
3564
|
this.fsChangeHandler = () => {
|
|
3480
3565
|
if (!document.fullscreenElement) this.setFsFallback(false);
|
|
3481
|
-
this.
|
|
3566
|
+
this.syncFullscreenButtons();
|
|
3482
3567
|
requestAnimationFrame(() => this.controller.zoomToFit());
|
|
3483
3568
|
};
|
|
3484
3569
|
document.addEventListener("fullscreenchange", this.fsChangeHandler);
|
|
@@ -3495,10 +3580,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
3495
3580
|
e.stopPropagation();
|
|
3496
3581
|
setSheet(root.dataset.sheet !== "open");
|
|
3497
3582
|
});
|
|
3583
|
+
this.els.peek?.addEventListener("click", (e) => {
|
|
3584
|
+
const go = e.target.closest(".sl-sheet-go");
|
|
3585
|
+
if (!go) return;
|
|
3586
|
+
e.stopPropagation();
|
|
3587
|
+
if (go.dataset.act === "checkout") void this.handleCta();
|
|
3588
|
+
else setSheet(true);
|
|
3589
|
+
});
|
|
3498
3590
|
let startY = 0;
|
|
3499
3591
|
let swiped = false;
|
|
3500
3592
|
let tracking = false;
|
|
3501
3593
|
head.addEventListener("pointerdown", (e) => {
|
|
3594
|
+
if (e.target.closest?.(".sl-seccard,.sl-sheet-toggle,.sl-sheet-go")) {
|
|
3595
|
+
tracking = false;
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3502
3598
|
tracking = true;
|
|
3503
3599
|
swiped = false;
|
|
3504
3600
|
startY = e.clientY;
|
|
@@ -3517,7 +3613,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3517
3613
|
});
|
|
3518
3614
|
head.addEventListener("pointerup", (e) => {
|
|
3519
3615
|
if (tracking && !swiped && Math.abs(e.clientY - startY) < 6) {
|
|
3520
|
-
|
|
3616
|
+
setSheet(root.dataset.sheet !== "open");
|
|
3521
3617
|
}
|
|
3522
3618
|
tracking = false;
|
|
3523
3619
|
head.releasePointerCapture?.(e.pointerId);
|
|
@@ -3570,7 +3666,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3570
3666
|
if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
|
|
3571
3667
|
else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
|
|
3572
3668
|
this.els.name.textContent = info.eventName ?? "";
|
|
3573
|
-
const when = info.startsAt ?
|
|
3669
|
+
const when = info.startsAt ? formatWhen(info.startsAt, info.timezone ?? null, this.opts.locale) : "";
|
|
3574
3670
|
this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
|
|
3575
3671
|
this.buildBadge(chartTheme);
|
|
3576
3672
|
const present = /* @__PURE__ */ new Set();
|
|
@@ -3711,12 +3807,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3711
3807
|
const narrow = this.root?.dataset.layout === "narrow";
|
|
3712
3808
|
const filters = this.els.filters;
|
|
3713
3809
|
if (filters) {
|
|
3810
|
+
if (this.cbEl) this.els.zoom?.appendChild(this.cbEl);
|
|
3714
3811
|
if (narrow) {
|
|
3715
3812
|
if (this.a11yChipsEl) filters.appendChild(this.a11yChipsEl);
|
|
3716
|
-
if (this.cbEl) filters.appendChild(this.cbEl);
|
|
3717
3813
|
} else {
|
|
3718
3814
|
if (this.a11yChipsEl) this.regions["top-left"]?.appendChild(this.a11yChipsEl);
|
|
3719
|
-
if (this.cbEl) this.els.zoom?.appendChild(this.cbEl);
|
|
3720
3815
|
}
|
|
3721
3816
|
const has = narrow && filters.children.length > 0;
|
|
3722
3817
|
filters.classList.toggle("has", has);
|
|
@@ -3823,8 +3918,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
3823
3918
|
if (this.badgeHidden(chartTheme)) return;
|
|
3824
3919
|
const foot = this.els.foot;
|
|
3825
3920
|
if (!foot) return;
|
|
3826
|
-
const el = document.createElement("
|
|
3921
|
+
const el = document.createElement("a");
|
|
3827
3922
|
el.className = "sl-powered";
|
|
3923
|
+
el.href = "https://seatlayer.io/?ref=picker";
|
|
3924
|
+
el.target = "_blank";
|
|
3925
|
+
el.rel = "noopener noreferrer";
|
|
3926
|
+
el.setAttribute("aria-label", this.tf("picker.poweredBy", "Powered by SeatLayer"));
|
|
3828
3927
|
el.innerHTML = `<span class="sl-powered-mark" aria-hidden="true">` + SEATLAYER_ATTRIBUTION_MARK_SVG + `</span><span>${this.tf("picker.poweredBy", "Powered by SeatLayer")}</span>`;
|
|
3829
3928
|
foot.appendChild(el);
|
|
3830
3929
|
}
|
|
@@ -4683,6 +4782,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4683
4782
|
el.querySelector(".sl-confirm-view")?.addEventListener("click", () => void this.openSeatView(seat));
|
|
4684
4783
|
el.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
|
|
4685
4784
|
if (this.buyerView === "venue3d") {
|
|
4785
|
+
this.dismissConfirm();
|
|
4686
4786
|
void this.view3dHandle?.flyToSeat(seat.id);
|
|
4687
4787
|
} else {
|
|
4688
4788
|
this.view3dReturnSeat = seat;
|
|
@@ -4761,13 +4861,24 @@ var SeatPicker = class _SeatPicker {
|
|
|
4761
4861
|
const doc = this.controller.doc;
|
|
4762
4862
|
const activeId = this.controller.getActiveFloorId();
|
|
4763
4863
|
const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
4764
|
-
let
|
|
4864
|
+
let panoSource;
|
|
4765
4865
|
let caption;
|
|
4766
4866
|
let real = false;
|
|
4767
4867
|
if (seat.viewUrl) {
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4868
|
+
const view = {
|
|
4869
|
+
url: seat.viewUrl,
|
|
4870
|
+
...seat.viewMeta?.previewUrl ? { previewUrl: seat.viewMeta.previewUrl } : {},
|
|
4871
|
+
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
4872
|
+
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
4873
|
+
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
4874
|
+
...seat.viewMeta?.previewHeight !== void 0 ? { previewHeight: seat.viewMeta.previewHeight } : {},
|
|
4875
|
+
...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
|
|
4876
|
+
...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
|
|
4877
|
+
...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
|
|
4878
|
+
};
|
|
4879
|
+
panoSource = view;
|
|
4880
|
+
caption = seatViewDisclosure(view);
|
|
4881
|
+
real = isAuthoredSeatView(view);
|
|
4771
4882
|
} else {
|
|
4772
4883
|
let pano2;
|
|
4773
4884
|
try {
|
|
@@ -4778,7 +4889,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4778
4889
|
return;
|
|
4779
4890
|
}
|
|
4780
4891
|
if (!this.root || !this.seatViewEnabled()) return;
|
|
4781
|
-
|
|
4892
|
+
panoSource = { url: pano2.url, generated: true };
|
|
4782
4893
|
caption = t2("picker.illustrationCaption", { m: pano2.distanceM });
|
|
4783
4894
|
}
|
|
4784
4895
|
this.closeSeatView();
|
|
@@ -4790,7 +4901,20 @@ var SeatPicker = class _SeatPicker {
|
|
|
4790
4901
|
this.root.appendChild(el);
|
|
4791
4902
|
this.viewEl = el;
|
|
4792
4903
|
const pano = el.querySelector(".sl-view-pano");
|
|
4793
|
-
|
|
4904
|
+
const delivery = planPanoramaDelivery(panoSource, browserPanoramaConstraints());
|
|
4905
|
+
const loadAbort = new AbortController();
|
|
4906
|
+
pano.style.backgroundImage = `url("${delivery.initialUrl}")`;
|
|
4907
|
+
let cancelUpgrade = () => {
|
|
4908
|
+
};
|
|
4909
|
+
if (delivery.upgradeUrl) {
|
|
4910
|
+
cancelUpgrade = schedulePanoramaUpgrade(() => {
|
|
4911
|
+
void loadPanoramaImage(delivery.upgradeUrl, loadAbort.signal).then(() => {
|
|
4912
|
+
if (!el.isConnected || loadAbort.signal.aborted) return;
|
|
4913
|
+
pano.style.backgroundImage = `url("${delivery.upgradeUrl}")`;
|
|
4914
|
+
}).catch(() => {
|
|
4915
|
+
});
|
|
4916
|
+
});
|
|
4917
|
+
}
|
|
4794
4918
|
const VFOV_DEG = 70;
|
|
4795
4919
|
const MAX_PITCH_DEG = 35;
|
|
4796
4920
|
let zoom = 1;
|
|
@@ -4852,6 +4976,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
4852
4976
|
el.addEventListener("keydown", onKey);
|
|
4853
4977
|
closeBtn.focus();
|
|
4854
4978
|
this.viewCleanup = () => {
|
|
4979
|
+
cancelUpgrade();
|
|
4980
|
+
loadAbort.abort();
|
|
4855
4981
|
pano.removeEventListener("pointerdown", onDown);
|
|
4856
4982
|
pano.removeEventListener("pointermove", onMove);
|
|
4857
4983
|
pano.removeEventListener("pointerup", onUp);
|
|
@@ -5216,10 +5342,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
5216
5342
|
if (previousCount === 0 && count > 0) this.animateOnce(this.els.cta, "sl-ready", 520);
|
|
5217
5343
|
if (this.els.peek) {
|
|
5218
5344
|
if (count) {
|
|
5219
|
-
|
|
5345
|
+
const holding = !!this.hold && !pendingCount;
|
|
5346
|
+
this.els.peek.innerHTML = `<span>${count} ${count === 1 ? "ticket" : "tickets"} \xB7 ${this.money(total)}</span><button type="button" class="go sl-sheet-go" data-act="${holding ? "checkout" : "open"}">${this.hold ? pendingCount ? "Secure more" : "Continue" : "Review"}</button>`;
|
|
5220
5347
|
} else {
|
|
5221
5348
|
const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
|
|
5222
|
-
this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<
|
|
5349
|
+
this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<button type="button" class="go sl-sheet-go" data-act="open">\u2726 Best seats</button>`;
|
|
5223
5350
|
}
|
|
5224
5351
|
}
|
|
5225
5352
|
this.lastTrayCount = count;
|
|
@@ -5655,6 +5782,58 @@ var SeatPicker = class _SeatPicker {
|
|
|
5655
5782
|
getSelection() {
|
|
5656
5783
|
return this.committedSelection();
|
|
5657
5784
|
}
|
|
5785
|
+
/**
|
|
5786
|
+
* Re-ink the DRAWN MAP after mount, so the canvas can follow a page palette
|
|
5787
|
+
* the host did not know at construction time.
|
|
5788
|
+
*
|
|
5789
|
+
* Deliberately narrower than the `theme` option: it takes only the map half.
|
|
5790
|
+
* The chrome half is CSS custom properties, which a host restyles from its
|
|
5791
|
+
* own stylesheet without asking the widget for anything — and a host that
|
|
5792
|
+
* used both mechanisms at once would have two things writing one token. This
|
|
5793
|
+
* method exists for the half CSS genuinely cannot reach: pixels Konva draws.
|
|
5794
|
+
*
|
|
5795
|
+
* The rebuild is the controller's (`setMapTheme`), which repaints statuses
|
|
5796
|
+
* from the map already in memory and restores the selection, so a buyer
|
|
5797
|
+
* mid-pick keeps their seats and no round trip is spent. A no-op when the
|
|
5798
|
+
* colours have not changed; safe to call on every render.
|
|
5799
|
+
*/
|
|
5800
|
+
setMapTheme(map) {
|
|
5801
|
+
if (this.destroyed) return;
|
|
5802
|
+
this.opts.theme = { ...this.opts.theme ?? {}, map: map ?? void 0 };
|
|
5803
|
+
this.controller.setMapTheme(map);
|
|
5804
|
+
}
|
|
5805
|
+
/**
|
|
5806
|
+
* Replace the host pricing override AFTER mount, and repaint everything that
|
|
5807
|
+
* shows a price.
|
|
5808
|
+
*
|
|
5809
|
+
* WHY IT CANNOT JUST BE A MOUNT OPTION. The prices a host knows are often not
|
|
5810
|
+
* the prices it knows AT MOUNT: SeatLayer's own event page learns them from
|
|
5811
|
+
* `GET /pub/events/:key/availability`, a separate cached read that lands a
|
|
5812
|
+
* round trip after the map does — deliberately, because the map is not
|
|
5813
|
+
* allowed to wait on it. Remounting the widget to hand it new options would
|
|
5814
|
+
* tear down a live hold, so the override is settable in place.
|
|
5815
|
+
*
|
|
5816
|
+
* The prices themselves are still the SERVER'S. This method takes an answer;
|
|
5817
|
+
* it never derives one. Nothing here re-reads a release, a window or a quota
|
|
5818
|
+
* — see `paidPrice`, the one funnel every displayed price flows through, and
|
|
5819
|
+
* note that the checkout handoff's line items do not come from here at all:
|
|
5820
|
+
* they are priced by the worker from its own hold. So the worst a wrong call
|
|
5821
|
+
* to this method can do is misprint a price, never mischarge one.
|
|
5822
|
+
*
|
|
5823
|
+
* A no-op when the map is unchanged, so a host may call it on every poll.
|
|
5824
|
+
*/
|
|
5825
|
+
setPricing(pricing) {
|
|
5826
|
+
const before = JSON.stringify(this.opts.pricing ?? null);
|
|
5827
|
+
const after = JSON.stringify(pricing ?? null);
|
|
5828
|
+
if (before === after) return;
|
|
5829
|
+
this.opts.pricing = pricing;
|
|
5830
|
+
if (this.destroyed || !this.els.prices) return;
|
|
5831
|
+
this.els.pricesSec?.querySelector(".sl-price-select")?.remove();
|
|
5832
|
+
this.buildPriceFilter();
|
|
5833
|
+
this.syncPrices();
|
|
5834
|
+
this.syncTray();
|
|
5835
|
+
if (this.lastSection) this.showSectionCard(this.lastSection);
|
|
5836
|
+
}
|
|
5658
5837
|
/** Current colorblind-safe render state, resolved from the stored buyer
|
|
5659
5838
|
* preference at mount. Host chrome (e.g. the Designer preview) reads this to
|
|
5660
5839
|
* surface the state rather than rendering colorblind colors silently. */
|
|
@@ -5706,10 +5885,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5706
5885
|
*
|
|
5707
5886
|
* Entering `'venue3d'` with `opts.flyToSeatId` runs the cinematic tour: the
|
|
5708
5887
|
* camera flies to the seat and holds in the live scene (a chip offers the
|
|
5709
|
-
* 360° view-from-seat)
|
|
5710
|
-
* the widget enters 3D and auto-flies the camera to that seat, dissolving into
|
|
5711
|
-
* its view-from-seat panorama (the same chain as tapping "See the view from
|
|
5712
|
-
* here" on a seat's confirm card). When the widget is **already** in the 3D
|
|
5888
|
+
* 360° view-from-seat). When the widget is **already** in the 3D
|
|
5713
5889
|
* view, the camera simply flies to the requested seat — the GL scene is not
|
|
5714
5890
|
* torn down or rebuilt, so there is no flash or re-entry.
|
|
5715
5891
|
*
|
|
@@ -5718,7 +5894,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5718
5894
|
*
|
|
5719
5895
|
* @param view `'map'` for the flat picker, `'venue3d'` for the 3D venue.
|
|
5720
5896
|
* @param opts.flyToSeatId When entering (or already in) `'venue3d'`, the seat
|
|
5721
|
-
* id to fly the camera to — cinematic →
|
|
5897
|
+
* id to fly the camera to — cinematic → live seat view.
|
|
5722
5898
|
* Ignored when `view` is `'map'`.
|
|
5723
5899
|
*
|
|
5724
5900
|
* @example
|
|
@@ -5732,7 +5908,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
5732
5908
|
}
|
|
5733
5909
|
const flyToSeatId = opts?.flyToSeatId;
|
|
5734
5910
|
if (this.buyerView === "venue3d") {
|
|
5735
|
-
if (flyToSeatId)
|
|
5911
|
+
if (flyToSeatId) {
|
|
5912
|
+
this.opts.onBuyerViewChange?.({ view: "venue3d", seatId: flyToSeatId });
|
|
5913
|
+
void this.view3dHandle?.flyToSeat(flyToSeatId);
|
|
5914
|
+
} else if (opts?.resetView) {
|
|
5915
|
+
this.view3dHandle?.focusOverview();
|
|
5916
|
+
}
|
|
5736
5917
|
return;
|
|
5737
5918
|
}
|
|
5738
5919
|
void this.enter3d(flyToSeatId);
|
|
@@ -5772,14 +5953,26 @@ var SeatPicker = class _SeatPicker {
|
|
|
5772
5953
|
async seatViewFor3d(seatId) {
|
|
5773
5954
|
const seat = this.allSeats().find((s) => s.id === seatId);
|
|
5774
5955
|
if (!seat) return null;
|
|
5775
|
-
if (seat.viewUrl) return {
|
|
5956
|
+
if (seat.viewUrl) return {
|
|
5957
|
+
url: seat.viewUrl,
|
|
5958
|
+
...seat.viewMeta?.previewUrl ? { previewUrl: seat.viewMeta.previewUrl } : {},
|
|
5959
|
+
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
5960
|
+
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
5961
|
+
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
5962
|
+
...seat.viewMeta?.previewHeight !== void 0 ? { previewHeight: seat.viewMeta.previewHeight } : {},
|
|
5963
|
+
...seat.viewMeta?.initialBearingDeg !== void 0 ? { initialBearingDeg: seat.viewMeta.initialBearingDeg } : {},
|
|
5964
|
+
...seat.viewMeta?.initialPitchDeg !== void 0 ? { initialPitchDeg: seat.viewMeta.initialPitchDeg } : {},
|
|
5965
|
+
...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
|
|
5966
|
+
...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
|
|
5967
|
+
...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
|
|
5968
|
+
};
|
|
5776
5969
|
const doc = this.controller.doc;
|
|
5777
5970
|
if (!doc) return null;
|
|
5778
5971
|
const activeId = this.controller.getActiveFloorId();
|
|
5779
5972
|
const focal = seat.focalPoint ?? doc.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc.focalPoint ?? { x: 0, y: 0 };
|
|
5780
5973
|
try {
|
|
5781
5974
|
const { generateSeatPanorama } = await loadPanorama();
|
|
5782
|
-
return { url: generateSeatPanorama(seat, focal, this.allSeats()).url };
|
|
5975
|
+
return { url: generateSeatPanorama(seat, focal, this.allSeats()).url, generated: true };
|
|
5783
5976
|
} catch {
|
|
5784
5977
|
return null;
|
|
5785
5978
|
}
|
|
@@ -5801,14 +5994,26 @@ var SeatPicker = class _SeatPicker {
|
|
|
5801
5994
|
}
|
|
5802
5995
|
const seat = this.allSeats().find((s) => s.id === seatId);
|
|
5803
5996
|
if (!seat) return;
|
|
5804
|
-
const
|
|
5997
|
+
const table = this.controller.tableSelection(seatId);
|
|
5998
|
+
const already = this.committedSelection().some((s) => table ? s.label === table.label : s.id === seatId);
|
|
5805
5999
|
if (already) {
|
|
5806
6000
|
this.controller.deselect([seatId]);
|
|
5807
6001
|
this.dismissConfirm();
|
|
5808
6002
|
return;
|
|
5809
6003
|
}
|
|
6004
|
+
const added = this.controller.select([seatId]);
|
|
6005
|
+
if (!added.length) {
|
|
6006
|
+
this.syncSelectionTo3d();
|
|
6007
|
+
this.dismissConfirm();
|
|
6008
|
+
return;
|
|
6009
|
+
}
|
|
6010
|
+
this.opts.onBuyerViewChange?.({ view: "venue3d", seatId });
|
|
5810
6011
|
this.flashPickedSeat(seatId);
|
|
5811
|
-
|
|
6012
|
+
if (table) {
|
|
6013
|
+
this.showTableDialog(table, false);
|
|
6014
|
+
this.syncSelectionTo3d();
|
|
6015
|
+
return;
|
|
6016
|
+
}
|
|
5812
6017
|
if (this.opts.confirmSelection !== false) this.showConfirm(seat);
|
|
5813
6018
|
else this.syncTray();
|
|
5814
6019
|
}
|
|
@@ -5828,12 +6033,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
5828
6033
|
*/
|
|
5829
6034
|
buildView3dNav(overlay, handle) {
|
|
5830
6035
|
const floors = handle.floors();
|
|
5831
|
-
const
|
|
5832
|
-
const
|
|
6036
|
+
const floorLabels = new Set(floors.map((floor) => floor.label.trim().toLocaleLowerCase()));
|
|
6037
|
+
const zones = handle.zones().filter((zone) => zone.seatCount > 0 && !floorLabels.has(zone.label.trim().toLocaleLowerCase()));
|
|
6038
|
+
const allSections = handle.sections().filter((s) => s.seatCount > 0);
|
|
6039
|
+
const sections = zones.length > 1 ? [] : allSections;
|
|
5833
6040
|
const wantFloors = floors.length > 1;
|
|
5834
6041
|
const wantZones = zones.length > 1;
|
|
5835
6042
|
const wantSections = sections.length > 1;
|
|
5836
|
-
|
|
6043
|
+
const wantLocator = allSections.length > 0 && handle.rows().length > 0;
|
|
6044
|
+
if (!wantFloors && !wantZones && !wantSections && !wantLocator) return;
|
|
5837
6045
|
const nav = document.createElement("div");
|
|
5838
6046
|
nav.className = "sl-view3d-nav";
|
|
5839
6047
|
if (wantFloors) {
|
|
@@ -5870,15 +6078,112 @@ var SeatPicker = class _SeatPicker {
|
|
|
5870
6078
|
} })) : sections.map((s) => ({ id: s.id, label: s.label || s.id, go: () => {
|
|
5871
6079
|
handle.focusSection(s.id);
|
|
5872
6080
|
} }));
|
|
5873
|
-
|
|
5874
|
-
const
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
6081
|
+
if (!wantZones && entries.length > MAX_3D_SECTION_PILLS) {
|
|
6082
|
+
const select = document.createElement("select");
|
|
6083
|
+
select.setAttribute("aria-label", this.tf("picker.jumpToSection", "Jump to section"));
|
|
6084
|
+
const placeholder = document.createElement("option");
|
|
6085
|
+
placeholder.value = "";
|
|
6086
|
+
placeholder.textContent = this.tf("picker.jumpToSection", "Jump to section");
|
|
6087
|
+
select.appendChild(placeholder);
|
|
6088
|
+
for (const entry of entries) {
|
|
6089
|
+
const option = document.createElement("option");
|
|
6090
|
+
option.value = entry.id;
|
|
6091
|
+
option.textContent = entry.label;
|
|
6092
|
+
select.appendChild(option);
|
|
6093
|
+
}
|
|
6094
|
+
select.addEventListener("change", () => {
|
|
6095
|
+
entries.find((entry) => entry.id === select.value)?.go();
|
|
6096
|
+
});
|
|
6097
|
+
row.appendChild(select);
|
|
6098
|
+
} else {
|
|
6099
|
+
for (const e of entries) {
|
|
6100
|
+
const b = document.createElement("button");
|
|
6101
|
+
b.type = "button";
|
|
6102
|
+
b.textContent = e.label;
|
|
6103
|
+
b.addEventListener("click", e.go);
|
|
6104
|
+
row.appendChild(b);
|
|
6105
|
+
}
|
|
5879
6106
|
}
|
|
5880
6107
|
nav.appendChild(row);
|
|
5881
6108
|
}
|
|
6109
|
+
if (wantLocator) {
|
|
6110
|
+
const locator = document.createElement("div");
|
|
6111
|
+
locator.className = "sl-view3d-locator";
|
|
6112
|
+
locator.setAttribute("role", "group");
|
|
6113
|
+
locator.setAttribute("aria-label", "Find an exact seat in 3D");
|
|
6114
|
+
const sectionSelect = document.createElement("select");
|
|
6115
|
+
sectionSelect.setAttribute("aria-label", "Choose section in 3D");
|
|
6116
|
+
const rowSelect = document.createElement("select");
|
|
6117
|
+
rowSelect.setAttribute("aria-label", "Choose row in 3D");
|
|
6118
|
+
const seatSelect = document.createElement("select");
|
|
6119
|
+
seatSelect.setAttribute("aria-label", "Choose seat in 3D");
|
|
6120
|
+
const view = document.createElement("button");
|
|
6121
|
+
view.type = "button";
|
|
6122
|
+
view.textContent = "View seat";
|
|
6123
|
+
view.disabled = true;
|
|
6124
|
+
const fill = (select, placeholder, entries) => {
|
|
6125
|
+
select.replaceChildren();
|
|
6126
|
+
const first = document.createElement("option");
|
|
6127
|
+
first.value = "";
|
|
6128
|
+
first.textContent = placeholder;
|
|
6129
|
+
select.appendChild(first);
|
|
6130
|
+
for (const entry of entries) {
|
|
6131
|
+
const option = document.createElement("option");
|
|
6132
|
+
option.value = entry.id;
|
|
6133
|
+
option.textContent = entry.label;
|
|
6134
|
+
select.appendChild(option);
|
|
6135
|
+
}
|
|
6136
|
+
select.value = "";
|
|
6137
|
+
};
|
|
6138
|
+
fill(sectionSelect, "1. Section", allSections.map((section) => ({
|
|
6139
|
+
id: section.id,
|
|
6140
|
+
label: `${section.label} \xB7 ${section.seatCount.toLocaleString()} seats`
|
|
6141
|
+
})));
|
|
6142
|
+
fill(rowSelect, "2. Row", []);
|
|
6143
|
+
fill(seatSelect, "3. Seat", []);
|
|
6144
|
+
rowSelect.disabled = true;
|
|
6145
|
+
seatSelect.disabled = true;
|
|
6146
|
+
sectionSelect.addEventListener("change", () => {
|
|
6147
|
+
const sectionId = sectionSelect.value;
|
|
6148
|
+
view.disabled = true;
|
|
6149
|
+
fill(seatSelect, "3. Seat", []);
|
|
6150
|
+
seatSelect.disabled = true;
|
|
6151
|
+
if (!sectionId || !handle.focusSection(sectionId)) {
|
|
6152
|
+
fill(rowSelect, "2. Row", []);
|
|
6153
|
+
rowSelect.disabled = true;
|
|
6154
|
+
return;
|
|
6155
|
+
}
|
|
6156
|
+
const rows = handle.rows(sectionId);
|
|
6157
|
+
fill(rowSelect, "2. Row", rows.map((row) => ({
|
|
6158
|
+
id: row.id,
|
|
6159
|
+
label: `${row.label} \xB7 ${row.seatCount} seats`
|
|
6160
|
+
})));
|
|
6161
|
+
rowSelect.disabled = rows.length === 0;
|
|
6162
|
+
});
|
|
6163
|
+
rowSelect.addEventListener("change", () => {
|
|
6164
|
+
const rowId = rowSelect.value;
|
|
6165
|
+
view.disabled = true;
|
|
6166
|
+
if (!rowId || !handle.focusRow(rowId)) {
|
|
6167
|
+
fill(seatSelect, "3. Seat", []);
|
|
6168
|
+
seatSelect.disabled = true;
|
|
6169
|
+
return;
|
|
6170
|
+
}
|
|
6171
|
+
const seats = handle.seatsInRow(rowId);
|
|
6172
|
+
fill(seatSelect, "3. Seat", seats);
|
|
6173
|
+
seatSelect.disabled = seats.length === 0;
|
|
6174
|
+
});
|
|
6175
|
+
seatSelect.addEventListener("change", () => {
|
|
6176
|
+
const seatId = seatSelect.value;
|
|
6177
|
+
view.disabled = !seatId;
|
|
6178
|
+
handle.setSelection(seatId ? [seatId] : []);
|
|
6179
|
+
});
|
|
6180
|
+
view.addEventListener("click", () => {
|
|
6181
|
+
const seatId = seatSelect.value;
|
|
6182
|
+
if (seatId) void handle.flyToSeat(seatId);
|
|
6183
|
+
});
|
|
6184
|
+
locator.append(sectionSelect, rowSelect, seatSelect, view);
|
|
6185
|
+
nav.appendChild(locator);
|
|
6186
|
+
}
|
|
5882
6187
|
overlay.appendChild(nav);
|
|
5883
6188
|
}
|
|
5884
6189
|
async enter3d(flySeatId) {
|
|
@@ -5886,6 +6191,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5886
6191
|
const doc = this.controller.doc;
|
|
5887
6192
|
if (!doc) return;
|
|
5888
6193
|
this.buyerView = "venue3d";
|
|
6194
|
+
this.opts.onBuyerViewChange?.({ view: "venue3d", ...flySeatId ? { seatId: flySeatId } : {} });
|
|
5889
6195
|
this.root?.setAttribute("data-view3d", "on");
|
|
5890
6196
|
this.dismissConfirm();
|
|
5891
6197
|
this.syncProjection();
|
|
@@ -5899,6 +6205,20 @@ var SeatPicker = class _SeatPicker {
|
|
|
5899
6205
|
back.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg><span>${this.tf("picker.backToMap", "Back to map")}</span>`;
|
|
5900
6206
|
back.addEventListener("click", () => this.exit3d());
|
|
5901
6207
|
overlay.appendChild(back);
|
|
6208
|
+
const fullscreen = document.createElement("button");
|
|
6209
|
+
fullscreen.type = "button";
|
|
6210
|
+
fullscreen.className = "sl-view3d-fs";
|
|
6211
|
+
fullscreen.textContent = "\u26F6";
|
|
6212
|
+
fullscreen.setAttribute("aria-label", "Full screen");
|
|
6213
|
+
fullscreen.setAttribute("aria-pressed", String(!!document.fullscreenElement || this.fsFallback || this.framedFs));
|
|
6214
|
+
fullscreen.addEventListener("click", () => this.toggleFullscreen());
|
|
6215
|
+
overlay.appendChild(fullscreen);
|
|
6216
|
+
const loading = document.createElement("div");
|
|
6217
|
+
loading.className = "sl-view3d-loading";
|
|
6218
|
+
loading.setAttribute("role", "status");
|
|
6219
|
+
loading.setAttribute("aria-live", "polite");
|
|
6220
|
+
loading.textContent = this.tf("picker.loading3d", "Building the 3D venue\u2026");
|
|
6221
|
+
overlay.appendChild(loading);
|
|
5902
6222
|
this.els.map.appendChild(overlay);
|
|
5903
6223
|
this.view3dEl = overlay;
|
|
5904
6224
|
requestAnimationFrame(() => {
|
|
@@ -5909,8 +6229,30 @@ var SeatPicker = class _SeatPicker {
|
|
|
5909
6229
|
const seats = expandChart(doc);
|
|
5910
6230
|
const mod = await loadVenue3d();
|
|
5911
6231
|
if (gen !== this.view3dGen || this.buyerView !== "venue3d" || this.view3dEl !== overlay) return;
|
|
5912
|
-
const
|
|
6232
|
+
const prepared = await mod.prepareVenue3D({ doc, seats });
|
|
6233
|
+
if (gen !== this.view3dGen || this.buyerView !== "venue3d" || this.view3dEl !== overlay) return;
|
|
6234
|
+
const handle = mod.mountVenue3D(overlay, { doc, seats, prepared }, {
|
|
6235
|
+
// A strict contain fit turns a wide bowl into a postage stamp inside a
|
|
6236
|
+
// phone. The bounded portrait fit was validated against every UX-lab
|
|
6237
|
+
// fixture; keep editor previews strict while making buyer seats legible.
|
|
6238
|
+
portraitOverviewCrop: true,
|
|
5913
6239
|
onSeatPick: (id) => this.onView3dSeatPick(id),
|
|
6240
|
+
onSectionFocusChange: (sectionId) => {
|
|
6241
|
+
const select = overlay.querySelector(
|
|
6242
|
+
'select[aria-label="Choose section in 3D"]'
|
|
6243
|
+
);
|
|
6244
|
+
const next = sectionId ?? "";
|
|
6245
|
+
if (!select || select.value === next) return;
|
|
6246
|
+
select.value = next;
|
|
6247
|
+
select.dispatchEvent(new Event("change"));
|
|
6248
|
+
},
|
|
6249
|
+
onViewTargetChange: (seatId) => {
|
|
6250
|
+
overlay.querySelector(".sl-view3d-nav")?.classList.toggle("is-seat-focused", !!seatId);
|
|
6251
|
+
this.opts.onBuyerViewChange?.({
|
|
6252
|
+
view: "venue3d",
|
|
6253
|
+
...seatId ? { seatId } : {}
|
|
6254
|
+
});
|
|
6255
|
+
},
|
|
5914
6256
|
// Deferred off the tap gesture: generateSeatPanorama walks every seat
|
|
5915
6257
|
// (O(n) on a 13k chart), and the module prefetches at pick — by the
|
|
5916
6258
|
// time the flight lands (~2.5s) the idle render has long finished. A
|
|
@@ -5930,12 +6272,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
5930
6272
|
onAnalytics: (event, props) => this.emit3dAnalytics(event, props)
|
|
5931
6273
|
});
|
|
5932
6274
|
this.view3dHandle = handle;
|
|
6275
|
+
loading.remove();
|
|
5933
6276
|
this.pushAvailabilityTo3d();
|
|
5934
6277
|
this.syncSelectionTo3d();
|
|
5935
6278
|
this.buildView3dNav(overlay, handle);
|
|
5936
6279
|
if (flySeatId) void handle.flyToSeat(flySeatId);
|
|
5937
6280
|
} catch (err) {
|
|
6281
|
+
if (gen !== this.view3dGen || this.buyerView !== "venue3d" || this.view3dEl !== overlay) return;
|
|
5938
6282
|
this.opts.onError?.(err);
|
|
6283
|
+
this.toast(this.tf("picker.unavailable3d", "3D could not start. The seat map is still available."), "warning");
|
|
5939
6284
|
this.exit3d();
|
|
5940
6285
|
}
|
|
5941
6286
|
}
|
|
@@ -5943,6 +6288,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5943
6288
|
if (this.buyerView !== "venue3d" && !this.view3dEl) return;
|
|
5944
6289
|
this.view3dGen++;
|
|
5945
6290
|
this.buyerView = "map";
|
|
6291
|
+
this.opts.onBuyerViewChange?.({ view: "map" });
|
|
5946
6292
|
this.root?.removeAttribute("data-view3d");
|
|
5947
6293
|
try {
|
|
5948
6294
|
this.view3dHandle?.dispose();
|