@seatlayer/js 0.30.0 → 0.31.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 +1 -0
- package/dist/index.cjs +127 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -2
- package/dist/index.d.ts +35 -2
- package/dist/index.js +127 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -667,6 +667,16 @@ interface SeatPickerOptions {
|
|
|
667
667
|
* dynamically imported on first use). Set false for embed hosts that must
|
|
668
668
|
* stay strictly 2D. */
|
|
669
669
|
enable3D?: boolean;
|
|
670
|
+
/**
|
|
671
|
+
* Seat-count ceiling above which 3D is not offered. Default: the measured-good
|
|
672
|
+
* ceiling of 15,000 seats (14,142 renders at 58 fps), halved to 7,500 on a
|
|
673
|
+
* device that reports itself as small/low-core.
|
|
674
|
+
*
|
|
675
|
+
* The 3D scene holds EVERY seat resident — there is no streaming rung yet — so
|
|
676
|
+
* a 53,000-seat chart builds its scene in ~780 ms on a desktop and much worse
|
|
677
|
+
* on a phone. Offering 3D there is offering a stall. A host that knows its
|
|
678
|
+
* audience can raise this; the ceiling goes away when seat streaming lands. */
|
|
679
|
+
max3DSeats?: number;
|
|
670
680
|
/**
|
|
671
681
|
* Optional analytics sink for the widget's own journey events. Currently emits
|
|
672
682
|
* the 3D venue-view journey (`3d_opened`, `3d_orbit_engaged`, `3d_seat_picked`,
|
|
@@ -821,6 +831,8 @@ declare class SeatPicker {
|
|
|
821
831
|
private miniTf;
|
|
822
832
|
private priceBandKeys;
|
|
823
833
|
private focusedCatKey;
|
|
834
|
+
/** "Hide limited-view seats" — mirrored into 3D by `seatState3dFor`. */
|
|
835
|
+
private limitedViewFilter;
|
|
824
836
|
private pricesExpanded;
|
|
825
837
|
/** Last surfaced section summary (re-rendered when the price band changes). */
|
|
826
838
|
private lastSection;
|
|
@@ -1046,9 +1058,15 @@ declare class SeatPicker {
|
|
|
1046
1058
|
/** Reflect the engine's current LOD rung onto the pill group. */
|
|
1047
1059
|
private syncRung;
|
|
1048
1060
|
private syncProjection;
|
|
1049
|
-
/** Can this picker offer the 3D venue view? Requires the option (default on)
|
|
1050
|
-
*
|
|
1061
|
+
/** Can this picker offer the 3D venue view? Requires the option (default on),
|
|
1062
|
+
* WebGL2, a chart to render, and a chart small enough to render WELL. */
|
|
1051
1063
|
private canOffer3d;
|
|
1064
|
+
/**
|
|
1065
|
+
* The seat ceiling for offering 3D. See `max3DSeats` — an explicit host value
|
|
1066
|
+
* always wins; otherwise a small/low-core device gets half the desktop budget,
|
|
1067
|
+
* because it is the device that turns "slow" into "stalled".
|
|
1068
|
+
*/
|
|
1069
|
+
private max3dSeats;
|
|
1052
1070
|
/** Reflect the active floor onto the switcher rail. */
|
|
1053
1071
|
private syncFloors;
|
|
1054
1072
|
/** Show (or clear, on null) the tapped-section summary card. */
|
|
@@ -1220,6 +1238,21 @@ declare class SeatPicker {
|
|
|
1220
1238
|
/** A 3D seat tap runs the SAME selection path as a 2D tap: toggle through the
|
|
1221
1239
|
* controller, then raise the shared confirm card (bottom-sheeted in 3D). */
|
|
1222
1240
|
private onView3dSeatPick;
|
|
1241
|
+
/**
|
|
1242
|
+
* The venue-navigation rail inside 3D: levels and areas.
|
|
1243
|
+
*
|
|
1244
|
+
* In 2D a buyer moves through the venue by floor switcher and by the LOD
|
|
1245
|
+
* rungs. Both are hidden while immersed, and the 3D module's own chips only
|
|
1246
|
+
* cover "go home" and "see the 360" — so on a multi-floor or multi-zone chart
|
|
1247
|
+
* the buyer entered 3D and LOST the ability to reach the level or area they
|
|
1248
|
+
* were booking. The camera moves for both already exist on the handle
|
|
1249
|
+
* (`focusFloor` / `focusZone`); this is the surface that offers them.
|
|
1250
|
+
*
|
|
1251
|
+
* Levels are a TOGGLE (a floor stays isolated until you pick another), areas
|
|
1252
|
+
* are an ACTION (the camera flies there and you are then free to orbit), which
|
|
1253
|
+
* is why only the level pills carry a pressed state.
|
|
1254
|
+
*/
|
|
1255
|
+
private buildView3dNav;
|
|
1223
1256
|
private enter3d;
|
|
1224
1257
|
private exit3d;
|
|
1225
1258
|
/** Current active/restored hold reflected in the tray. */
|
package/dist/index.d.ts
CHANGED
|
@@ -667,6 +667,16 @@ interface SeatPickerOptions {
|
|
|
667
667
|
* dynamically imported on first use). Set false for embed hosts that must
|
|
668
668
|
* stay strictly 2D. */
|
|
669
669
|
enable3D?: boolean;
|
|
670
|
+
/**
|
|
671
|
+
* Seat-count ceiling above which 3D is not offered. Default: the measured-good
|
|
672
|
+
* ceiling of 15,000 seats (14,142 renders at 58 fps), halved to 7,500 on a
|
|
673
|
+
* device that reports itself as small/low-core.
|
|
674
|
+
*
|
|
675
|
+
* The 3D scene holds EVERY seat resident — there is no streaming rung yet — so
|
|
676
|
+
* a 53,000-seat chart builds its scene in ~780 ms on a desktop and much worse
|
|
677
|
+
* on a phone. Offering 3D there is offering a stall. A host that knows its
|
|
678
|
+
* audience can raise this; the ceiling goes away when seat streaming lands. */
|
|
679
|
+
max3DSeats?: number;
|
|
670
680
|
/**
|
|
671
681
|
* Optional analytics sink for the widget's own journey events. Currently emits
|
|
672
682
|
* the 3D venue-view journey (`3d_opened`, `3d_orbit_engaged`, `3d_seat_picked`,
|
|
@@ -821,6 +831,8 @@ declare class SeatPicker {
|
|
|
821
831
|
private miniTf;
|
|
822
832
|
private priceBandKeys;
|
|
823
833
|
private focusedCatKey;
|
|
834
|
+
/** "Hide limited-view seats" — mirrored into 3D by `seatState3dFor`. */
|
|
835
|
+
private limitedViewFilter;
|
|
824
836
|
private pricesExpanded;
|
|
825
837
|
/** Last surfaced section summary (re-rendered when the price band changes). */
|
|
826
838
|
private lastSection;
|
|
@@ -1046,9 +1058,15 @@ declare class SeatPicker {
|
|
|
1046
1058
|
/** Reflect the engine's current LOD rung onto the pill group. */
|
|
1047
1059
|
private syncRung;
|
|
1048
1060
|
private syncProjection;
|
|
1049
|
-
/** Can this picker offer the 3D venue view? Requires the option (default on)
|
|
1050
|
-
*
|
|
1061
|
+
/** Can this picker offer the 3D venue view? Requires the option (default on),
|
|
1062
|
+
* WebGL2, a chart to render, and a chart small enough to render WELL. */
|
|
1051
1063
|
private canOffer3d;
|
|
1064
|
+
/**
|
|
1065
|
+
* The seat ceiling for offering 3D. See `max3DSeats` — an explicit host value
|
|
1066
|
+
* always wins; otherwise a small/low-core device gets half the desktop budget,
|
|
1067
|
+
* because it is the device that turns "slow" into "stalled".
|
|
1068
|
+
*/
|
|
1069
|
+
private max3dSeats;
|
|
1052
1070
|
/** Reflect the active floor onto the switcher rail. */
|
|
1053
1071
|
private syncFloors;
|
|
1054
1072
|
/** Show (or clear, on null) the tapped-section summary card. */
|
|
@@ -1220,6 +1238,21 @@ declare class SeatPicker {
|
|
|
1220
1238
|
/** A 3D seat tap runs the SAME selection path as a 2D tap: toggle through the
|
|
1221
1239
|
* controller, then raise the shared confirm card (bottom-sheeted in 3D). */
|
|
1222
1240
|
private onView3dSeatPick;
|
|
1241
|
+
/**
|
|
1242
|
+
* The venue-navigation rail inside 3D: levels and areas.
|
|
1243
|
+
*
|
|
1244
|
+
* In 2D a buyer moves through the venue by floor switcher and by the LOD
|
|
1245
|
+
* rungs. Both are hidden while immersed, and the 3D module's own chips only
|
|
1246
|
+
* cover "go home" and "see the 360" — so on a multi-floor or multi-zone chart
|
|
1247
|
+
* the buyer entered 3D and LOST the ability to reach the level or area they
|
|
1248
|
+
* were booking. The camera moves for both already exist on the handle
|
|
1249
|
+
* (`focusFloor` / `focusZone`); this is the surface that offers them.
|
|
1250
|
+
*
|
|
1251
|
+
* Levels are a TOGGLE (a floor stays isolated until you pick another), areas
|
|
1252
|
+
* are an ACTION (the camera flies there and you are then free to orbit), which
|
|
1253
|
+
* is why only the level pills carry a pressed state.
|
|
1254
|
+
*/
|
|
1255
|
+
private buildView3dNav;
|
|
1223
1256
|
private enter3d;
|
|
1224
1257
|
private exit3d;
|
|
1225
1258
|
/** Current active/restored hold reflected in the tray. */
|
package/dist/index.js
CHANGED
|
@@ -93,6 +93,9 @@ var PubApi = class {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
// src/seatLayerBrand.ts
|
|
97
|
+
var SEATLAYER_ATTRIBUTION_MARK_SVG = '<svg viewBox="0 0 64 56" width="12" height="11" fill="none" aria-hidden="true" focusable="false" style="display:block"><path d="M4 13 Q16 6 29 7 L28.5 17 Q17 16.5 7 22 Z" fill="#f4b740"/><path d="M4 13 Q16 6 29 7 L28.5 17 Q17 16.5 7 22 Z" fill="#f4b740" transform="translate(64 0) scale(-1 1)"/><path d="M8 28 Q18 22 28.6 23 L28.2 32 Q18 28.5 10 36 Z" fill="#fcf7ee"/><path d="M8 28 Q18 22 28.6 23 L28.2 32 Q18 28.5 10 36 Z" fill="#fcf7ee" transform="translate(64 0) scale(-1 1)"/><path d="M11 41 Q19 35 28.2 36 L27.8 45 Q19.5 42 13.5 49 Z" fill="#fcf7ee"/><path d="M11 41 Q19 35 28.2 36 L27.8 45 Q19.5 42 13.5 49 Z" fill="#fcf7ee" transform="translate(64 0) scale(-1 1)"/></svg>';
|
|
98
|
+
|
|
96
99
|
// src/SeatingChart.ts
|
|
97
100
|
var DEFAULT_API_BASE = "https://api.seatlayer.io";
|
|
98
101
|
var DEFAULT_MAX_SELECTION = 10;
|
|
@@ -208,7 +211,7 @@ var SeatingChart = class {
|
|
|
208
211
|
badge.rel = "noopener noreferrer";
|
|
209
212
|
badge.setAttribute("aria-label", t("picker.poweredBy"));
|
|
210
213
|
badge.style.cssText = 'position:absolute;bottom:10px;right:12px;z-index:5;display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border-radius:999px;background:rgba(255,255,255,.92);color:#4a5163;text-decoration:none;font:600 11px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:.02em;box-shadow:0 2px 8px rgba(0,0,0,.12);';
|
|
211
|
-
badge.innerHTML =
|
|
214
|
+
badge.innerHTML = '<span aria-hidden="true" style="width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;background:#0c1220;color:#fcf7ee">' + SEATLAYER_ATTRIBUTION_MARK_SVG + `</span><span>${t("picker.poweredBy")}</span>`;
|
|
212
215
|
host.appendChild(badge);
|
|
213
216
|
}
|
|
214
217
|
placeTooltip() {
|
|
@@ -1078,6 +1081,8 @@ import {
|
|
|
1078
1081
|
var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
|
|
1079
1082
|
var DEFAULT_MAX_SELECTION2 = 10;
|
|
1080
1083
|
var EXTEND_PROMPT_MS = 6e4;
|
|
1084
|
+
var MAX_3D_SEATS_DEFAULT = 15e3;
|
|
1085
|
+
var MAX_3D_SECTION_PILLS = 12;
|
|
1081
1086
|
function pointInPolygon(x, y, poly) {
|
|
1082
1087
|
let inside = false;
|
|
1083
1088
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
@@ -1497,14 +1502,14 @@ var CSS = `
|
|
|
1497
1502
|
.sl-closed-pill.on{display:inline-flex}
|
|
1498
1503
|
.sl-closed-pill svg{width:13px;height:13px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
1499
1504
|
|
|
1500
|
-
/* "Powered by SeatLayer" attribution badge (side-panel foot) \u2014 the
|
|
1501
|
-
|
|
1505
|
+
/* "Powered by SeatLayer" attribution badge (side-panel foot) \u2014 the canonical
|
|
1506
|
+
Layered Rows mark + wordmark. Hidden when the host opts out or the org's paid
|
|
1502
1507
|
theme sets hideBadge. */
|
|
1503
1508
|
.sl-powered{display:flex;align-items:center;justify-content:center;gap:6px;margin-top:10px;
|
|
1504
1509
|
font-size:11px;letter-spacing:.03em;color:var(--sl-muted)}
|
|
1505
1510
|
.sl-powered-mark{width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;
|
|
1506
|
-
background
|
|
1507
|
-
.sl-powered-mark svg{width:
|
|
1511
|
+
background:#0c1220;color:#fcf7ee}
|
|
1512
|
+
.sl-powered-mark svg{width:12px;height:11px}
|
|
1508
1513
|
|
|
1509
1514
|
/* a11y filter chips (flow within the top-left region) */
|
|
1510
1515
|
.sl-chips{display:flex;gap:6px;flex-wrap:wrap}
|
|
@@ -1686,6 +1691,20 @@ var CSS = `
|
|
|
1686
1691
|
color:#e6edf3;background:rgba(10,14,20,.62);border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px)}
|
|
1687
1692
|
.sl-view3d-back:hover,.sl-view3d-back:focus-visible{background:rgba(16,22,30,.82);border-color:rgba(255,255,255,.4)}
|
|
1688
1693
|
.sl-view3d-back svg{width:15px;height:15px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
1694
|
+
/* Venue navigation inside 3D: levels (isolate a floor) and areas (fly to a zone).
|
|
1695
|
+
Bottom-LEFT, clear of the module's own chips (Overview bottom-right, 360
|
|
1696
|
+
bottom-centre) and of the bottom-sheeted confirm card. Horizontally scrollable
|
|
1697
|
+
so a venue with many zones never pushes the rail off a phone screen. */
|
|
1698
|
+
.sl-view3d-nav{position:absolute;left:12px;bottom:16px;z-index:3;display:flex;flex-direction:column;gap:6px;
|
|
1699
|
+
max-width:calc(100% - 24px);pointer-events:none}
|
|
1700
|
+
.sl-view3d-nav > div{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;pointer-events:auto;
|
|
1701
|
+
padding:1px;-webkit-overflow-scrolling:touch}
|
|
1702
|
+
.sl-view3d-nav > div::-webkit-scrollbar{display:none}
|
|
1703
|
+
.sl-view3d-nav button{flex:0 0 auto;min-height:32px;padding:7px 12px;border-radius:999px;white-space:nowrap;
|
|
1704
|
+
font-size:11.5px;font-weight:700;color:#c9d4ea;background:rgba(12,18,32,.72);
|
|
1705
|
+
border:1px solid rgba(150,165,205,.35);backdrop-filter:blur(6px);cursor:pointer}
|
|
1706
|
+
.sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
|
|
1707
|
+
.sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
1689
1708
|
/* While immersed, the 2D-only chrome is meaningless \u2014 hide it, keep Map|3D. */
|
|
1690
1709
|
.sl-picker[data-view3d="on"] .sl-rungs,
|
|
1691
1710
|
.sl-picker[data-view3d="on"] .sl-floors,
|
|
@@ -1959,6 +1978,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
1959
1978
|
// F4 price-band filter — active band's category keys (null = all prices)
|
|
1960
1979
|
this.priceBandKeys = null;
|
|
1961
1980
|
this.focusedCatKey = null;
|
|
1981
|
+
/** "Hide limited-view seats" — mirrored into 3D by `seatState3dFor`. */
|
|
1982
|
+
this.limitedViewFilter = false;
|
|
1962
1983
|
this.pricesExpanded = false;
|
|
1963
1984
|
/** Last surfaced section summary (re-rendered when the price band changes). */
|
|
1964
1985
|
this.lastSection = null;
|
|
@@ -2627,12 +2648,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
2627
2648
|
limited.setAttribute("aria-pressed", "false");
|
|
2628
2649
|
limited.innerHTML = `\u25D0 ${this.tf("picker.hideLimitedView", "Hide limited-view seats")}`;
|
|
2629
2650
|
chips.appendChild(limited);
|
|
2630
|
-
let limitedOn = false;
|
|
2631
2651
|
limited.addEventListener("click", () => {
|
|
2632
|
-
limitedOn = !
|
|
2652
|
+
const limitedOn = !this.limitedViewFilter;
|
|
2653
|
+
this.limitedViewFilter = limitedOn;
|
|
2633
2654
|
limited.classList.toggle("on", limitedOn);
|
|
2634
2655
|
limited.setAttribute("aria-pressed", String(limitedOn));
|
|
2635
2656
|
this.controller.setCommercialLimitedFilter(limitedOn);
|
|
2657
|
+
this.pushAvailabilityTo3d();
|
|
2636
2658
|
if (limitedOn) focusSeatsForFilter();
|
|
2637
2659
|
});
|
|
2638
2660
|
}
|
|
@@ -2790,7 +2812,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2790
2812
|
if (!foot) return;
|
|
2791
2813
|
const el = document.createElement("div");
|
|
2792
2814
|
el.className = "sl-powered";
|
|
2793
|
-
el.innerHTML = `<span class="sl-powered-mark" aria-hidden="true"
|
|
2815
|
+
el.innerHTML = `<span class="sl-powered-mark" aria-hidden="true">` + SEATLAYER_ATTRIBUTION_MARK_SVG + `</span><span>${this.tf("picker.poweredBy", "Powered by SeatLayer")}</span>`;
|
|
2794
2816
|
foot.appendChild(el);
|
|
2795
2817
|
}
|
|
2796
2818
|
// ---- Feature 6: chrome anchor regions -------------------------------------
|
|
@@ -3221,6 +3243,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3221
3243
|
this.priceBandKeys = keys ? new Set(keys) : null;
|
|
3222
3244
|
this.controller.setCategoryFilter(keys);
|
|
3223
3245
|
this.controller.focusCategoryFilter(keys);
|
|
3246
|
+
this.pushAvailabilityTo3d();
|
|
3224
3247
|
this.syncFloors();
|
|
3225
3248
|
this.syncRung();
|
|
3226
3249
|
this.refreshMinimap();
|
|
@@ -3318,10 +3341,23 @@ var SeatPicker = class _SeatPicker {
|
|
|
3318
3341
|
button.setAttribute("aria-pressed", String(on));
|
|
3319
3342
|
});
|
|
3320
3343
|
}
|
|
3321
|
-
/** Can this picker offer the 3D venue view? Requires the option (default on)
|
|
3322
|
-
*
|
|
3344
|
+
/** Can this picker offer the 3D venue view? Requires the option (default on),
|
|
3345
|
+
* WebGL2, a chart to render, and a chart small enough to render WELL. */
|
|
3323
3346
|
canOffer3d() {
|
|
3324
|
-
|
|
3347
|
+
if (this.opts.enable3D === false || !this.controller.doc || !hasWebGL2()) return false;
|
|
3348
|
+
return this.allSeats().length <= this.max3dSeats();
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* The seat ceiling for offering 3D. See `max3DSeats` — an explicit host value
|
|
3352
|
+
* always wins; otherwise a small/low-core device gets half the desktop budget,
|
|
3353
|
+
* because it is the device that turns "slow" into "stalled".
|
|
3354
|
+
*/
|
|
3355
|
+
max3dSeats() {
|
|
3356
|
+
const authored = this.opts.max3DSeats;
|
|
3357
|
+
if (typeof authored === "number" && authored > 0) return authored;
|
|
3358
|
+
const nav = globalThis.navigator;
|
|
3359
|
+
const small = (nav?.hardwareConcurrency ?? 8) <= 4 || (nav?.deviceMemory ?? 8) <= 4 || (globalThis.matchMedia?.("(pointer: coarse)").matches ?? false);
|
|
3360
|
+
return small ? MAX_3D_SEATS_DEFAULT / 2 : MAX_3D_SEATS_DEFAULT;
|
|
3325
3361
|
}
|
|
3326
3362
|
/** Reflect the active floor onto the switcher rail. */
|
|
3327
3363
|
syncFloors() {
|
|
@@ -3874,6 +3910,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3874
3910
|
if (select) select.value = "all";
|
|
3875
3911
|
this.controller.setCategoryFilter(next ? [next] : null);
|
|
3876
3912
|
this.controller.focusCategoryFilter(next ? [next] : null);
|
|
3913
|
+
this.pushAvailabilityTo3d();
|
|
3877
3914
|
this.syncFloors();
|
|
3878
3915
|
this.syncRung();
|
|
3879
3916
|
this.refreshMinimap();
|
|
@@ -4565,8 +4602,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
4565
4602
|
void this.enter3d(flyToSeatId);
|
|
4566
4603
|
}
|
|
4567
4604
|
/** SeatStatus → the view3d palette state. Selection is layered separately. */
|
|
4568
|
-
seatState3dFor(
|
|
4569
|
-
switch (this.controller.getStatus(id)) {
|
|
4605
|
+
seatState3dFor(seat) {
|
|
4606
|
+
switch (this.controller.getStatus(seat.id)) {
|
|
4570
4607
|
case "held":
|
|
4571
4608
|
return "held";
|
|
4572
4609
|
case "booked":
|
|
@@ -4574,14 +4611,19 @@ var SeatPicker = class _SeatPicker {
|
|
|
4574
4611
|
case "not_for_sale":
|
|
4575
4612
|
return "dimmed";
|
|
4576
4613
|
default:
|
|
4577
|
-
|
|
4614
|
+
break;
|
|
4615
|
+
}
|
|
4616
|
+
if (this.priceBandKeys != null && !this.priceBandKeys.has(seat.categoryKey)) return "dimmed";
|
|
4617
|
+
if (this.limitedViewFilter && (seat.commercial?.restrictedView || seat.commercial?.obstructedView)) {
|
|
4618
|
+
return "dimmed";
|
|
4578
4619
|
}
|
|
4620
|
+
return "available";
|
|
4579
4621
|
}
|
|
4580
4622
|
/** Push the full live availability snapshot into the 3D handle (selection is
|
|
4581
4623
|
* preserved inside the module). Cheap enough per status delta. */
|
|
4582
4624
|
pushAvailabilityTo3d() {
|
|
4583
4625
|
if (!this.view3dHandle) return;
|
|
4584
|
-
const updates = this.allSeats().map((s) => ({ seatId: s.id, state: this.seatState3dFor(s
|
|
4626
|
+
const updates = this.allSeats().map((s) => ({ seatId: s.id, state: this.seatState3dFor(s) }));
|
|
4585
4627
|
this.view3dHandle.setAvailability(updates);
|
|
4586
4628
|
}
|
|
4587
4629
|
/** Mirror the authoritative widget selection into the 3D handle. */
|
|
@@ -4633,6 +4675,75 @@ var SeatPicker = class _SeatPicker {
|
|
|
4633
4675
|
if (this.opts.confirmSelection !== false) this.showConfirm(seat);
|
|
4634
4676
|
else this.syncTray();
|
|
4635
4677
|
}
|
|
4678
|
+
/**
|
|
4679
|
+
* The venue-navigation rail inside 3D: levels and areas.
|
|
4680
|
+
*
|
|
4681
|
+
* In 2D a buyer moves through the venue by floor switcher and by the LOD
|
|
4682
|
+
* rungs. Both are hidden while immersed, and the 3D module's own chips only
|
|
4683
|
+
* cover "go home" and "see the 360" — so on a multi-floor or multi-zone chart
|
|
4684
|
+
* the buyer entered 3D and LOST the ability to reach the level or area they
|
|
4685
|
+
* were booking. The camera moves for both already exist on the handle
|
|
4686
|
+
* (`focusFloor` / `focusZone`); this is the surface that offers them.
|
|
4687
|
+
*
|
|
4688
|
+
* Levels are a TOGGLE (a floor stays isolated until you pick another), areas
|
|
4689
|
+
* are an ACTION (the camera flies there and you are then free to orbit), which
|
|
4690
|
+
* is why only the level pills carry a pressed state.
|
|
4691
|
+
*/
|
|
4692
|
+
buildView3dNav(overlay, handle) {
|
|
4693
|
+
const floors = handle.floors();
|
|
4694
|
+
const zones = handle.zones().filter((z) => z.seatCount > 0);
|
|
4695
|
+
const sections = zones.length > 1 ? [] : handle.sections().filter((s) => s.seatCount > 0).slice(0, MAX_3D_SECTION_PILLS);
|
|
4696
|
+
const wantFloors = floors.length > 1;
|
|
4697
|
+
const wantZones = zones.length > 1;
|
|
4698
|
+
const wantSections = sections.length > 1;
|
|
4699
|
+
if (!wantFloors && !wantZones && !wantSections) return;
|
|
4700
|
+
const nav = document.createElement("div");
|
|
4701
|
+
nav.className = "sl-view3d-nav";
|
|
4702
|
+
if (wantFloors) {
|
|
4703
|
+
const row = document.createElement("div");
|
|
4704
|
+
row.setAttribute("role", "group");
|
|
4705
|
+
row.setAttribute("aria-label", this.tf("picker.levels", "Levels"));
|
|
4706
|
+
const pills = [];
|
|
4707
|
+
const select = (index) => {
|
|
4708
|
+
if (!handle.focusFloor(index)) return;
|
|
4709
|
+
pills.forEach((p) => {
|
|
4710
|
+
p.setAttribute("aria-pressed", String((p.dataset.floor === "" ? null : Number(p.dataset.floor)) === index));
|
|
4711
|
+
});
|
|
4712
|
+
};
|
|
4713
|
+
const add = (label, index) => {
|
|
4714
|
+
const b = document.createElement("button");
|
|
4715
|
+
b.type = "button";
|
|
4716
|
+
b.textContent = label;
|
|
4717
|
+
b.dataset.floor = index === null ? "" : String(index);
|
|
4718
|
+
b.setAttribute("aria-pressed", String(index === null));
|
|
4719
|
+
b.addEventListener("click", () => select(index));
|
|
4720
|
+
pills.push(b);
|
|
4721
|
+
row.appendChild(b);
|
|
4722
|
+
};
|
|
4723
|
+
add(this.tf("picker.allLevels", "All levels"), null);
|
|
4724
|
+
for (const f of floors) add(f.label || `Level ${f.index + 1}`, f.index);
|
|
4725
|
+
nav.appendChild(row);
|
|
4726
|
+
}
|
|
4727
|
+
if (wantZones || wantSections) {
|
|
4728
|
+
const row = document.createElement("div");
|
|
4729
|
+
row.setAttribute("role", "group");
|
|
4730
|
+
row.setAttribute("aria-label", this.tf("picker.areas", "Areas"));
|
|
4731
|
+
const entries = wantZones ? zones.map((z) => ({ id: z.id, label: z.label || z.id, go: () => {
|
|
4732
|
+
handle.focusZone(z.id);
|
|
4733
|
+
} })) : sections.map((s) => ({ id: s.id, label: s.label || s.id, go: () => {
|
|
4734
|
+
handle.focusSection(s.id);
|
|
4735
|
+
} }));
|
|
4736
|
+
for (const e of entries) {
|
|
4737
|
+
const b = document.createElement("button");
|
|
4738
|
+
b.type = "button";
|
|
4739
|
+
b.textContent = e.label;
|
|
4740
|
+
b.addEventListener("click", e.go);
|
|
4741
|
+
row.appendChild(b);
|
|
4742
|
+
}
|
|
4743
|
+
nav.appendChild(row);
|
|
4744
|
+
}
|
|
4745
|
+
overlay.appendChild(nav);
|
|
4746
|
+
}
|
|
4636
4747
|
async enter3d(flySeatId) {
|
|
4637
4748
|
if (this.view3dEl || !this.canOffer3d() || !this.els.map) return;
|
|
4638
4749
|
const doc = this.controller.doc;
|
|
@@ -4683,6 +4794,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4683
4794
|
this.view3dHandle = handle;
|
|
4684
4795
|
this.pushAvailabilityTo3d();
|
|
4685
4796
|
this.syncSelectionTo3d();
|
|
4797
|
+
this.buildView3dNav(overlay, handle);
|
|
4686
4798
|
if (flySeatId) void handle.flyToSeat(flySeatId);
|
|
4687
4799
|
} catch (err) {
|
|
4688
4800
|
this.opts.onError?.(err);
|