@seatlayer/core 0.51.0 → 0.52.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/dist/{chunk-GD267BFB.js → chunk-UID7KN44.js} +75 -2
- package/dist/chunk-UID7KN44.js.map +1 -0
- package/dist/core/seatConfidence.d.cts +1 -1
- package/dist/core/seatConfidence.d.ts +1 -1
- package/dist/{de-V53JPFHN.js → de-YGODYJ3E.js} +3 -3
- package/dist/de-YGODYJ3E.js.map +1 -0
- package/dist/{es-37ISLCZA.js → es-VMBTGVFQ.js} +3 -3
- package/dist/es-VMBTGVFQ.js.map +1 -0
- package/dist/{fr-QKK2T64Q.js → fr-SLBE2ULB.js} +3 -3
- package/dist/fr-SLBE2ULB.js.map +1 -0
- package/dist/index.cjs +647 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -17
- package/dist/index.d.ts +131 -17
- package/dist/index.js +575 -58
- package/dist/index.js.map +1 -1
- package/dist/picker/minimapGeometry.cjs +80 -0
- package/dist/picker/minimapGeometry.cjs.map +1 -0
- package/dist/picker/minimapGeometry.d.cts +37 -0
- package/dist/picker/minimapGeometry.d.ts +37 -0
- package/dist/picker/minimapGeometry.js +52 -0
- package/dist/picker/minimapGeometry.js.map +1 -0
- package/dist/{types-DR54nqKL.d.cts → types-tLOunI-B.d.cts} +131 -1
- package/dist/{types-DR54nqKL.d.ts → types-tLOunI-B.d.ts} +131 -1
- package/dist/view3d/crossfade/panorama.d.cts +1 -1
- package/dist/view3d/crossfade/panorama.d.ts +1 -1
- package/dist/view3d/index.cjs +1158 -123
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +20 -12
- package/dist/view3d/index.d.ts +20 -12
- package/dist/view3d/index.js +1158 -122
- package/dist/view3d/index.js.map +1 -1
- package/package.json +14 -1
- package/dist/chunk-GD267BFB.js.map +0 -1
- package/dist/de-V53JPFHN.js.map +0 -1
- package/dist/es-37ISLCZA.js.map +0 -1
- package/dist/fr-QKK2T64Q.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
expandTable,
|
|
23
23
|
expandTableSlots,
|
|
24
24
|
floorObjects,
|
|
25
|
+
floorOverviewTranslations,
|
|
25
26
|
floorsOf,
|
|
26
27
|
layerOf,
|
|
28
|
+
layoutFloorOverview,
|
|
27
29
|
objectCenter,
|
|
28
30
|
owningSectionForObject,
|
|
29
31
|
pointInPolygon,
|
|
@@ -37,8 +39,9 @@ import {
|
|
|
37
39
|
sectionGeometry,
|
|
38
40
|
stackFloors,
|
|
39
41
|
tableInventoryCount,
|
|
40
|
-
tableSeatCountsBySide
|
|
41
|
-
|
|
42
|
+
tableSeatCountsBySide,
|
|
43
|
+
translateObject
|
|
44
|
+
} from "./chunk-UID7KN44.js";
|
|
42
45
|
|
|
43
46
|
// src/core/ga.ts
|
|
44
47
|
var PREFIX = "__sl_ga__";
|
|
@@ -1286,6 +1289,8 @@ var en = {
|
|
|
1286
1289
|
"picker.poweredBy": "Powered by SeatLayer",
|
|
1287
1290
|
"picker.testMode": "TEST MODE",
|
|
1288
1291
|
"picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
|
|
1292
|
+
"picker.companionRequiresWheelchair": "Requires the adjacent wheelchair place",
|
|
1293
|
+
"picker.companionPairRequired": "Select the adjacent wheelchair place with each companion ticket.",
|
|
1289
1294
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
1290
1295
|
"map.aria": "Seating map. Use arrow keys to move between seats, Enter to select.",
|
|
1291
1296
|
"map.seatsLeft": "{count} LEFT",
|
|
@@ -1397,8 +1402,6 @@ var en = {
|
|
|
1397
1402
|
"picker.fewerGuests": "Fewer guests",
|
|
1398
1403
|
"picker.fewerSeats": "Fewer seats",
|
|
1399
1404
|
"picker.filterAndFocusByPrice": "Filter and focus seats by price",
|
|
1400
|
-
"picker.filteredSeat": "Unavailable with current filters",
|
|
1401
|
-
"picker.filteredSeatExplanation": "Change the active price or view filters to make this seat selectable.",
|
|
1402
1405
|
"picker.filters": "Filters",
|
|
1403
1406
|
"picker.findBestSeatsCount.one": "Find {count} best seat",
|
|
1404
1407
|
"picker.findBestSeatsCount.other": "Find {count} best seats",
|
|
@@ -1634,10 +1637,11 @@ var WHEELCHAIR_GLYPH_MIN_PX = 10;
|
|
|
1634
1637
|
var FILTERED_WHEELCHAIR_GLYPH_MIN_PX = 14;
|
|
1635
1638
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
1636
1639
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
1637
|
-
var
|
|
1640
|
+
var SEAT_DETAIL_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
1638
1641
|
var PAN_START_SLOP_PX = 8;
|
|
1639
1642
|
var GHOST_CLICK_MS = 700;
|
|
1640
1643
|
var ROW_LABEL_CLEARANCE = 2;
|
|
1644
|
+
var FOCUSED_ROW_LABEL_MIN_PX = MIN_VISIBLE_BOOKABLE_LABEL_PX;
|
|
1641
1645
|
var ROW_LABEL_COLLISION_CELL = 64;
|
|
1642
1646
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
1643
1647
|
var MAX_LABELS = 700;
|
|
@@ -2033,6 +2037,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2033
2037
|
this.activeFloorId = "";
|
|
2034
2038
|
/** When true on a multi-floor chart, render ALL floors stacked (3D overview). */
|
|
2035
2039
|
this.stacked = false;
|
|
2040
|
+
/** Buyer-facing 2D overview: floors are rigidly arranged without altering topology. */
|
|
2041
|
+
this.floorOverview = false;
|
|
2036
2042
|
/** Interactive node per seat/booth — a Circle for seats, a Rect for booths. */
|
|
2037
2043
|
this.circleById = /* @__PURE__ */ new Map();
|
|
2038
2044
|
/** Booth block geometry, keyed by booth id (= the unit's rowId). */
|
|
@@ -2288,6 +2294,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2288
2294
|
return;
|
|
2289
2295
|
}
|
|
2290
2296
|
if (this.pinch && this.pointers.size >= 2) {
|
|
2297
|
+
this.releaseSectionFocusDim();
|
|
2291
2298
|
const [a, b] = [...this.pointers.values()];
|
|
2292
2299
|
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2293
2300
|
if (this.pinch.startDist < 1) return;
|
|
@@ -2306,6 +2313,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2306
2313
|
if (!this.panStarted) {
|
|
2307
2314
|
if (this.moved <= PAN_START_SLOP_PX) return;
|
|
2308
2315
|
this.panStarted = true;
|
|
2316
|
+
this.releaseSectionFocusDim();
|
|
2309
2317
|
}
|
|
2310
2318
|
this.stage.position({
|
|
2311
2319
|
x: this.stage.x() + (p.x - this.panLast.x),
|
|
@@ -2426,13 +2434,23 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2426
2434
|
}
|
|
2427
2435
|
// ---- ISeatmapRenderer -----------------------------------------------------
|
|
2428
2436
|
setChart(doc, opts) {
|
|
2429
|
-
if (doc !== this.chartDoc)
|
|
2437
|
+
if (doc !== this.chartDoc) {
|
|
2438
|
+
this.stacked = false;
|
|
2439
|
+
this.floorOverview = false;
|
|
2440
|
+
}
|
|
2430
2441
|
this.cancelAssetLoads();
|
|
2431
2442
|
this.chartDoc = doc;
|
|
2432
2443
|
this.activeFloorId = opts?.floorId ?? floorsOf(doc)[0].id;
|
|
2433
2444
|
this.objectFloor.clear();
|
|
2434
2445
|
if (doc.floors && doc.floors.length > 1) {
|
|
2435
|
-
for (const f of doc.floors)
|
|
2446
|
+
for (const f of doc.floors) {
|
|
2447
|
+
for (const o of f.objects) {
|
|
2448
|
+
this.objectFloor.set(o.id, f.id);
|
|
2449
|
+
if (o.type === "section" && o.logicalSectionId) {
|
|
2450
|
+
this.objectFloor.set(o.logicalSectionId, f.id);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2436
2454
|
}
|
|
2437
2455
|
const view = this.floorView(doc);
|
|
2438
2456
|
this.focusedId = null;
|
|
@@ -2530,7 +2548,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2530
2548
|
this.seats = expandChart(view, {
|
|
2531
2549
|
// A stacked overview is presentation-only and never opens a seat POV. The
|
|
2532
2550
|
// per-section renderer below still resolves each object's real floor base.
|
|
2533
|
-
floorBaseHeightM: this.stacked ? 0 : this.floorBaseHeightMFor()
|
|
2551
|
+
floorBaseHeightM: this.stacked || this.floorOverview ? 0 : this.floorBaseHeightMFor()
|
|
2534
2552
|
});
|
|
2535
2553
|
for (const s of this.seats) {
|
|
2536
2554
|
this.seatById.set(s.id, s);
|
|
@@ -2551,14 +2569,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2551
2569
|
this.overlayLayer.add(this.focusRing);
|
|
2552
2570
|
this.zoomToFit();
|
|
2553
2571
|
}
|
|
2554
|
-
/** The chart to render:
|
|
2555
|
-
* the whole chart for single-floor charts. */
|
|
2572
|
+
/** The chart to render: authored-coordinate overview, stacked 3D overview,
|
|
2573
|
+
* the active floor, or the whole chart for single-floor charts. */
|
|
2556
2574
|
floorView(doc) {
|
|
2557
2575
|
if (!doc.floors || !doc.floors.length) return {
|
|
2558
2576
|
...doc,
|
|
2559
2577
|
referenceImage: void 0,
|
|
2560
2578
|
backgroundImage: buyerBackgroundImage(doc)
|
|
2561
2579
|
};
|
|
2580
|
+
if (this.floorOverview && doc.floors.length >= 2) {
|
|
2581
|
+
return {
|
|
2582
|
+
...layoutFloorOverview(doc),
|
|
2583
|
+
referenceImage: void 0,
|
|
2584
|
+
backgroundImage: buyerBackgroundImage(doc)
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2562
2587
|
if (this.stacked && doc.floors.length >= 2) {
|
|
2563
2588
|
return {
|
|
2564
2589
|
...stackFloors(doc),
|
|
@@ -2591,14 +2616,30 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2591
2616
|
const multi = !!this.chartDoc.floors && this.chartDoc.floors.length >= 2;
|
|
2592
2617
|
if (!multi) return;
|
|
2593
2618
|
this.stacked = on;
|
|
2619
|
+
if (on) this.floorOverview = false;
|
|
2594
2620
|
this.setChart(this.chartDoc, { floorId: this.activeFloorId });
|
|
2595
2621
|
}
|
|
2596
2622
|
isStacked() {
|
|
2597
2623
|
return this.stacked;
|
|
2598
2624
|
}
|
|
2625
|
+
/** Toggle the buyer's combined 2D floor overview. Every floor is translated
|
|
2626
|
+
* as one rigid group for clarity; its authored internal topology is exact. */
|
|
2627
|
+
setFloorOverview(on) {
|
|
2628
|
+
if (!this.chartDoc || on === this.floorOverview) return;
|
|
2629
|
+
const multi = !!this.chartDoc.floors && this.chartDoc.floors.length >= 2;
|
|
2630
|
+
if (!multi) return;
|
|
2631
|
+
this.floorOverview = on;
|
|
2632
|
+
if (on) this.stacked = false;
|
|
2633
|
+
this.setChart(this.chartDoc, { floorId: this.activeFloorId });
|
|
2634
|
+
}
|
|
2635
|
+
isFloorOverview() {
|
|
2636
|
+
return this.floorOverview;
|
|
2637
|
+
}
|
|
2599
2638
|
/** Switch which floor is shown (2D). Re-renders + re-fits; no-op if unchanged. */
|
|
2600
2639
|
setActiveFloor(floorId) {
|
|
2601
|
-
if (!this.chartDoc || floorId === this.activeFloorId) return;
|
|
2640
|
+
if (!this.chartDoc || floorId === this.activeFloorId && !this.floorOverview && !this.stacked) return;
|
|
2641
|
+
this.floorOverview = false;
|
|
2642
|
+
this.stacked = false;
|
|
2602
2643
|
this.setChart(this.chartDoc, { floorId });
|
|
2603
2644
|
}
|
|
2604
2645
|
/** Floors for the host's switcher (single-floor charts return one synthetic floor). */
|
|
@@ -3057,6 +3098,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3057
3098
|
const h = this.stage.height();
|
|
3058
3099
|
const b = this.viewMode === "perspective" && this.perspectiveBounds ? this.perspectiveBounds : this.bounds;
|
|
3059
3100
|
this.fitScale = Math.min(w / b.width, h / b.height) || 1;
|
|
3101
|
+
if (this.opts.portraitFitCrop && !this.exportMode) {
|
|
3102
|
+
const widthBound = w / b.width <= h / b.height;
|
|
3103
|
+
const projectedH = b.height * this.fitScale;
|
|
3104
|
+
if (widthBound && projectedH < 0.55 * h) {
|
|
3105
|
+
this.fitScale = Math.min(this.fitScale * 1.3, 0.62 * h / b.height);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3060
3108
|
const s = this.fitScale;
|
|
3061
3109
|
this.stage.scale({ x: s, y: s });
|
|
3062
3110
|
this.stage.position({
|
|
@@ -3066,6 +3114,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3066
3114
|
this.afterViewChange();
|
|
3067
3115
|
this.stage.batchDraw();
|
|
3068
3116
|
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Reframe after a host/layout resize without throwing away section context.
|
|
3119
|
+
* The stage already represents the usable map area (the picker side panel is
|
|
3120
|
+
* a sibling), so measuring it here naturally respects open/closed panels.
|
|
3121
|
+
*/
|
|
3122
|
+
refitCurrentView() {
|
|
3123
|
+
if (this.focusedSectionId && this.sectionBounds(this.focusedSectionId)) {
|
|
3124
|
+
this.focusRegion(this.focusedSectionId, { animate: false });
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
this.zoomToFit();
|
|
3128
|
+
}
|
|
3069
3129
|
zoomIn() {
|
|
3070
3130
|
const center = { x: this.stage.width() / 2, y: this.stage.height() / 2 };
|
|
3071
3131
|
this.zoomAbout(this.stage.scaleX() * _SeatmapRenderer.ZOOM_STEP, center);
|
|
@@ -4156,8 +4216,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4156
4216
|
}
|
|
4157
4217
|
/**
|
|
4158
4218
|
* AXS section-focus: dim + desaturate every other section, draw a calm backdrop
|
|
4159
|
-
* panel behind this section's seats, and glide the camera
|
|
4160
|
-
*
|
|
4219
|
+
* panel behind this section's seats, and glide the camera to a padded fit of
|
|
4220
|
+
* the whole section. A large section may therefore use smaller seat markers,
|
|
4221
|
+
* but it never clips its outer rows merely to meet a fixed detail scale.
|
|
4161
4222
|
*/
|
|
4162
4223
|
focusSection(id) {
|
|
4163
4224
|
if (!this.sections.some((section) => section.id === id || section.logicalId === id)) return;
|
|
@@ -4166,7 +4227,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4166
4227
|
this.bgLayer.batchDraw();
|
|
4167
4228
|
this.overlayLayer.batchDraw();
|
|
4168
4229
|
this.updateLOD();
|
|
4169
|
-
this.focusRegion(id
|
|
4230
|
+
this.focusRegion(id);
|
|
4231
|
+
}
|
|
4232
|
+
/**
|
|
4233
|
+
* Release only the focus dim + backdrop, keeping the focused-section state
|
|
4234
|
+
* (summary card, adjacent-section navigation, LOD) intact. Called on the
|
|
4235
|
+
* buyer's first manual pan/pinch/wheel after a section glide: the dim reads
|
|
4236
|
+
* as "everything else is disabled", but every seat outside the focus was
|
|
4237
|
+
* always hit-testable — free roaming across sections is the product
|
|
4238
|
+
* behaviour, the glide is only an entrance.
|
|
4239
|
+
*/
|
|
4240
|
+
releaseSectionFocusDim() {
|
|
4241
|
+
if (!this.focusBackdrop && !this.focusDimOverlay) return;
|
|
4242
|
+
this.focusBackdrop?.destroy();
|
|
4243
|
+
this.focusBackdrop = null;
|
|
4244
|
+
this.focusDimOverlay?.destroy();
|
|
4245
|
+
this.focusDimOverlay = null;
|
|
4246
|
+
this.bgLayer.batchDraw();
|
|
4247
|
+
this.overlayLayer.batchDraw();
|
|
4170
4248
|
}
|
|
4171
4249
|
/** Clear an AXS section focus — restore full-bowl brightness + drop the backdrop. */
|
|
4172
4250
|
clearSectionFocus() {
|
|
@@ -4304,6 +4382,44 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4304
4382
|
if (!Number.isFinite(minX)) return { x: 0, y: 0, width: 1, height: 1 };
|
|
4305
4383
|
return { x: minX, y: minY, width: Math.max(1, maxX - minX), height: Math.max(1, maxY - minY) };
|
|
4306
4384
|
}
|
|
4385
|
+
/** Live minimap geometry comes from the rendered scene, so a content-aware
|
|
4386
|
+
* all-floor layout cannot collapse back onto the source coordinate stack. */
|
|
4387
|
+
getMinimapSnapshot() {
|
|
4388
|
+
const sections = this.sections.map((section) => {
|
|
4389
|
+
const inactive = this.isSectionClosed(section) || section.free <= 0;
|
|
4390
|
+
const selected = section.memberIds.some((id) => this.selection.has(id));
|
|
4391
|
+
const active2 = this.focusedSectionId === section.id || this.focusedSectionId === section.logicalId;
|
|
4392
|
+
return {
|
|
4393
|
+
id: section.logicalId,
|
|
4394
|
+
label: section.label,
|
|
4395
|
+
outline: section.outline.map((point) => ({ ...point })),
|
|
4396
|
+
...this.objectFloor.get(section.id) ? { floorId: this.objectFloor.get(section.id) } : {},
|
|
4397
|
+
state: selected ? "selected" : inactive ? "inactive" : "available",
|
|
4398
|
+
active: active2
|
|
4399
|
+
};
|
|
4400
|
+
});
|
|
4401
|
+
return {
|
|
4402
|
+
sections,
|
|
4403
|
+
seats: sections.length ? [] : this.seats.map((seat) => ({
|
|
4404
|
+
x: seat.x,
|
|
4405
|
+
y: seat.y,
|
|
4406
|
+
categoryKey: seat.categoryKey,
|
|
4407
|
+
state: this.selection.has(seat.id) ? "selected" : this.statusById.get(seat.id) === "free" ? "available" : "inactive"
|
|
4408
|
+
}))
|
|
4409
|
+
};
|
|
4410
|
+
}
|
|
4411
|
+
/** Minimap drag/click navigation: preserve zoom and move one world point to
|
|
4412
|
+
* the viewport centre. Works for flat and projected views via worldToScreen. */
|
|
4413
|
+
panToWorld(point) {
|
|
4414
|
+
this.cancelGlide();
|
|
4415
|
+
const screen = this.worldToScreen(point);
|
|
4416
|
+
this.stage.position({
|
|
4417
|
+
x: this.stage.x() + this.stage.width() / 2 - screen.x,
|
|
4418
|
+
y: this.stage.y() + this.stage.height() / 2 - screen.y
|
|
4419
|
+
});
|
|
4420
|
+
this.afterViewChange();
|
|
4421
|
+
this.stage.batchDraw();
|
|
4422
|
+
}
|
|
4307
4423
|
/** Legend hover: highlight one category (dim the rest), or null to clear. */
|
|
4308
4424
|
setCategoryHighlight(key) {
|
|
4309
4425
|
if (this.categoryHighlight === key) return;
|
|
@@ -5072,7 +5188,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5072
5188
|
blockT = clamp((BLOCK_MELT_TOP - scale) / (BLOCK_MELT_TOP - SECTION_PROMINENT_SCALE), 0, 1);
|
|
5073
5189
|
zoneT = clamp((SECTION_PROMINENT_SCALE - scale) / (SECTION_PROMINENT_SCALE - ZONE_PROMINENT_SCALE), 0, 1);
|
|
5074
5190
|
}
|
|
5075
|
-
|
|
5191
|
+
if (this.floorOverview) {
|
|
5192
|
+
blockT = 0;
|
|
5193
|
+
zoneT = 0;
|
|
5194
|
+
}
|
|
5195
|
+
if (this.focusedSectionId) {
|
|
5196
|
+
blockT = 0;
|
|
5197
|
+
zoneT = 0;
|
|
5198
|
+
}
|
|
5199
|
+
const sectionOverview = !this.floorOverview && !this.focusedSectionId && scale < CACHE_THRESHOLD;
|
|
5076
5200
|
if (sectionOverview) blockT = 1;
|
|
5077
5201
|
if (!this.zones.length) zoneT = 0;
|
|
5078
5202
|
const deferPerspectiveSeatReveal = this.viewMode === "perspective" && this.glideInProgress && this.seats.length > 2500;
|
|
@@ -5271,6 +5395,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5271
5395
|
zone.back.offset({ x: width / 2, y: height / 2 });
|
|
5272
5396
|
zone.back.cornerRadius(7 * this.uiScale() / sx);
|
|
5273
5397
|
zone.back.strokeWidth(1 / sx);
|
|
5398
|
+
const abs = zone.back.getAbsolutePosition();
|
|
5399
|
+
const minScreenY = 62 + height * sx / 2;
|
|
5400
|
+
if (abs.y < minScreenY) {
|
|
5401
|
+
const dy = (minScreenY - abs.y) / sx;
|
|
5402
|
+
zone.label.y(zone.label.y() + dy);
|
|
5403
|
+
if (zone.sub) zone.sub.y(zone.sub.y() + dy);
|
|
5404
|
+
zone.back.y(zone.back.y() + dy);
|
|
5405
|
+
}
|
|
5274
5406
|
}
|
|
5275
5407
|
/**
|
|
5276
5408
|
* Map a container-relative screen point back to world coords. Inverts the
|
|
@@ -5495,7 +5627,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5495
5627
|
* must fall through and pick.
|
|
5496
5628
|
*/
|
|
5497
5629
|
sectionBounds(id) {
|
|
5498
|
-
const
|
|
5630
|
+
const matching = this.sections.filter((section) => section.id === id || section.logicalId === id);
|
|
5631
|
+
const bounds2 = matching.map((section) => {
|
|
5499
5632
|
if (this.viewMode === "perspective" && section.perspectiveAffine) {
|
|
5500
5633
|
return polyBounds(section.outline.map((point) => this.projectedSectionPoint(section, point)));
|
|
5501
5634
|
}
|
|
@@ -5507,11 +5640,77 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5507
5640
|
return polyBounds(points);
|
|
5508
5641
|
});
|
|
5509
5642
|
if (!bounds2.length) return null;
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5643
|
+
let left = Math.min(...bounds2.map((box) => box.x));
|
|
5644
|
+
let top = Math.min(...bounds2.map((box) => box.y));
|
|
5645
|
+
let right = Math.max(...bounds2.map((box) => box.x + box.width));
|
|
5646
|
+
let bottom = Math.max(...bounds2.map((box) => box.y + box.height));
|
|
5647
|
+
const memberIds = new Set(matching.flatMap((section) => section.memberIds));
|
|
5648
|
+
for (const seatId of memberIds) {
|
|
5649
|
+
const seat = this.seatById.get(seatId);
|
|
5650
|
+
if (!seat) continue;
|
|
5651
|
+
const point = this.renderedSeatPoint(seat);
|
|
5652
|
+
left = Math.min(left, point.x - this.seatR);
|
|
5653
|
+
top = Math.min(top, point.y - this.seatR);
|
|
5654
|
+
right = Math.max(right, point.x + this.seatR);
|
|
5655
|
+
bottom = Math.max(bottom, point.y + this.seatR);
|
|
5656
|
+
}
|
|
5657
|
+
const includeBox = (box) => {
|
|
5658
|
+
if (![box.x, box.y, box.width, box.height].every(Number.isFinite)) return;
|
|
5659
|
+
left = Math.min(left, box.x);
|
|
5660
|
+
top = Math.min(top, box.y);
|
|
5661
|
+
right = Math.max(right, box.x + box.width);
|
|
5662
|
+
bottom = Math.max(bottom, box.y + box.height);
|
|
5663
|
+
};
|
|
5664
|
+
const belongsToSection = (box) => {
|
|
5665
|
+
const centre = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
5666
|
+
return matching.some((section) => pointInPolygonWithHoles(centre, section.outline, section.holes));
|
|
5667
|
+
};
|
|
5668
|
+
for (const { node, backdrop } of this.freeTextById.values()) {
|
|
5669
|
+
const nodeBox = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true });
|
|
5670
|
+
if (!belongsToSection(nodeBox)) continue;
|
|
5671
|
+
includeBox(nodeBox);
|
|
5672
|
+
if (backdrop) includeBox(backdrop.getClientRect({ relativeTo: this.bgLayer, skipShadow: true }));
|
|
5673
|
+
}
|
|
5674
|
+
for (const { node } of this.iconNodeById.values()) {
|
|
5675
|
+
const box = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true });
|
|
5676
|
+
if (belongsToSection(box)) includeBox(box);
|
|
5677
|
+
}
|
|
5678
|
+
const occupiedText = new WorldBoxIndex();
|
|
5679
|
+
for (const { node } of this.freeTextById.values()) {
|
|
5680
|
+
const box = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true, skipStroke: true });
|
|
5681
|
+
occupiedText.insert({
|
|
5682
|
+
x: box.x - ROW_LABEL_CLEARANCE,
|
|
5683
|
+
y: box.y - ROW_LABEL_CLEARANCE,
|
|
5684
|
+
width: box.width + ROW_LABEL_CLEARANCE * 2,
|
|
5685
|
+
height: box.height + ROW_LABEL_CLEARANCE * 2
|
|
5686
|
+
});
|
|
5687
|
+
}
|
|
5688
|
+
for (const plan of this.rowLabelPlan) {
|
|
5689
|
+
const relevant = plan.focusKeys?.some((key) => key === id || matching.some((section) => key === section.id || key === section.logicalId || key === section.zone)) ?? false;
|
|
5690
|
+
const label = new Text({
|
|
5691
|
+
text: plan.text,
|
|
5692
|
+
fontSize: plan.fontSize,
|
|
5693
|
+
fontStyle: "700",
|
|
5694
|
+
fontFamily: this.labelFont(),
|
|
5695
|
+
rotation: plan.rotation,
|
|
5696
|
+
listening: false
|
|
5697
|
+
});
|
|
5698
|
+
label.offsetX(label.width() / 2);
|
|
5699
|
+
label.offsetY(label.height() / 2);
|
|
5700
|
+
const resolved = this.resolveAutomaticRowLabelPosition(label, plan, occupiedText);
|
|
5701
|
+
const box = this.rowLabelBox(label, resolved);
|
|
5702
|
+
if (relevant) includeBox(box);
|
|
5703
|
+
occupiedText.insert(box);
|
|
5704
|
+
label.destroy();
|
|
5705
|
+
}
|
|
5706
|
+
const span = Math.max(right - left, bottom - top);
|
|
5707
|
+
const padding = Math.max(this.seatR * 2, Math.min(24, span * 0.025));
|
|
5708
|
+
return {
|
|
5709
|
+
x: left - padding,
|
|
5710
|
+
y: top - padding,
|
|
5711
|
+
width: Math.max(1, right - left + padding * 2),
|
|
5712
|
+
height: Math.max(1, bottom - top + padding * 2)
|
|
5713
|
+
};
|
|
5515
5714
|
}
|
|
5516
5715
|
sectionFrameScale(id) {
|
|
5517
5716
|
const b = this.sectionBounds(id);
|
|
@@ -5522,7 +5721,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5522
5721
|
const margin = 1.12;
|
|
5523
5722
|
if (b.width <= 0 || b.height <= 0) return this.stage.scaleX();
|
|
5524
5723
|
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
5525
|
-
return clamp(
|
|
5724
|
+
return clamp(frameScale, min, max);
|
|
5526
5725
|
}
|
|
5527
5726
|
/**
|
|
5528
5727
|
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
@@ -5535,7 +5734,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5535
5734
|
* fall through and PICK. This is the invariant: seats are never unpickable.
|
|
5536
5735
|
*/
|
|
5537
5736
|
handleSeatTap(id) {
|
|
5538
|
-
if (this.stacked && this.opts.onDeckTap) {
|
|
5737
|
+
if ((this.stacked || this.floorOverview) && this.opts.onDeckTap) {
|
|
5539
5738
|
const floorId = this.objectFloor.get(this.seatById.get(id)?.rowId ?? "");
|
|
5540
5739
|
if (floorId) {
|
|
5541
5740
|
this.opts.onDeckTap(floorId);
|
|
@@ -5610,6 +5809,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5610
5809
|
});
|
|
5611
5810
|
this.stage.on("wheel", (e) => {
|
|
5612
5811
|
e.evt.preventDefault();
|
|
5812
|
+
this.releaseSectionFocusDim();
|
|
5613
5813
|
const r = this.container.getBoundingClientRect();
|
|
5614
5814
|
const pointer = { x: e.evt.clientX - r.left, y: e.evt.clientY - r.top };
|
|
5615
5815
|
const factor = Math.exp(-e.evt.deltaY * 2e-3);
|
|
@@ -5626,8 +5826,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5626
5826
|
if (near) this.handleSeatTap(near);
|
|
5627
5827
|
return;
|
|
5628
5828
|
}
|
|
5629
|
-
if (this.stacked && this.opts.onDeckTap) {
|
|
5630
|
-
const
|
|
5829
|
+
if ((this.stacked || this.floorOverview) && this.opts.onDeckTap) {
|
|
5830
|
+
const sectionId = this.floorOverview ? this.sectionAt(pointer) : null;
|
|
5831
|
+
const floorId = (sectionId ? this.objectFloor.get(sectionId) : null) ?? this.deckFloorAt();
|
|
5631
5832
|
if (floorId) {
|
|
5632
5833
|
this.opts.onDeckTap(floorId);
|
|
5633
5834
|
return;
|
|
@@ -6025,6 +6226,28 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6025
6226
|
...hiddenReason ? { hiddenReason } : {}
|
|
6026
6227
|
};
|
|
6027
6228
|
});
|
|
6229
|
+
const rowLabels = this.labelGroup.find((node) => node.getAttr("rowLabel") === true).filter((node) => node instanceof Text).map((node) => {
|
|
6230
|
+
const screenBounds = node.getClientRect({
|
|
6231
|
+
skipShadow: true,
|
|
6232
|
+
skipStroke: true
|
|
6233
|
+
});
|
|
6234
|
+
const outside = screenBounds.x + screenBounds.width < 0 || screenBounds.x > viewport.width || screenBounds.y + screenBounds.height < 0 || screenBounds.y > viewport.height;
|
|
6235
|
+
const ink = node.fill();
|
|
6236
|
+
return {
|
|
6237
|
+
text: node.text(),
|
|
6238
|
+
visible: node.isVisible() && !outside,
|
|
6239
|
+
renderedFontPx: rounded(node.fontSize() * effectiveScale),
|
|
6240
|
+
ink: typeof ink === "string" ? ink : "",
|
|
6241
|
+
...!outside ? {
|
|
6242
|
+
screenBox: {
|
|
6243
|
+
x: rounded(screenBounds.x),
|
|
6244
|
+
y: rounded(screenBounds.y),
|
|
6245
|
+
width: rounded(screenBounds.width),
|
|
6246
|
+
height: rounded(screenBounds.height)
|
|
6247
|
+
}
|
|
6248
|
+
} : {}
|
|
6249
|
+
};
|
|
6250
|
+
});
|
|
6028
6251
|
const palette = overviewPalette(this.canvasBackground);
|
|
6029
6252
|
const neutralSectionFills = new Set(
|
|
6030
6253
|
["normal", "nearly-gone", "sold-out", "closed"].map((state) => overviewStateFill(palette, state).toLowerCase())
|
|
@@ -6069,7 +6292,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6069
6292
|
labels,
|
|
6070
6293
|
gaAreas,
|
|
6071
6294
|
hierarchyLabels,
|
|
6072
|
-
freeTextLabels
|
|
6295
|
+
freeTextLabels,
|
|
6296
|
+
rowLabels
|
|
6073
6297
|
};
|
|
6074
6298
|
}
|
|
6075
6299
|
/** Jump the camera to a rung's zoom band (glided). */
|
|
@@ -6081,7 +6305,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6081
6305
|
}
|
|
6082
6306
|
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(
|
|
6083
6307
|
this.seatLabelTargetScale() * 1.05,
|
|
6084
|
-
|
|
6308
|
+
SEAT_DETAIL_SCALE,
|
|
6085
6309
|
CACHE_THRESHOLD * 1.3
|
|
6086
6310
|
);
|
|
6087
6311
|
const w = this.stage.width();
|
|
@@ -6368,14 +6592,25 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6368
6592
|
for (const { objectId, node, backdrop, categoryKey, kind } of this.freeTextById.values()) {
|
|
6369
6593
|
const gaDimmed = categoryKey != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaCategoryDimmed(categoryKey);
|
|
6370
6594
|
const gaOverviewHidden = objectId != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaById.get(objectId)?.sectionId != null && effectiveScale < CACHE_THRESHOLD;
|
|
6371
|
-
const
|
|
6595
|
+
const focusedAnnotation = kind === "free-text" && this.nodeBelongsToFocusedSection(node);
|
|
6596
|
+
const visible = !gaDimmed && !gaOverviewHidden && (focusedAnnotation || isBookableLabelLegibleAtScale(node.fontSize(), effectiveScale));
|
|
6372
6597
|
node.visible(visible);
|
|
6373
6598
|
backdrop?.visible(visible);
|
|
6374
6599
|
}
|
|
6375
6600
|
for (const { node, fontSize } of this.iconNodeById.values()) {
|
|
6376
|
-
node.visible(isBookableLabelLegibleAtScale(fontSize, effectiveScale));
|
|
6601
|
+
node.visible(this.nodeBelongsToFocusedSection(node) || isBookableLabelLegibleAtScale(fontSize, effectiveScale));
|
|
6377
6602
|
}
|
|
6378
6603
|
}
|
|
6604
|
+
/** Geometric association only: importer data remains unchanged. */
|
|
6605
|
+
nodeBelongsToFocusedSection(node) {
|
|
6606
|
+
const focus = this.focusedSectionId;
|
|
6607
|
+
if (!focus) return false;
|
|
6608
|
+
const matching = this.sections.filter((section) => section.id === focus || section.logicalId === focus || section.zone === focus);
|
|
6609
|
+
if (!matching.length) return false;
|
|
6610
|
+
const box = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true });
|
|
6611
|
+
const centre = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
6612
|
+
return matching.some((section) => pointInPolygonWithHoles(centre, section.outline, section.holes));
|
|
6613
|
+
}
|
|
6379
6614
|
/** OV-45(a): resolve authored row-letter labels for the buyer/preview map, in
|
|
6380
6615
|
* world space, matching the Designer's placement contract (start/end/both/none
|
|
6381
6616
|
* preset, a free-drag `position` that wins over the preset, rotation, and
|
|
@@ -6385,18 +6620,25 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6385
6620
|
this.rowLabelPlan = [];
|
|
6386
6621
|
const background = this.theme.background ?? "#0e1117";
|
|
6387
6622
|
const seatsByRow = /* @__PURE__ */ new Map();
|
|
6623
|
+
const addSeat = (key, seat) => {
|
|
6624
|
+
const list = seatsByRow.get(key) ?? [];
|
|
6625
|
+
list.push(seat);
|
|
6626
|
+
seatsByRow.set(key, list);
|
|
6627
|
+
};
|
|
6388
6628
|
for (const seat of this.seats) {
|
|
6389
6629
|
if (seat.kind === "booth") continue;
|
|
6390
6630
|
const key = seat.logicalRowId ?? seat.rowId;
|
|
6391
|
-
(
|
|
6631
|
+
addSeat(key, seat);
|
|
6632
|
+
if (key !== seat.rowId) addSeat(seat.rowId, seat);
|
|
6392
6633
|
}
|
|
6393
6634
|
const seatOrder = (seat) => seat.logicalSeatIndex ?? Number(seat.id.split(":").pop() ?? 0);
|
|
6394
6635
|
for (const obj of view.objects) {
|
|
6395
6636
|
if (obj.type !== "row") continue;
|
|
6396
|
-
|
|
6397
|
-
|
|
6637
|
+
const repeatOnComponents = obj.segmentedRow?.repeatLabelOnComponents === true;
|
|
6638
|
+
if (obj.segmentedRow && !repeatOnComponents && obj.segmentedRow.componentIndex !== 0) continue;
|
|
6639
|
+
const presentation = repeatOnComponents ? obj.labelPresentation : obj.segmentedRow?.labelPresentation ?? obj.labelPresentation;
|
|
6398
6640
|
if (presentation?.visible === false || presentation?.positionPreset === "none") continue;
|
|
6399
|
-
const key = obj.segmentedRow ? obj.segmentedRow.groupId : obj.id;
|
|
6641
|
+
const key = obj.segmentedRow && !repeatOnComponents ? obj.segmentedRow.groupId : obj.id;
|
|
6400
6642
|
const seats = (seatsByRow.get(key) ?? []).slice().sort((a, b) => seatOrder(a) - seatOrder(b));
|
|
6401
6643
|
if (!seats.length) continue;
|
|
6402
6644
|
const first = seats[0];
|
|
@@ -6411,6 +6653,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6411
6653
|
const rotation = presentation?.rotation ?? 0;
|
|
6412
6654
|
const text = obj.segmentedRow?.displayLabel ?? obj.displayLabel ?? obj.label;
|
|
6413
6655
|
const opacity = this.objectFilteredOut(obj) ? 0.15 : 1;
|
|
6656
|
+
const containingSection = this.seatSection.get(first.id);
|
|
6657
|
+
const focusKeys = containingSection ? [...new Set([containingSection.id, containingSection.logicalId, containingSection.zone].filter(
|
|
6658
|
+
(value) => Boolean(value)
|
|
6659
|
+
))] : void 0;
|
|
6414
6660
|
const push = (x, y, automaticAway) => this.rowLabelPlan.push({
|
|
6415
6661
|
text,
|
|
6416
6662
|
x,
|
|
@@ -6419,6 +6665,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6419
6665
|
fontSize,
|
|
6420
6666
|
ink,
|
|
6421
6667
|
opacity,
|
|
6668
|
+
...focusKeys?.length ? { focusKeys } : {},
|
|
6422
6669
|
...automaticAway ? { automaticAway } : {}
|
|
6423
6670
|
});
|
|
6424
6671
|
if (presentation?.position) {
|
|
@@ -6438,6 +6685,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6438
6685
|
push(last.x + away.x * gap, last.y + away.y * gap, away);
|
|
6439
6686
|
}
|
|
6440
6687
|
}
|
|
6688
|
+
const mergeRadius = (this.seatR + 12) * 2.2;
|
|
6689
|
+
const merged = [];
|
|
6690
|
+
for (const entry of this.rowLabelPlan) {
|
|
6691
|
+
const partner = merged.find((candidate) => candidate.text === entry.text && Math.hypot(candidate.x - entry.x, candidate.y - entry.y) <= mergeRadius);
|
|
6692
|
+
if (partner) {
|
|
6693
|
+
partner.x = (partner.x + entry.x) / 2;
|
|
6694
|
+
partner.y = (partner.y + entry.y) / 2;
|
|
6695
|
+
continue;
|
|
6696
|
+
}
|
|
6697
|
+
merged.push(entry);
|
|
6698
|
+
}
|
|
6699
|
+
this.rowLabelPlan = merged;
|
|
6441
6700
|
}
|
|
6442
6701
|
/** Axis-aligned world bounds of a centred, potentially rotated row label. */
|
|
6443
6702
|
rowLabelBox(label, position, padding = ROW_LABEL_CLEARANCE) {
|
|
@@ -6514,12 +6773,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6514
6773
|
this.labelGroup.destroyChildren();
|
|
6515
6774
|
this.labelLiftGroups.clear();
|
|
6516
6775
|
this.seatLabelById.clear();
|
|
6517
|
-
if (!show) {
|
|
6518
|
-
this.overlayLayer.batchDraw();
|
|
6519
|
-
return;
|
|
6520
|
-
}
|
|
6521
6776
|
let count = 0;
|
|
6522
|
-
for (const seat of this.visibleSeatCandidates()) {
|
|
6777
|
+
if (show) for (const seat of this.visibleSeatCandidates()) {
|
|
6523
6778
|
if (seat.kind === "booth") continue;
|
|
6524
6779
|
const screen = this.worldToScreen(seat);
|
|
6525
6780
|
if (screen.x < -20 || screen.x > this.stage.width() + 20 || screen.y < -20 || screen.y > this.stage.height() + 20) continue;
|
|
@@ -6610,13 +6865,16 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6610
6865
|
});
|
|
6611
6866
|
}
|
|
6612
6867
|
for (const rl of this.rowLabelPlan) {
|
|
6868
|
+
const focused = this.focusedSectionId != null && rl.focusKeys?.includes(this.focusedSectionId);
|
|
6869
|
+
const renderedFontSize = focused ? Math.max(rl.fontSize, FOCUSED_ROW_LABEL_MIN_PX / Math.max(effectiveScale, 1e-4)) : rl.fontSize;
|
|
6870
|
+
if (!focused && !isBookableLabelLegibleAtScale(renderedFontSize, effectiveScale)) continue;
|
|
6613
6871
|
const screen = this.worldToScreen({ x: rl.x, y: rl.y });
|
|
6614
6872
|
if (screen.x < -40 || screen.x > this.stage.width() + 40 || screen.y < -40 || screen.y > this.stage.height() + 40) continue;
|
|
6615
6873
|
const t2 = new Text({
|
|
6616
6874
|
x: rl.x,
|
|
6617
6875
|
y: rl.y,
|
|
6618
6876
|
text: rl.text,
|
|
6619
|
-
fontSize:
|
|
6877
|
+
fontSize: renderedFontSize,
|
|
6620
6878
|
fontStyle: "700",
|
|
6621
6879
|
fontFamily: this.labelFont(),
|
|
6622
6880
|
fill: rl.ink,
|
|
@@ -6645,7 +6903,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6645
6903
|
const h = this.container.clientHeight || 1;
|
|
6646
6904
|
if (w === this.stage.width() && h === this.stage.height()) return;
|
|
6647
6905
|
this.stage.size({ width: w, height: h });
|
|
6648
|
-
this.
|
|
6906
|
+
this.refitCurrentView();
|
|
6649
6907
|
}
|
|
6650
6908
|
startFpsLoop() {
|
|
6651
6909
|
const tick = (now) => {
|
|
@@ -6803,6 +7061,99 @@ function pickBestAvailable(seats, available, opts) {
|
|
|
6803
7061
|
return { labels: ranked.slice(0, qty).map((r) => r.seat.label) };
|
|
6804
7062
|
}
|
|
6805
7063
|
|
|
7064
|
+
// src/core/ticketRestrictions.ts
|
|
7065
|
+
function ticketOptionNotApplicableIssue(labels) {
|
|
7066
|
+
return {
|
|
7067
|
+
code: "ticket_option_not_applicable",
|
|
7068
|
+
labels,
|
|
7069
|
+
message: "That ticket choice is not available for the selected seat."
|
|
7070
|
+
};
|
|
7071
|
+
}
|
|
7072
|
+
function ticketTierAppliesToSeat(tier, seat) {
|
|
7073
|
+
const applicability = tier.applicability;
|
|
7074
|
+
if (!applicability) return true;
|
|
7075
|
+
if (applicability.seatLabels?.length && !applicability.seatLabels.includes(seat.label)) return false;
|
|
7076
|
+
if (applicability.accessibility?.length && !applicability.accessibility.some((key) => seat.accessibility?.includes(key))) return false;
|
|
7077
|
+
return true;
|
|
7078
|
+
}
|
|
7079
|
+
function applicableTicketTiers(doc, seat) {
|
|
7080
|
+
return (doc.categories.find((category) => category.key === seat.categoryKey)?.tiers ?? []).filter((tier) => ticketTierAppliesToSeat(tier, seat));
|
|
7081
|
+
}
|
|
7082
|
+
function resolveTicketTierForSeat(doc, seat, tierId) {
|
|
7083
|
+
const tiers = doc.categories.find((category) => category.key === seat.categoryKey)?.tiers;
|
|
7084
|
+
if (!tiers?.length) return { kind: "none" };
|
|
7085
|
+
const tier = tierId == null ? tiers.find((candidate) => ticketTierAppliesToSeat(candidate, seat)) : tiers.find((candidate) => candidate.id === tierId && ticketTierAppliesToSeat(candidate, seat));
|
|
7086
|
+
return tier ? { kind: "resolved", tier } : { kind: "invalid" };
|
|
7087
|
+
}
|
|
7088
|
+
function sameLogicalRow(a, b) {
|
|
7089
|
+
return (a.logicalRowId ?? a.rowId) === (b.logicalRowId ?? b.rowId);
|
|
7090
|
+
}
|
|
7091
|
+
function authoredIndex(seat) {
|
|
7092
|
+
if (Number.isInteger(seat.logicalSeatIndex)) return seat.logicalSeatIndex;
|
|
7093
|
+
const raw = seat.id.slice(seat.id.lastIndexOf(":") + 1);
|
|
7094
|
+
const index = Number(raw);
|
|
7095
|
+
return Number.isInteger(index) ? index : void 0;
|
|
7096
|
+
}
|
|
7097
|
+
function observablyAdjacent(a, b) {
|
|
7098
|
+
if (!sameLogicalRow(a, b)) return false;
|
|
7099
|
+
const aIndex = authoredIndex(a);
|
|
7100
|
+
const bIndex = authoredIndex(b);
|
|
7101
|
+
return aIndex != null && bIndex != null && Math.abs(aIndex - bIndex) === 1;
|
|
7102
|
+
}
|
|
7103
|
+
function companionSelectionIssue(doc, selections) {
|
|
7104
|
+
if (!selections.length) return null;
|
|
7105
|
+
const byLabel = new Map(expandChart(doc, { resolveEyeHeights: false }).map((seat) => [seat.label, seat]));
|
|
7106
|
+
const selected = selections.flatMap((selection) => {
|
|
7107
|
+
const seat = byLabel.get(selection.label);
|
|
7108
|
+
return seat ? [{ selection, seat }] : [];
|
|
7109
|
+
});
|
|
7110
|
+
const wheelchair = selected.map(({ seat }) => seat).filter((seat) => seat.accessibility?.includes("wheelchair"));
|
|
7111
|
+
const unpaired = [];
|
|
7112
|
+
for (const { selection, seat } of selected) {
|
|
7113
|
+
const resolution = resolveTicketTierForSeat(doc, seat, selection.tierId);
|
|
7114
|
+
const tier = resolution.kind === "resolved" ? resolution.tier : void 0;
|
|
7115
|
+
const isCompanion = seat.accessibility?.includes("companion") || tier?.restriction === "companion";
|
|
7116
|
+
if (isCompanion && !wheelchair.some((candidate) => candidate.id !== seat.id && observablyAdjacent(seat, candidate))) {
|
|
7117
|
+
unpaired.push(seat.label);
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
return unpaired.length ? {
|
|
7121
|
+
code: "companion_pair_required",
|
|
7122
|
+
labels: unpaired,
|
|
7123
|
+
message: "Select the adjacent wheelchair place with each companion ticket."
|
|
7124
|
+
} : null;
|
|
7125
|
+
}
|
|
7126
|
+
function ticketSelectionIssue(doc, selections) {
|
|
7127
|
+
if (!selections.length) return null;
|
|
7128
|
+
const byLabel = new Map(expandChart(doc, { resolveEyeHeights: false }).map((seat) => [seat.label, seat]));
|
|
7129
|
+
const notForSaleCategories = new Set(
|
|
7130
|
+
doc.categories.filter((category) => category.notForSale).map((category) => category.key)
|
|
7131
|
+
);
|
|
7132
|
+
const notForSale = [];
|
|
7133
|
+
const inapplicable = [];
|
|
7134
|
+
for (const selection of selections) {
|
|
7135
|
+
const seat = byLabel.get(selection.label);
|
|
7136
|
+
if (!seat) continue;
|
|
7137
|
+
if (notForSaleCategories.has(seat.categoryKey)) {
|
|
7138
|
+
notForSale.push(seat.label);
|
|
7139
|
+
continue;
|
|
7140
|
+
}
|
|
7141
|
+
const resolution = resolveTicketTierForSeat(doc, seat, selection.tierId);
|
|
7142
|
+
if (resolution.kind === "invalid") inapplicable.push(seat.label);
|
|
7143
|
+
}
|
|
7144
|
+
if (notForSale.length) {
|
|
7145
|
+
return {
|
|
7146
|
+
code: "not_for_sale",
|
|
7147
|
+
labels: notForSale,
|
|
7148
|
+
message: "These seats are not for sale."
|
|
7149
|
+
};
|
|
7150
|
+
}
|
|
7151
|
+
if (inapplicable.length) {
|
|
7152
|
+
return ticketOptionNotApplicableIssue(inapplicable);
|
|
7153
|
+
}
|
|
7154
|
+
return companionSelectionIssue(doc, selections);
|
|
7155
|
+
}
|
|
7156
|
+
|
|
6806
7157
|
// src/core/tableInventory.ts
|
|
6807
7158
|
var GroupedTableProjectionError = class extends Error {
|
|
6808
7159
|
constructor(objectId, message) {
|
|
@@ -6857,6 +7208,15 @@ function groupedTableInventory(doc, modelVersion) {
|
|
|
6857
7208
|
// src/picker/PickerController.ts
|
|
6858
7209
|
var DEFAULT_MAX_SELECTION = 10;
|
|
6859
7210
|
var MAX_BACKOFF_MS = 15e3;
|
|
7211
|
+
var PickerSelectionError = class extends Error {
|
|
7212
|
+
constructor(issue) {
|
|
7213
|
+
super(issue.message);
|
|
7214
|
+
this.status = 422;
|
|
7215
|
+
this.name = "PickerSelectionError";
|
|
7216
|
+
this.reason = issue.code;
|
|
7217
|
+
this.labels = issue.labels;
|
|
7218
|
+
}
|
|
7219
|
+
};
|
|
6860
7220
|
var MAP_THEME_KEYS = ["background", "rowLabelColor", "textColor", "selectionColor"];
|
|
6861
7221
|
function sameMapTheme(a, b) {
|
|
6862
7222
|
if (!a || !b) return !a && !b;
|
|
@@ -6986,7 +7346,9 @@ var PickerController = class {
|
|
|
6986
7346
|
if (!r) return true;
|
|
6987
7347
|
const selected = r.getSelection().map((seat) => seat.id);
|
|
6988
7348
|
const floorId = this.getActiveFloorId();
|
|
7349
|
+
const floorOverview = r.isFloorOverview?.() ?? false;
|
|
6989
7350
|
r.setChart(this.visibleDoc(), floorId ? { floorId } : void 0);
|
|
7351
|
+
if (floorOverview) r.setFloorOverview?.(true);
|
|
6990
7352
|
if (this.opts.colorblindSafe) r.setColorblindSafe?.(true);
|
|
6991
7353
|
if (this.closedSections.size) r.setClosedSections?.([...this.closedSections]);
|
|
6992
7354
|
this.applySeatsMap(Object.fromEntries(this.liveStatuses), this.liveDefault);
|
|
@@ -6998,7 +7360,9 @@ var PickerController = class {
|
|
|
6998
7360
|
const next = ids.filter((x) => typeof x === "string");
|
|
6999
7361
|
if (next.length === this.hidden.size && next.every((id) => this.hidden.has(id))) return false;
|
|
7000
7362
|
this.hidden = new Set(next);
|
|
7363
|
+
const floorOverview = this.renderer?.isFloorOverview?.() ?? false;
|
|
7001
7364
|
this.renderer?.setChart(this.visibleDoc());
|
|
7365
|
+
if (floorOverview) this.renderer?.setFloorOverview?.(true);
|
|
7002
7366
|
return true;
|
|
7003
7367
|
}
|
|
7004
7368
|
/** Adopt a new closed-section set; restyle (grey + non-pickable) if it differs.
|
|
@@ -7073,16 +7437,22 @@ var PickerController = class {
|
|
|
7073
7437
|
/** Fetch chart, build label maps, mount the renderer, seed statuses, go live. */
|
|
7074
7438
|
async render(host) {
|
|
7075
7439
|
if (this.renderer) return null;
|
|
7440
|
+
const renderStartedAt = performance.now();
|
|
7441
|
+
const profile = {};
|
|
7442
|
+
const duration = (startedAt) => Math.round((performance.now() - startedAt) * 100) / 100;
|
|
7076
7443
|
this.closed = false;
|
|
7077
7444
|
let res;
|
|
7445
|
+
const chartStartedAt = performance.now();
|
|
7078
7446
|
try {
|
|
7079
7447
|
res = await this.api.chart(this.key);
|
|
7080
7448
|
} catch (err) {
|
|
7081
7449
|
this.emitError(err);
|
|
7082
7450
|
return null;
|
|
7083
7451
|
}
|
|
7452
|
+
profile.loadChart = duration(chartStartedAt);
|
|
7084
7453
|
if (this.closed) return null;
|
|
7085
7454
|
this._doc = res.doc;
|
|
7455
|
+
const normalizeStartedAt = performance.now();
|
|
7086
7456
|
try {
|
|
7087
7457
|
const groups = groupedTableInventory(
|
|
7088
7458
|
res.doc,
|
|
@@ -7147,13 +7517,16 @@ var PickerController = class {
|
|
|
7147
7517
|
this.labelToId.set(group.label, representative.id);
|
|
7148
7518
|
this.labelToSeat.set(group.label, representative);
|
|
7149
7519
|
}
|
|
7520
|
+
profile.normalizeBuyerModel = duration(normalizeStartedAt);
|
|
7150
7521
|
const currency = res.event.currency ?? this.opts.currency;
|
|
7151
7522
|
this.currency = currency ?? "USD";
|
|
7523
|
+
const rendererStartedAt = performance.now();
|
|
7152
7524
|
const renderer = createRenderer(host, {
|
|
7153
7525
|
// Group chairs are selected together for paint, but count as one logical
|
|
7154
7526
|
// inventory line. The controller enforces the guest-weighted cap.
|
|
7155
7527
|
maxSelection: this.rendererSelectionCap(),
|
|
7156
7528
|
confirmSelection: this.opts.confirmSelection,
|
|
7529
|
+
portraitFitCrop: true,
|
|
7157
7530
|
currency,
|
|
7158
7531
|
onSelect: (seat) => {
|
|
7159
7532
|
this.handleRendererSelect(seat);
|
|
@@ -7174,6 +7547,7 @@ var PickerController = class {
|
|
|
7174
7547
|
onDeckTap: (floorId) => this.handleDeckTap(floorId),
|
|
7175
7548
|
onFps: this.opts.onFps
|
|
7176
7549
|
});
|
|
7550
|
+
profile.createRenderer = duration(rendererStartedAt);
|
|
7177
7551
|
if (this.closed) {
|
|
7178
7552
|
renderer.destroy();
|
|
7179
7553
|
return null;
|
|
@@ -7182,6 +7556,7 @@ var PickerController = class {
|
|
|
7182
7556
|
let seats = null;
|
|
7183
7557
|
let seatsDefault = "free";
|
|
7184
7558
|
let closedIds = [];
|
|
7559
|
+
const statusesStartedAt = performance.now();
|
|
7185
7560
|
try {
|
|
7186
7561
|
const objs = await this.api.objects(this.key);
|
|
7187
7562
|
this.hidden = new Set(objs.hidden ?? []);
|
|
@@ -7190,18 +7565,30 @@ var PickerController = class {
|
|
|
7190
7565
|
seatsDefault = objs.default ?? "free";
|
|
7191
7566
|
} catch {
|
|
7192
7567
|
}
|
|
7568
|
+
profile.loadStatuses = duration(statusesStartedAt);
|
|
7193
7569
|
if (this.closed) {
|
|
7194
7570
|
renderer.destroy();
|
|
7195
7571
|
this.renderer = null;
|
|
7196
7572
|
return null;
|
|
7197
7573
|
}
|
|
7574
|
+
const sceneStartedAt = performance.now();
|
|
7198
7575
|
renderer.setChart(this.visibleDoc());
|
|
7576
|
+
if ((res.doc.floors?.length ?? 0) > 1) renderer.setFloorOverview?.(true);
|
|
7199
7577
|
if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
|
|
7578
|
+
profile.buildScene = duration(sceneStartedAt);
|
|
7200
7579
|
this.closedSections = new Set(closedIds);
|
|
7580
|
+
const initialStateStartedAt = performance.now();
|
|
7201
7581
|
if (closedIds.length) renderer.setClosedSections?.(closedIds);
|
|
7202
7582
|
if (seats) this.applySeatsMap(seats, seatsDefault);
|
|
7583
|
+
profile.applyInitialState = duration(initialStateStartedAt);
|
|
7584
|
+
const firstPaintStartedAt = performance.now();
|
|
7585
|
+
renderer.forceDraw();
|
|
7586
|
+
profile.firstPaint = duration(firstPaintStartedAt);
|
|
7587
|
+
const realtimeStartedAt = performance.now();
|
|
7203
7588
|
this.attachVisibilityListener();
|
|
7204
7589
|
this.connect();
|
|
7590
|
+
profile.startRealtime = duration(realtimeStartedAt);
|
|
7591
|
+
profile.controllerTotal = duration(renderStartedAt);
|
|
7205
7592
|
return {
|
|
7206
7593
|
doc: res.doc,
|
|
7207
7594
|
salesClosed: !!res.event.salesClosed,
|
|
@@ -7212,7 +7599,8 @@ var PickerController = class {
|
|
|
7212
7599
|
currency,
|
|
7213
7600
|
// 'test' ⇒ sandbox event: hosts render a TEST MODE ribbon (Phase 11).
|
|
7214
7601
|
mode: res.event.mode ?? "live",
|
|
7215
|
-
posterUrl: res.event.posterUrl ?? null
|
|
7602
|
+
posterUrl: res.event.posterUrl ?? null,
|
|
7603
|
+
performanceProfile: profile
|
|
7216
7604
|
};
|
|
7217
7605
|
}
|
|
7218
7606
|
// ---- selection ------------------------------------------------------------
|
|
@@ -7268,11 +7656,23 @@ var PickerController = class {
|
|
|
7268
7656
|
select(ids) {
|
|
7269
7657
|
const before = new Set(this.getSelection().map((seat) => seat.label));
|
|
7270
7658
|
const expanded = /* @__PURE__ */ new Set();
|
|
7659
|
+
const rejected = [];
|
|
7271
7660
|
for (const id of ids) {
|
|
7272
7661
|
const table = this.groupedTableBySeatId.get(id) ?? this.groupedTablesByLabel.get(id);
|
|
7273
|
-
if (table)
|
|
7274
|
-
|
|
7662
|
+
if (table) {
|
|
7663
|
+
const resolution = this.tableTierResolution(table);
|
|
7664
|
+
if (resolution.kind === "invalid") rejected.push(table.label);
|
|
7665
|
+
else table.chairs.forEach((chair) => expanded.add(chair.id));
|
|
7666
|
+
} else {
|
|
7667
|
+
const seat = this.seatById.get(id);
|
|
7668
|
+
if (seat && this.seatTierResolution(seat).kind === "invalid") rejected.push(seat.label);
|
|
7669
|
+
else expanded.add(id);
|
|
7670
|
+
}
|
|
7275
7671
|
}
|
|
7672
|
+
if (rejected.length) {
|
|
7673
|
+
this.emitError(new PickerSelectionError(ticketOptionNotApplicableIssue(rejected)));
|
|
7674
|
+
}
|
|
7675
|
+
if (!expanded.size) return [];
|
|
7276
7676
|
this.renderer?.select?.([...expanded]);
|
|
7277
7677
|
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7278
7678
|
this.renderer?.deselect([...expanded]);
|
|
@@ -7340,6 +7740,13 @@ var PickerController = class {
|
|
|
7340
7740
|
}
|
|
7341
7741
|
handleRendererSelect(seat) {
|
|
7342
7742
|
const table = this.groupedTableBySeatId.get(seat.id);
|
|
7743
|
+
const resolution = table ? this.tableTierResolution(table) : this.seatTierResolution(seat);
|
|
7744
|
+
if (resolution.kind === "invalid") {
|
|
7745
|
+
this.renderer?.deselect(table ? table.chairs.map((chair) => chair.id) : [seat.id]);
|
|
7746
|
+
this.emitError(new PickerSelectionError(ticketOptionNotApplicableIssue([table?.label ?? seat.label])));
|
|
7747
|
+
this.emitSelectionChange();
|
|
7748
|
+
return;
|
|
7749
|
+
}
|
|
7343
7750
|
if (table) {
|
|
7344
7751
|
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7345
7752
|
this.renderer?.deselect(table.chairs.map((chair) => chair.id));
|
|
@@ -7421,6 +7828,8 @@ var PickerController = class {
|
|
|
7421
7828
|
const existingGA = (this.hold_?.items ?? []).filter((item) => item.objectType === "ga");
|
|
7422
7829
|
const combinedLabels = [.../* @__PURE__ */ new Set([...existingGA.map((item) => item.label), ...labels])];
|
|
7423
7830
|
if (!combinedLabels.length) return null;
|
|
7831
|
+
const restriction = this.ticketIssue(combinedLabels);
|
|
7832
|
+
if (restriction) throw new PickerSelectionError(restriction);
|
|
7424
7833
|
if (this.hold_ && this.holdCovers(combinedLabels)) return this.hold_;
|
|
7425
7834
|
try {
|
|
7426
7835
|
const selections = combinedLabels.map((label) => {
|
|
@@ -7592,6 +8001,7 @@ var PickerController = class {
|
|
|
7592
8001
|
const available = /* @__PURE__ */ new Set();
|
|
7593
8002
|
for (const seat of seats) {
|
|
7594
8003
|
if (held.has(seat.label)) continue;
|
|
8004
|
+
if (this.seatTierResolution(seat).kind === "invalid") continue;
|
|
7595
8005
|
const id = this.labelToId.get(seat.label);
|
|
7596
8006
|
const status = id ? this.getStatus(id) : void 0;
|
|
7597
8007
|
if ((status ?? "free") === "free") available.add(seat.label);
|
|
@@ -7673,6 +8083,8 @@ var PickerController = class {
|
|
|
7673
8083
|
if (!this.api.book) throw new Error("picker: transport has no book() \u2014 hold-only mode");
|
|
7674
8084
|
const labels = labelsArg ?? this.getSelection().map((seat) => seat.label);
|
|
7675
8085
|
if (!labels.length) return null;
|
|
8086
|
+
const restriction = this.ticketIssue(labels);
|
|
8087
|
+
if (restriction) throw new PickerSelectionError(restriction);
|
|
7676
8088
|
let holdId;
|
|
7677
8089
|
try {
|
|
7678
8090
|
if (this.hold_ && this.holdCovers(labels)) {
|
|
@@ -7799,6 +8211,20 @@ var PickerController = class {
|
|
|
7799
8211
|
getStatus(id) {
|
|
7800
8212
|
return this.renderer?.getStatus(id);
|
|
7801
8213
|
}
|
|
8214
|
+
/** Seat ids whose category the author marked not-for-sale. */
|
|
8215
|
+
notForSaleCategorySeatIds() {
|
|
8216
|
+
const doc = this.doc;
|
|
8217
|
+
if (!doc) return [];
|
|
8218
|
+
const flagged = new Set(
|
|
8219
|
+
doc.categories.filter((category) => category.notForSale).map((category) => category.key)
|
|
8220
|
+
);
|
|
8221
|
+
if (!flagged.size) return [];
|
|
8222
|
+
const ids = [];
|
|
8223
|
+
for (const [id, seat] of this.seatById) {
|
|
8224
|
+
if (flagged.has(seat.categoryKey)) ids.push(id);
|
|
8225
|
+
}
|
|
8226
|
+
return ids;
|
|
8227
|
+
}
|
|
7802
8228
|
flashSeat(id, color) {
|
|
7803
8229
|
this.renderer?.flashSeat(id, color);
|
|
7804
8230
|
}
|
|
@@ -7811,6 +8237,11 @@ var PickerController = class {
|
|
|
7811
8237
|
zoomToFit() {
|
|
7812
8238
|
this.renderer?.zoomToFit();
|
|
7813
8239
|
}
|
|
8240
|
+
/** Preserve section focus while the usable map viewport changes. */
|
|
8241
|
+
refitCurrentView() {
|
|
8242
|
+
if (this.renderer?.refitCurrentView) this.renderer.refitCurrentView();
|
|
8243
|
+
else this.renderer?.zoomToFit();
|
|
8244
|
+
}
|
|
7814
8245
|
worldToScreen(p) {
|
|
7815
8246
|
return this.renderer?.worldToScreen(p) ?? { x: 0, y: 0 };
|
|
7816
8247
|
}
|
|
@@ -7840,7 +8271,8 @@ var PickerController = class {
|
|
|
7840
8271
|
/** Switch the shown floor (2D), then re-apply live seat statuses onto it. */
|
|
7841
8272
|
setFloor(id) {
|
|
7842
8273
|
const r = this.renderer;
|
|
7843
|
-
if (!r?.setActiveFloor || id === r.getActiveFloorId?.()) return;
|
|
8274
|
+
if (!r?.setActiveFloor || id === r.getActiveFloorId?.() && !r.isFloorOverview?.()) return;
|
|
8275
|
+
r.setFloorOverview?.(false);
|
|
7844
8276
|
r.setStacked?.(false);
|
|
7845
8277
|
r.setActiveFloor(id);
|
|
7846
8278
|
void this.resnapshot();
|
|
@@ -7856,12 +8288,24 @@ var PickerController = class {
|
|
|
7856
8288
|
isMultiFloor() {
|
|
7857
8289
|
return (this._doc?.floors?.length ?? 0) > 1;
|
|
7858
8290
|
}
|
|
8291
|
+
/** Enter/leave the source-coordinate all-floor buyer overview. */
|
|
8292
|
+
setFloorOverview(on) {
|
|
8293
|
+
const r = this.renderer;
|
|
8294
|
+
if (!r?.setFloorOverview || on === r.isFloorOverview?.()) return;
|
|
8295
|
+
r.setFloorOverview(on);
|
|
8296
|
+
void this.resnapshot();
|
|
8297
|
+
this.emitSelectionChange();
|
|
8298
|
+
}
|
|
8299
|
+
isFloorOverview() {
|
|
8300
|
+
return this.renderer?.isFloorOverview?.() ?? false;
|
|
8301
|
+
}
|
|
7859
8302
|
/** Tap-a-deck-to-enter: leave the 3D stack, drop to flat 2D on `floorId`, and
|
|
7860
8303
|
* tell the host so it can sync its 2D/3D toggle + floor-switcher state. */
|
|
7861
8304
|
handleDeckTap(floorId) {
|
|
7862
8305
|
const r = this.renderer;
|
|
7863
8306
|
if (!r) return;
|
|
7864
8307
|
r.setViewMode?.("flat");
|
|
8308
|
+
r.setFloorOverview?.(false);
|
|
7865
8309
|
r.setStacked?.(false);
|
|
7866
8310
|
r.setActiveFloor?.(floorId);
|
|
7867
8311
|
void this.resnapshot();
|
|
@@ -7930,10 +8374,30 @@ var PickerController = class {
|
|
|
7930
8374
|
if (!r?.getVisibleWorldRect || !r.getWorldBounds) return null;
|
|
7931
8375
|
return { visible: r.getVisibleWorldRect(), bounds: r.getWorldBounds() };
|
|
7932
8376
|
}
|
|
8377
|
+
getMinimapSnapshot() {
|
|
8378
|
+
return this.renderer?.getMinimapSnapshot?.() ?? { sections: [], seats: [] };
|
|
8379
|
+
}
|
|
8380
|
+
panToWorld(point) {
|
|
8381
|
+
this.renderer?.panToWorld?.(point);
|
|
8382
|
+
}
|
|
8383
|
+
/** Leave section-focus dimming without changing the current zoom. Used when
|
|
8384
|
+
* the minimap viewport itself is dragged to a different part of the venue. */
|
|
8385
|
+
clearSectionFocus() {
|
|
8386
|
+
this.renderer?.clearSectionFocus?.();
|
|
8387
|
+
this.opts.onSectionFocus?.(null);
|
|
8388
|
+
}
|
|
8389
|
+
/** Read-only browser-render evidence for parity/performance audit tooling. */
|
|
8390
|
+
getRenderedQualityEvidence() {
|
|
8391
|
+
return this.renderer?.getRenderedQualityEvidence() ?? null;
|
|
8392
|
+
}
|
|
7933
8393
|
/** Glide in on a section and surface its summary (same path as a section tap). */
|
|
7934
8394
|
focusSection(id) {
|
|
7935
8395
|
this.handleSectionTap(id);
|
|
7936
8396
|
}
|
|
8397
|
+
/** Live seat membership count used by buyer section-navigation controls. */
|
|
8398
|
+
getSectionSeatCount(id) {
|
|
8399
|
+
return this.renderer?.sectionMembers?.(id)?.length ?? 0;
|
|
8400
|
+
}
|
|
7937
8401
|
/** Zoom back out to the whole chart and clear the section-summary card + focus. */
|
|
7938
8402
|
overview() {
|
|
7939
8403
|
this.renderer?.clearSectionFocus?.();
|
|
@@ -7986,15 +8450,21 @@ var PickerController = class {
|
|
|
7986
8450
|
}
|
|
7987
8451
|
const categories = [...byCat.entries()].map(([key, count]) => {
|
|
7988
8452
|
const cat = doc.categories.find((c) => c.key === key);
|
|
8453
|
+
const prices = cat?.tiers?.length ? cat.tiers.map((tier) => tier.price) : [cat?.price ?? 0];
|
|
8454
|
+
const priceMin = Math.min(...prices);
|
|
8455
|
+
const priceMax = Math.max(...prices);
|
|
7989
8456
|
return {
|
|
7990
8457
|
key,
|
|
7991
8458
|
count,
|
|
7992
8459
|
label: cat?.label ?? key,
|
|
7993
8460
|
color: cat?.color ?? "#6e7bff",
|
|
7994
|
-
price:
|
|
8461
|
+
price: priceMin,
|
|
8462
|
+
priceMin,
|
|
8463
|
+
priceMax
|
|
7995
8464
|
};
|
|
7996
8465
|
}).sort((a, b) => a.price - b.price);
|
|
7997
|
-
const
|
|
8466
|
+
const minimums = categories.map((c) => c.priceMin);
|
|
8467
|
+
const maximums = categories.map((c) => c.priceMax);
|
|
7998
8468
|
const zone = sec.zone ? doc.zones?.find((z) => z.id === sec.zone) : void 0;
|
|
7999
8469
|
const color = sec.color ?? zone?.color ?? categories[0]?.color ?? "#6e7bff";
|
|
8000
8470
|
return {
|
|
@@ -8004,8 +8474,8 @@ var PickerController = class {
|
|
|
8004
8474
|
...sec.entrance && sec.entrance.trim() ? { entrance: sec.entrance.trim() } : {},
|
|
8005
8475
|
color,
|
|
8006
8476
|
seatsLeft,
|
|
8007
|
-
priceMin:
|
|
8008
|
-
priceMax:
|
|
8477
|
+
priceMin: minimums.length ? Math.min(...minimums) : 0,
|
|
8478
|
+
priceMax: maximums.length ? Math.max(...maximums) : 0,
|
|
8009
8479
|
categories
|
|
8010
8480
|
};
|
|
8011
8481
|
}
|
|
@@ -8046,11 +8516,12 @@ var PickerController = class {
|
|
|
8046
8516
|
const accessibility = s.accessibility?.length ? { accessibility: s.accessibility } : void 0;
|
|
8047
8517
|
const wheelchair = s.wheelchairSpaceType ? { wheelchairSpaceType: s.wheelchairSpaceType } : void 0;
|
|
8048
8518
|
const context = this.seatContext.get(s.id);
|
|
8049
|
-
const tiers = this.
|
|
8050
|
-
|
|
8051
|
-
|
|
8519
|
+
const tiers = this.tiersForSeat(s);
|
|
8520
|
+
const resolution = this.seatTierResolution(s);
|
|
8521
|
+
if (resolution.kind !== "resolved" || !tiers) {
|
|
8522
|
+
return { id: s.id, label: s.label, ...display, ...context, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), currency: this.currency, ...commercial, ...accessibility, ...wheelchair };
|
|
8052
8523
|
}
|
|
8053
|
-
const chosen =
|
|
8524
|
+
const chosen = resolution.tier;
|
|
8054
8525
|
return {
|
|
8055
8526
|
id: s.id,
|
|
8056
8527
|
label: s.label,
|
|
@@ -8058,6 +8529,7 @@ var PickerController = class {
|
|
|
8058
8529
|
...context,
|
|
8059
8530
|
categoryKey: s.categoryKey,
|
|
8060
8531
|
price: chosen.price,
|
|
8532
|
+
currency: this.currency,
|
|
8061
8533
|
tiers,
|
|
8062
8534
|
tierId: chosen.id,
|
|
8063
8535
|
...commercial,
|
|
@@ -8067,8 +8539,9 @@ var PickerController = class {
|
|
|
8067
8539
|
}
|
|
8068
8540
|
toTableSeat(table) {
|
|
8069
8541
|
const representative = table.chairs[0];
|
|
8070
|
-
const tiers = this.tiersFor(table.categoryKey);
|
|
8071
|
-
const
|
|
8542
|
+
const tiers = this.tiersFor(table.categoryKey)?.filter((tier) => resolveTicketTierForSeat(this._doc, this.tableTierSeat(table), tier.id).kind === "resolved");
|
|
8543
|
+
const resolution = this.tableTierResolution(table);
|
|
8544
|
+
const chosen = resolution.kind === "resolved" ? resolution.tier : void 0;
|
|
8072
8545
|
return {
|
|
8073
8546
|
id: representative?.id ?? table.objectId,
|
|
8074
8547
|
label: table.label,
|
|
@@ -8079,6 +8552,7 @@ var PickerController = class {
|
|
|
8079
8552
|
rowLabel: table.displayLabel ?? table.label,
|
|
8080
8553
|
categoryKey: table.categoryKey,
|
|
8081
8554
|
price: chosen?.price ?? this.priceFor(table.categoryKey),
|
|
8555
|
+
currency: this.currency,
|
|
8082
8556
|
...tiers ? { tiers, tierId: chosen?.id } : {},
|
|
8083
8557
|
objectType: "table",
|
|
8084
8558
|
bookingMode: table.mode,
|
|
@@ -8108,6 +8582,27 @@ var PickerController = class {
|
|
|
8108
8582
|
const t2 = this._doc?.categories.find((c) => c.key === categoryKey)?.tiers;
|
|
8109
8583
|
return t2 && t2.length ? t2 : void 0;
|
|
8110
8584
|
}
|
|
8585
|
+
tiersForSeat(seat) {
|
|
8586
|
+
if (!this._doc) return void 0;
|
|
8587
|
+
const tiers = applicableTicketTiers(this._doc, seat);
|
|
8588
|
+
return tiers.length ? tiers : void 0;
|
|
8589
|
+
}
|
|
8590
|
+
seatTierResolution(seat) {
|
|
8591
|
+
if (!this._doc) return { kind: "none" };
|
|
8592
|
+
return resolveTicketTierForSeat(this._doc, seat, this.seatTiers.get(seat.id));
|
|
8593
|
+
}
|
|
8594
|
+
tableTierSeat(table) {
|
|
8595
|
+
return {
|
|
8596
|
+
label: table.label,
|
|
8597
|
+
categoryKey: table.categoryKey,
|
|
8598
|
+
accessibility: table.chairs.flatMap((chair) => chair.accessibility ?? [])
|
|
8599
|
+
};
|
|
8600
|
+
}
|
|
8601
|
+
tableTierResolution(table) {
|
|
8602
|
+
if (!this._doc) return { kind: "none" };
|
|
8603
|
+
const representativeId = table.chairs[0]?.id ?? "";
|
|
8604
|
+
return resolveTicketTierForSeat(this._doc, this.tableTierSeat(table), this.seatTiers.get(representativeId));
|
|
8605
|
+
}
|
|
8111
8606
|
/**
|
|
8112
8607
|
* Choose a ticket tier for a selected seat (e.g. Adult → Child). Re-emits the
|
|
8113
8608
|
* selection so the host's cart + the eventual hold carry the new tier + price.
|
|
@@ -8117,7 +8612,7 @@ var PickerController = class {
|
|
|
8117
8612
|
setSeatTier(seatId, tierId) {
|
|
8118
8613
|
const seat = this.seatById.get(seatId);
|
|
8119
8614
|
if (!seat) return;
|
|
8120
|
-
const tiers = this.
|
|
8615
|
+
const tiers = this.tiersForSeat(seat);
|
|
8121
8616
|
if (!tiers) return;
|
|
8122
8617
|
if (tierId == null) this.seatTiers.delete(seatId);
|
|
8123
8618
|
else if (tiers.some((t2) => t2.id === tierId)) this.seatTiers.set(seatId, tierId);
|
|
@@ -8138,6 +8633,16 @@ var PickerController = class {
|
|
|
8138
8633
|
this.opts.onSelectionChange?.(this.getSelection());
|
|
8139
8634
|
this.emitOrphanHint();
|
|
8140
8635
|
}
|
|
8636
|
+
/** Check explicit tier applicability and companion relationships for the
|
|
8637
|
+
* current (or supplied) seats. Ticket display names are never semantic input. */
|
|
8638
|
+
ticketIssue(labels) {
|
|
8639
|
+
if (!this._doc) return null;
|
|
8640
|
+
const seats = labels ? this.seatsForLabels(labels) : this.getSelection();
|
|
8641
|
+
return ticketSelectionIssue(this._doc, seats.map((seat) => ({
|
|
8642
|
+
label: seat.label,
|
|
8643
|
+
...seat.tierId ? { tierId: seat.tierId } : {}
|
|
8644
|
+
})));
|
|
8645
|
+
}
|
|
8141
8646
|
/**
|
|
8142
8647
|
* Orphan-seat advice on manual selection: when the buyer's current picks
|
|
8143
8648
|
* strand one (or more) single free seats between unavailable same-row
|
|
@@ -8148,6 +8653,12 @@ var PickerController = class {
|
|
|
8148
8653
|
if (!this.opts.onHint) return;
|
|
8149
8654
|
const r = this.renderer;
|
|
8150
8655
|
if (!r) return;
|
|
8656
|
+
const restriction = this.ticketIssue();
|
|
8657
|
+
if (restriction) {
|
|
8658
|
+
this.hintShown = true;
|
|
8659
|
+
this.opts.onHint(restriction.code === "companion_pair_required" ? t("picker.companionPairRequired") : "That ticket choice is not available for the selected seat.");
|
|
8660
|
+
return;
|
|
8661
|
+
}
|
|
8151
8662
|
const selected = new Set(r.getSelection().map((s) => s.id));
|
|
8152
8663
|
const stranded = selected.size ? strandedSingles(this.seatById.values(), (id) => r.getStatus(id), selected) : [];
|
|
8153
8664
|
if (stranded.length > 0) {
|
|
@@ -8269,6 +8780,8 @@ var PickerController = class {
|
|
|
8269
8780
|
["free", "held", "booked", "not_for_sale"].forEach((st) => {
|
|
8270
8781
|
if (st !== defaultSeatStatus && byStatus[st].length) r.setStatus(byStatus[st], st);
|
|
8271
8782
|
});
|
|
8783
|
+
const notForSaleCategoryIds = this.notForSaleCategorySeatIds();
|
|
8784
|
+
if (notForSaleCategoryIds.length) r.setStatus(notForSaleCategoryIds, "not_for_sale");
|
|
8272
8785
|
this.clearBookedHoldIfSettled();
|
|
8273
8786
|
this.opts.onStatusChange?.();
|
|
8274
8787
|
}
|
|
@@ -9621,9 +10134,9 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
9621
10134
|
|
|
9622
10135
|
// src/i18n/bundles.ts
|
|
9623
10136
|
var LOADERS = {
|
|
9624
|
-
es: () => import("./es-
|
|
9625
|
-
de: () => import("./de-
|
|
9626
|
-
fr: () => import("./fr-
|
|
10137
|
+
es: () => import("./es-VMBTGVFQ.js").then((m) => ({ default: m.es })),
|
|
10138
|
+
de: () => import("./de-YGODYJ3E.js").then((m) => ({ default: m.de })),
|
|
10139
|
+
fr: () => import("./fr-SLBE2ULB.js").then((m) => ({ default: m.fr }))
|
|
9627
10140
|
};
|
|
9628
10141
|
var loaded = /* @__PURE__ */ new Set(["en"]);
|
|
9629
10142
|
async function loadLocale(code) {
|
|
@@ -9653,6 +10166,7 @@ export {
|
|
|
9653
10166
|
MAX_EVENT_INVENTORY,
|
|
9654
10167
|
MAX_GA_CAPACITY,
|
|
9655
10168
|
PickerController,
|
|
10169
|
+
PickerSelectionError,
|
|
9656
10170
|
RENDERED_QUALITY_REPORT_VERSION,
|
|
9657
10171
|
SEAT_COMMERCIAL_MARKS,
|
|
9658
10172
|
SUPPORTED_LOCALES,
|
|
@@ -9676,6 +10190,7 @@ export {
|
|
|
9676
10190
|
expandTable,
|
|
9677
10191
|
expandTableSlots,
|
|
9678
10192
|
floorObjects,
|
|
10193
|
+
floorOverviewTranslations,
|
|
9679
10194
|
floorsOf,
|
|
9680
10195
|
formatDate,
|
|
9681
10196
|
formatMinor,
|
|
@@ -9694,6 +10209,7 @@ export {
|
|
|
9694
10209
|
isGaUnitLabel,
|
|
9695
10210
|
isSectionHidden,
|
|
9696
10211
|
layerOf,
|
|
10212
|
+
layoutFloorOverview,
|
|
9697
10213
|
loadLocale,
|
|
9698
10214
|
objectCenter,
|
|
9699
10215
|
owningSectionForObject,
|
|
@@ -9713,6 +10229,7 @@ export {
|
|
|
9713
10229
|
tCount,
|
|
9714
10230
|
tableInventoryCount,
|
|
9715
10231
|
tableSeatCountsBySide,
|
|
10232
|
+
translateObject,
|
|
9716
10233
|
validGAInventorySegments
|
|
9717
10234
|
};
|
|
9718
10235
|
//# sourceMappingURL=index.js.map
|