@seatlayer/core 0.50.2 → 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-YGODYJ3E.js +292 -0
- package/dist/de-YGODYJ3E.js.map +1 -0
- package/dist/es-VMBTGVFQ.js +292 -0
- package/dist/es-VMBTGVFQ.js.map +1 -0
- package/dist/fr-SLBE2ULB.js +292 -0
- package/dist/fr-SLBE2ULB.js.map +1 -0
- package/dist/index.cjs +1606 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -17
- package/dist/index.d.ts +138 -17
- package/dist/index.js +829 -65
- 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-ST4H423D.js +0 -56
- package/dist/de-ST4H423D.js.map +0 -1
- package/dist/es-WEC5NHRT.js +0 -56
- package/dist/es-WEC5NHRT.js.map +0 -1
- package/dist/fr-SGBFNAQV.js +0 -56
- package/dist/fr-SGBFNAQV.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",
|
|
@@ -1324,7 +1329,241 @@ var en = {
|
|
|
1324
1329
|
"picker.premiumFallbackNote": "No premium block of {count} \u2014 showing best overall",
|
|
1325
1330
|
"picker.loading3d": "Building the 3D venue\u2026",
|
|
1326
1331
|
"picker.unavailable3d": "3D could not start. The seat map is still available.",
|
|
1327
|
-
"picker.jumpToSection": "Jump to section"
|
|
1332
|
+
"picker.jumpToSection": "Jump to section",
|
|
1333
|
+
"picker.salesClosedPill": "Sales are closed",
|
|
1334
|
+
"picker.salesClosedCopy": "Ticket sales for this event have ended.",
|
|
1335
|
+
"picker.salesClosedCta": "Sales closed",
|
|
1336
|
+
"picker.salesClosedToast": "Sales are closed for this event.",
|
|
1337
|
+
"picker.holdReassurance": "Yours for {time} \u2014 you won\u2019t be charged yet.",
|
|
1338
|
+
"picker.securingSeats": "Securing your seats\u2026",
|
|
1339
|
+
"picker.openingCheckout": "Opening secure checkout\u2026",
|
|
1340
|
+
"picker.peekSecured": "\u2713 {count} secured \xB7 {total} \u2014 you won\u2019t be charged yet",
|
|
1341
|
+
"picker.priceHint": "Colours on the map are ticket types \u2014 tap one below to show just those seats.",
|
|
1342
|
+
"picker.findTogetherInstead": "Find seats together instead",
|
|
1343
|
+
"picker.keepMyPicks": "Keep my picks",
|
|
1344
|
+
// picker widget — buyer-facing SeatPicker.ts strings (localization pass)
|
|
1345
|
+
"picker.accessExpiredBody": "Sign in again, or reload the page, to keep browsing these seats. Anything you are already holding stays yours.",
|
|
1346
|
+
"picker.accessExpiredTitle": "Your access session has ended",
|
|
1347
|
+
"picker.accessInvalidBody": "You can still book anything shown as available. Contact whoever sent you here for access to the rest.",
|
|
1348
|
+
"picker.accessInvalidTitle": "We couldn\u2019t verify your access",
|
|
1349
|
+
"picker.accessPausedBody": "The organizer has paused this selection. Try again in a few minutes.",
|
|
1350
|
+
"picker.accessPausedTitle": "These seats are on hold right now",
|
|
1351
|
+
"picker.accessRetry": "Try again",
|
|
1352
|
+
"picker.accessRevokedBody": "Ask whoever sent you here for a new link to keep booking these seats.",
|
|
1353
|
+
"picker.accessRevokedTitle": "This access link is no longer active",
|
|
1354
|
+
"picker.accessiblePhysicalSeat": "Accessible physical seat",
|
|
1355
|
+
"picker.addTime": "Add time",
|
|
1356
|
+
"picker.addingEllipsis": "Adding\u2026",
|
|
1357
|
+
"picker.allLevels": "All levels",
|
|
1358
|
+
"picker.allPlacesBookedTogether": "All {count} places are booked together as one exclusive table.",
|
|
1359
|
+
"picker.allPrices": "All prices",
|
|
1360
|
+
"picker.allSeats": "All seats",
|
|
1361
|
+
"picker.allSetTitle": "You\u2019re all set",
|
|
1362
|
+
"picker.anyTicketType": "Any ticket type",
|
|
1363
|
+
"picker.anyVenueZone": "Any venue zone",
|
|
1364
|
+
"picker.areas": "Areas",
|
|
1365
|
+
"picker.available": "Available",
|
|
1366
|
+
"picker.backToMap": "Back to map",
|
|
1367
|
+
"picker.backToVenue": "Back to venue",
|
|
1368
|
+
"picker.bestSeatsStar": "\u2726 Best seats",
|
|
1369
|
+
"picker.booth": "Booth",
|
|
1370
|
+
"picker.capacityGuests": "{count} guests",
|
|
1371
|
+
"picker.change": "Change",
|
|
1372
|
+
"picker.chartDerivedModel": "Chart-derived model",
|
|
1373
|
+
"picker.chartDerivedSeatEye": "Live 3D \xB7 chart-derived seat-eye \xB7 not surveyed",
|
|
1374
|
+
"picker.checkConnection": "Check your connection and try again.",
|
|
1375
|
+
"picker.checkoutCouldNotBeOpened": "Checkout could not be opened. Your seats are still held \u2014 please try again.",
|
|
1376
|
+
"picker.chooseAnotherToCompare": "Choose another seat to compare.",
|
|
1377
|
+
"picker.chooseGuestsCopy": "Choose how many guests will sit together. This table is held exclusively for your party.",
|
|
1378
|
+
"picker.chooseMinMaxGuests": "Choose {min}\u2013{max} guests",
|
|
1379
|
+
"picker.clearSavedSeatComparison": "Clear saved seat comparison",
|
|
1380
|
+
"picker.close": "Close",
|
|
1381
|
+
"picker.closeSeatStatus": "Close seat status",
|
|
1382
|
+
"picker.closestGroupChosenInstantly": "Closest available group, chosen instantly.",
|
|
1383
|
+
"picker.collapseTicketPanel": "Collapse ticket panel",
|
|
1384
|
+
"picker.compareCount": "Compare {count}",
|
|
1385
|
+
"picker.compareWithSaved": "Compare with saved",
|
|
1386
|
+
"picker.confirmOrCancelSeat": "Confirm or cancel this seat",
|
|
1387
|
+
"picker.confirmSeatLabel": "Confirm seat {label}",
|
|
1388
|
+
"picker.confirmYourTable": "Confirm your table",
|
|
1389
|
+
"picker.confirmedAndOnWay": "confirmed. A confirmation is on its way.",
|
|
1390
|
+
"picker.continue": "Continue",
|
|
1391
|
+
"picker.continueToCheckout": "Continue to checkout",
|
|
1392
|
+
"picker.couldNotAddMoreTime": "Couldn\u2019t add more time \u2014 please head to checkout now.",
|
|
1393
|
+
"picker.couldNotFindSeatsTogether": "We couldn\u2019t find {count} seats together. Try fewer seats or another ticket type.",
|
|
1394
|
+
"picker.couldNotReleaseTickets": "Couldn\u2019t release your tickets. Your hold is unchanged.",
|
|
1395
|
+
"picker.couldNotRemoveLabel": "Couldn\u2019t remove {label}. Your hold is unchanged.",
|
|
1396
|
+
"picker.currentOffer": "Current offer",
|
|
1397
|
+
"picker.currentTicketOffer": "Current ticket offer",
|
|
1398
|
+
"picker.dragToLookAround": "Drag to look around \xB7 scroll to zoom",
|
|
1399
|
+
"picker.emptyWheelchairSpace": "Empty wheelchair space",
|
|
1400
|
+
"picker.exitFullScreen": "Exit full screen",
|
|
1401
|
+
"picker.fewer": "Fewer",
|
|
1402
|
+
"picker.fewerGuests": "Fewer guests",
|
|
1403
|
+
"picker.fewerSeats": "Fewer seats",
|
|
1404
|
+
"picker.filterAndFocusByPrice": "Filter and focus seats by price",
|
|
1405
|
+
"picker.filters": "Filters",
|
|
1406
|
+
"picker.findBestSeatsCount.one": "Find {count} best seat",
|
|
1407
|
+
"picker.findBestSeatsCount.other": "Find {count} best seats",
|
|
1408
|
+
"picker.findBestSeatsTogether": "Find the best seats together",
|
|
1409
|
+
"picker.findNewSeats": "Find new seats",
|
|
1410
|
+
"picker.findingBestSeats": "Finding the best seats\u2026",
|
|
1411
|
+
"picker.findingEllipsis": "Finding\u2026",
|
|
1412
|
+
"picker.fitToScreen": "Fit to screen",
|
|
1413
|
+
"picker.flat2dMap": "Flat 2D map",
|
|
1414
|
+
"picker.flexiblePartyTypeWord": "Flexible party \xB7 {typeWord}",
|
|
1415
|
+
"picker.fullScreen": "Full screen",
|
|
1416
|
+
"picker.generalAdmission": "General admission",
|
|
1417
|
+
"picker.guestCountCouldNotBeSecured": "That guest count could not be secured. Your current table hold is unchanged.",
|
|
1418
|
+
"picker.guestCountNoLongerAvailable": "That guest count is no longer available. Your current hold is unchanged.",
|
|
1419
|
+
"picker.guests": "Guests",
|
|
1420
|
+
"picker.guestsCount": "{count} guests",
|
|
1421
|
+
"picker.guestsCountEdit": "{count} guests \xB7 Edit",
|
|
1422
|
+
"picker.held": "Held",
|
|
1423
|
+
"picker.heldForYou": "Held for you",
|
|
1424
|
+
"picker.heldSeatExplanation": "Another buyer is holding this seat. It may become available again.",
|
|
1425
|
+
"picker.heldTicketsReleased": "Held tickets released. Choose your new seats.",
|
|
1426
|
+
"picker.heldTicketsRestored": "Your held tickets have been restored.",
|
|
1427
|
+
"picker.hidePanel": "Hide panel",
|
|
1428
|
+
"picker.hideTicketPanel": "Hide the ticket panel",
|
|
1429
|
+
"picker.holdSeatsAndCheckout": "Hold seats & checkout",
|
|
1430
|
+
"picker.howOfferWorks": "How the {name} offer works",
|
|
1431
|
+
"picker.interactive3dVenueView": "Interactive 3D venue view",
|
|
1432
|
+
"picker.keepMine": "Keep mine",
|
|
1433
|
+
"picker.labelNoLongerAvailable": "{label} is no longer available.",
|
|
1434
|
+
"picker.labelRemoved": "{label} removed.",
|
|
1435
|
+
"picker.labelRemovedFromHold": "{label} removed from your hold.",
|
|
1436
|
+
"picker.labelRestored": "{label} restored.",
|
|
1437
|
+
"picker.labelsNoLongerAvailable.one": "{labels} is no longer available. Choose another seat.",
|
|
1438
|
+
"picker.labelsNoLongerAvailable.other": "{labels} are no longer available. Choose another group.",
|
|
1439
|
+
"picker.leftCount.one": "{count} left",
|
|
1440
|
+
"picker.leftCount.other": "{count} left",
|
|
1441
|
+
"picker.levelNumber": "Level {number}",
|
|
1442
|
+
"picker.levels": "Levels",
|
|
1443
|
+
"picker.liveAvailability": "Live availability \u2014 seats update in real time",
|
|
1444
|
+
"picker.loadingSeatMap": "Loading seat map\u2026",
|
|
1445
|
+
"picker.lookAroundLive3d": "Look around in live 3D",
|
|
1446
|
+
"picker.manualTicketsRemovedNote": "Your manually selected tickets will be removed only after a new group is secured.",
|
|
1447
|
+
"picker.map": "Map",
|
|
1448
|
+
"picker.mapDidNotLoad": "The seat map didn\u2019t load",
|
|
1449
|
+
"picker.maxTicketsForOrder": "You can select up to {count} tickets for this order.",
|
|
1450
|
+
"picker.minToMaxGuests": "{min} to {max} guests",
|
|
1451
|
+
"picker.more": "More",
|
|
1452
|
+
"picker.moreGuests": "More guests",
|
|
1453
|
+
"picker.moreSeats": "More seats",
|
|
1454
|
+
"picker.moreSelectedCount": "{count} more selected",
|
|
1455
|
+
"picker.moreTimeAdded": "More time added \u2014 your seats are still held.",
|
|
1456
|
+
"picker.noAccessibilityMetadata": "No accessibility metadata supplied",
|
|
1457
|
+
"picker.noAuthoredRestriction": "No organizer-authored restriction",
|
|
1458
|
+
"picker.noSeatsSelected": "No seats selected",
|
|
1459
|
+
"picker.notChargedYet": "You won\u2019t be charged yet.",
|
|
1460
|
+
"picker.notForSale": "Not for sale",
|
|
1461
|
+
"picker.notForSaleExplanation": "This seat is not included in the current sale.",
|
|
1462
|
+
"picker.numberOfGuests": "Number of guests",
|
|
1463
|
+
"picker.offerDetailActive": "This price applies automatically to eligible seats. Tickets in active carts temporarily reduce the available quantity; released or expired holds return it. When the offer ends, the next matching offer or normal ticket price takes over.",
|
|
1464
|
+
"picker.offerDetailUpcoming": "Tickets are available at their normal price now. This scheduled offer will apply automatically to eligible seats when it starts.",
|
|
1465
|
+
"picker.offerRemainingAvailable": "{count} available",
|
|
1466
|
+
"picker.offerStarts": "starts {time}",
|
|
1467
|
+
"picker.offerUntil": "until {time}",
|
|
1468
|
+
"picker.oneSeatSaved": "1 seat saved",
|
|
1469
|
+
"picker.oneSeatSavedChooseAnother": "One seat saved; choose another to compare",
|
|
1470
|
+
"picker.openAuthored360": "Open venue 360\xB0",
|
|
1471
|
+
"picker.openComparison": "Open comparison",
|
|
1472
|
+
"picker.openComparisonOfSeats": "Open comparison of {count} seats",
|
|
1473
|
+
"picker.openTicketPanel": "Open ticket panel",
|
|
1474
|
+
"picker.peekFromPrice": "From {price}",
|
|
1475
|
+
"picker.peekTicketsTotal.one": "{count} ticket \xB7 {total}",
|
|
1476
|
+
"picker.peekTicketsTotal.other": "{count} tickets \xB7 {total}",
|
|
1477
|
+
"picker.perGuestPrice": "{price} per guest",
|
|
1478
|
+
"picker.pickYourSeats": "Pick your seats",
|
|
1479
|
+
"picker.preferredTicketType": "Preferred ticket type",
|
|
1480
|
+
"picker.preferredVenueZone": "Preferred venue zone",
|
|
1481
|
+
"picker.priceNotSupplied": "Price not supplied",
|
|
1482
|
+
"picker.releaseHeldTickets": "Release held tickets and choose different seats",
|
|
1483
|
+
"picker.releasingEllipsis": "Releasing\u2026",
|
|
1484
|
+
"picker.removeHeldTicketLabel": "Remove held ticket {label}",
|
|
1485
|
+
"picker.removeSeatLabel": "Remove {label}",
|
|
1486
|
+
"picker.removeTicketsUntilOrFewer": "Remove tickets until your order has {count} or fewer.",
|
|
1487
|
+
"picker.replaceCurrentChoices": "Replace your current choices?",
|
|
1488
|
+
"picker.review": "Review",
|
|
1489
|
+
"picker.row": "Row",
|
|
1490
|
+
"picker.rowLabel": "Row {label}",
|
|
1491
|
+
"picker.saveToCompare": "Save to compare",
|
|
1492
|
+
"picker.savedForComparison": "Saved for comparison",
|
|
1493
|
+
"picker.savedSeatComparison": "Saved seat comparison",
|
|
1494
|
+
"picker.scheduledOffer": "Scheduled offer",
|
|
1495
|
+
"picker.seat": "Seat",
|
|
1496
|
+
"picker.seatCount.one": "{count} seat",
|
|
1497
|
+
"picker.seatCount.other": "{count} seats",
|
|
1498
|
+
"picker.seatJustTakenByAnother": "Seat {label} was just taken by another buyer.",
|
|
1499
|
+
"picker.seatLabel": "Seat {label}",
|
|
1500
|
+
"picker.seatNoLongerAvailable": "That seat is no longer available.",
|
|
1501
|
+
"picker.seatNoLongerYours": "Some seats are no longer available to you. They have been removed from your order.",
|
|
1502
|
+
"picker.seatNumberLower": "seat {label}",
|
|
1503
|
+
"picker.seatSalesClosedForEvent": "Seat sales have closed for this event.",
|
|
1504
|
+
"picker.seatSelection": "Seat selection",
|
|
1505
|
+
"picker.seatStatusLegend": "Seat status legend",
|
|
1506
|
+
"picker.seatTaken": "Someone else took a seat you had picked. It has been removed from your order.",
|
|
1507
|
+
"picker.seatsHeldForNeedMoreTime": "Your seats are held for <b>{time}</b>. Need more time?",
|
|
1508
|
+
"picker.seatsJustTaken": "One or more seats were just taken. Please pick again.",
|
|
1509
|
+
"picker.seatsJustTakenInCategory.one": "{count} seat just taken in {label} \xB7 {left} left",
|
|
1510
|
+
"picker.seatsJustTakenInCategory.other": "{count} seats just taken in {label} \xB7 {left} left",
|
|
1511
|
+
"picker.seatsNoLongerAvailableTryAnother": "Those seats are no longer available. Try another quantity or ticket type.",
|
|
1512
|
+
"picker.seatsSecured": "Seats secured",
|
|
1513
|
+
"picker.seatsSelectedCount.one": "{count} selected",
|
|
1514
|
+
"picker.seatsSelectedCount.other": "{count} selected",
|
|
1515
|
+
"picker.section": "Section",
|
|
1516
|
+
"picker.secureMore": "Secure more",
|
|
1517
|
+
"picker.secureMoreAndCheckout": "Secure {count} more & checkout",
|
|
1518
|
+
"picker.securedCount": "{count} secured",
|
|
1519
|
+
"picker.seeItIn3d": "See it in 3D",
|
|
1520
|
+
"picker.select": "Select",
|
|
1521
|
+
"picker.selectSeats": "Select seats",
|
|
1522
|
+
"picker.selectTable": "Select table",
|
|
1523
|
+
"picker.selectWholeTable": "Select whole table",
|
|
1524
|
+
"picker.selected": "Selected",
|
|
1525
|
+
"picker.selectingEllipsis": "Selecting\u2026",
|
|
1526
|
+
"picker.showAllSeats": "Show all seats",
|
|
1527
|
+
"picker.showAllTicketTypes": "Show all {count} ticket types",
|
|
1528
|
+
"picker.showFewer": "Show fewer",
|
|
1529
|
+
"picker.showLabelSeatsOnMap": "Show {label} seats on the map",
|
|
1530
|
+
"picker.showTicketPanel": "Show the ticket panel",
|
|
1531
|
+
"picker.sold": "Sold",
|
|
1532
|
+
"picker.soldOutCopy": "No reserved seats are currently available for this event.",
|
|
1533
|
+
"picker.soldOutEyebrow": "This event",
|
|
1534
|
+
"picker.soldOutTitle": "Sold out",
|
|
1535
|
+
"picker.soldSeatExplanation": "This seat has already been booked.",
|
|
1536
|
+
"picker.statusAvailable": "available",
|
|
1537
|
+
"picker.statusOnHold": "on hold",
|
|
1538
|
+
"picker.statusTaken2": "taken",
|
|
1539
|
+
"picker.table": "Table",
|
|
1540
|
+
"picker.tableCapacity": "Table capacity",
|
|
1541
|
+
"picker.tableUpdatedForGuests": "{label} updated for {count} guests.",
|
|
1542
|
+
"picker.temporarilyHeld": "Temporarily held",
|
|
1543
|
+
"picker.ticketPrices": "Ticket prices",
|
|
1544
|
+
"picker.ticketTypeSoldOut": "That ticket type is sold out. Try another ticket type.",
|
|
1545
|
+
"picker.tickets": "Tickets",
|
|
1546
|
+
"picker.ticketsCount.one": "{count} ticket",
|
|
1547
|
+
"picker.ticketsCount.other": "{count} tickets",
|
|
1548
|
+
"picker.toggleColorblindColors": "Toggle colorblind-friendly colors",
|
|
1549
|
+
"picker.total": "Total",
|
|
1550
|
+
"picker.trayHintTapOrBest": "Tap a seat on the map, or let us pick the best available for you.",
|
|
1551
|
+
"picker.trayHintTapOrStanding": "Tap a seat on the map \u2014 or grab standing tickets below.",
|
|
1552
|
+
"picker.undo": "Undo",
|
|
1553
|
+
"picker.upcomingTicketOffer": "Upcoming ticket offer",
|
|
1554
|
+
"picker.updateTable": "Update table",
|
|
1555
|
+
"picker.updatingEllipsis": "Updating\u2026",
|
|
1556
|
+
"picker.venueView": "Venue view",
|
|
1557
|
+
"picker.viewFromHere": "View from here",
|
|
1558
|
+
"picker.viewFromThisSeat": "View from this seat",
|
|
1559
|
+
"picker.wholeTypeWord": "Whole {typeWord}",
|
|
1560
|
+
"picker.willChooseClosestGroup": "We\u2019ll choose the closest available group for you.",
|
|
1561
|
+
"picker.willFindSeatsTogether": "We\u2019ll find {count} seats together.",
|
|
1562
|
+
"picker.yourSeats": "Your seats",
|
|
1563
|
+
"picker.zoomIn": "Zoom in",
|
|
1564
|
+
"picker.zoomOut": "Zoom out",
|
|
1565
|
+
"picker.closeVenueNav": "Close",
|
|
1566
|
+
"picker.levelsAndAreas": "Levels & areas"
|
|
1328
1567
|
};
|
|
1329
1568
|
|
|
1330
1569
|
// src/i18n/index.ts
|
|
@@ -1398,10 +1637,11 @@ var WHEELCHAIR_GLYPH_MIN_PX = 10;
|
|
|
1398
1637
|
var FILTERED_WHEELCHAIR_GLYPH_MIN_PX = 14;
|
|
1399
1638
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
1400
1639
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
1401
|
-
var
|
|
1640
|
+
var SEAT_DETAIL_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
1402
1641
|
var PAN_START_SLOP_PX = 8;
|
|
1403
1642
|
var GHOST_CLICK_MS = 700;
|
|
1404
1643
|
var ROW_LABEL_CLEARANCE = 2;
|
|
1644
|
+
var FOCUSED_ROW_LABEL_MIN_PX = MIN_VISIBLE_BOOKABLE_LABEL_PX;
|
|
1405
1645
|
var ROW_LABEL_COLLISION_CELL = 64;
|
|
1406
1646
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
1407
1647
|
var MAX_LABELS = 700;
|
|
@@ -1797,6 +2037,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1797
2037
|
this.activeFloorId = "";
|
|
1798
2038
|
/** When true on a multi-floor chart, render ALL floors stacked (3D overview). */
|
|
1799
2039
|
this.stacked = false;
|
|
2040
|
+
/** Buyer-facing 2D overview: floors are rigidly arranged without altering topology. */
|
|
2041
|
+
this.floorOverview = false;
|
|
1800
2042
|
/** Interactive node per seat/booth — a Circle for seats, a Rect for booths. */
|
|
1801
2043
|
this.circleById = /* @__PURE__ */ new Map();
|
|
1802
2044
|
/** Booth block geometry, keyed by booth id (= the unit's rowId). */
|
|
@@ -2052,6 +2294,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2052
2294
|
return;
|
|
2053
2295
|
}
|
|
2054
2296
|
if (this.pinch && this.pointers.size >= 2) {
|
|
2297
|
+
this.releaseSectionFocusDim();
|
|
2055
2298
|
const [a, b] = [...this.pointers.values()];
|
|
2056
2299
|
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2057
2300
|
if (this.pinch.startDist < 1) return;
|
|
@@ -2070,6 +2313,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2070
2313
|
if (!this.panStarted) {
|
|
2071
2314
|
if (this.moved <= PAN_START_SLOP_PX) return;
|
|
2072
2315
|
this.panStarted = true;
|
|
2316
|
+
this.releaseSectionFocusDim();
|
|
2073
2317
|
}
|
|
2074
2318
|
this.stage.position({
|
|
2075
2319
|
x: this.stage.x() + (p.x - this.panLast.x),
|
|
@@ -2190,13 +2434,23 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2190
2434
|
}
|
|
2191
2435
|
// ---- ISeatmapRenderer -----------------------------------------------------
|
|
2192
2436
|
setChart(doc, opts) {
|
|
2193
|
-
if (doc !== this.chartDoc)
|
|
2437
|
+
if (doc !== this.chartDoc) {
|
|
2438
|
+
this.stacked = false;
|
|
2439
|
+
this.floorOverview = false;
|
|
2440
|
+
}
|
|
2194
2441
|
this.cancelAssetLoads();
|
|
2195
2442
|
this.chartDoc = doc;
|
|
2196
2443
|
this.activeFloorId = opts?.floorId ?? floorsOf(doc)[0].id;
|
|
2197
2444
|
this.objectFloor.clear();
|
|
2198
2445
|
if (doc.floors && doc.floors.length > 1) {
|
|
2199
|
-
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
|
+
}
|
|
2200
2454
|
}
|
|
2201
2455
|
const view = this.floorView(doc);
|
|
2202
2456
|
this.focusedId = null;
|
|
@@ -2294,7 +2548,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2294
2548
|
this.seats = expandChart(view, {
|
|
2295
2549
|
// A stacked overview is presentation-only and never opens a seat POV. The
|
|
2296
2550
|
// per-section renderer below still resolves each object's real floor base.
|
|
2297
|
-
floorBaseHeightM: this.stacked ? 0 : this.floorBaseHeightMFor()
|
|
2551
|
+
floorBaseHeightM: this.stacked || this.floorOverview ? 0 : this.floorBaseHeightMFor()
|
|
2298
2552
|
});
|
|
2299
2553
|
for (const s of this.seats) {
|
|
2300
2554
|
this.seatById.set(s.id, s);
|
|
@@ -2315,14 +2569,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2315
2569
|
this.overlayLayer.add(this.focusRing);
|
|
2316
2570
|
this.zoomToFit();
|
|
2317
2571
|
}
|
|
2318
|
-
/** The chart to render:
|
|
2319
|
-
* 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. */
|
|
2320
2574
|
floorView(doc) {
|
|
2321
2575
|
if (!doc.floors || !doc.floors.length) return {
|
|
2322
2576
|
...doc,
|
|
2323
2577
|
referenceImage: void 0,
|
|
2324
2578
|
backgroundImage: buyerBackgroundImage(doc)
|
|
2325
2579
|
};
|
|
2580
|
+
if (this.floorOverview && doc.floors.length >= 2) {
|
|
2581
|
+
return {
|
|
2582
|
+
...layoutFloorOverview(doc),
|
|
2583
|
+
referenceImage: void 0,
|
|
2584
|
+
backgroundImage: buyerBackgroundImage(doc)
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2326
2587
|
if (this.stacked && doc.floors.length >= 2) {
|
|
2327
2588
|
return {
|
|
2328
2589
|
...stackFloors(doc),
|
|
@@ -2355,14 +2616,30 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2355
2616
|
const multi = !!this.chartDoc.floors && this.chartDoc.floors.length >= 2;
|
|
2356
2617
|
if (!multi) return;
|
|
2357
2618
|
this.stacked = on;
|
|
2619
|
+
if (on) this.floorOverview = false;
|
|
2358
2620
|
this.setChart(this.chartDoc, { floorId: this.activeFloorId });
|
|
2359
2621
|
}
|
|
2360
2622
|
isStacked() {
|
|
2361
2623
|
return this.stacked;
|
|
2362
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
|
+
}
|
|
2363
2638
|
/** Switch which floor is shown (2D). Re-renders + re-fits; no-op if unchanged. */
|
|
2364
2639
|
setActiveFloor(floorId) {
|
|
2365
|
-
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;
|
|
2366
2643
|
this.setChart(this.chartDoc, { floorId });
|
|
2367
2644
|
}
|
|
2368
2645
|
/** Floors for the host's switcher (single-floor charts return one synthetic floor). */
|
|
@@ -2821,6 +3098,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2821
3098
|
const h = this.stage.height();
|
|
2822
3099
|
const b = this.viewMode === "perspective" && this.perspectiveBounds ? this.perspectiveBounds : this.bounds;
|
|
2823
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
|
+
}
|
|
2824
3108
|
const s = this.fitScale;
|
|
2825
3109
|
this.stage.scale({ x: s, y: s });
|
|
2826
3110
|
this.stage.position({
|
|
@@ -2830,6 +3114,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2830
3114
|
this.afterViewChange();
|
|
2831
3115
|
this.stage.batchDraw();
|
|
2832
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
|
+
}
|
|
2833
3129
|
zoomIn() {
|
|
2834
3130
|
const center = { x: this.stage.width() / 2, y: this.stage.height() / 2 };
|
|
2835
3131
|
this.zoomAbout(this.stage.scaleX() * _SeatmapRenderer.ZOOM_STEP, center);
|
|
@@ -3920,8 +4216,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3920
4216
|
}
|
|
3921
4217
|
/**
|
|
3922
4218
|
* AXS section-focus: dim + desaturate every other section, draw a calm backdrop
|
|
3923
|
-
* panel behind this section's seats, and glide the camera
|
|
3924
|
-
*
|
|
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.
|
|
3925
4222
|
*/
|
|
3926
4223
|
focusSection(id) {
|
|
3927
4224
|
if (!this.sections.some((section) => section.id === id || section.logicalId === id)) return;
|
|
@@ -3930,7 +4227,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3930
4227
|
this.bgLayer.batchDraw();
|
|
3931
4228
|
this.overlayLayer.batchDraw();
|
|
3932
4229
|
this.updateLOD();
|
|
3933
|
-
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();
|
|
3934
4248
|
}
|
|
3935
4249
|
/** Clear an AXS section focus — restore full-bowl brightness + drop the backdrop. */
|
|
3936
4250
|
clearSectionFocus() {
|
|
@@ -4068,6 +4382,44 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4068
4382
|
if (!Number.isFinite(minX)) return { x: 0, y: 0, width: 1, height: 1 };
|
|
4069
4383
|
return { x: minX, y: minY, width: Math.max(1, maxX - minX), height: Math.max(1, maxY - minY) };
|
|
4070
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
|
+
}
|
|
4071
4423
|
/** Legend hover: highlight one category (dim the rest), or null to clear. */
|
|
4072
4424
|
setCategoryHighlight(key) {
|
|
4073
4425
|
if (this.categoryHighlight === key) return;
|
|
@@ -4836,7 +5188,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4836
5188
|
blockT = clamp((BLOCK_MELT_TOP - scale) / (BLOCK_MELT_TOP - SECTION_PROMINENT_SCALE), 0, 1);
|
|
4837
5189
|
zoneT = clamp((SECTION_PROMINENT_SCALE - scale) / (SECTION_PROMINENT_SCALE - ZONE_PROMINENT_SCALE), 0, 1);
|
|
4838
5190
|
}
|
|
4839
|
-
|
|
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;
|
|
4840
5200
|
if (sectionOverview) blockT = 1;
|
|
4841
5201
|
if (!this.zones.length) zoneT = 0;
|
|
4842
5202
|
const deferPerspectiveSeatReveal = this.viewMode === "perspective" && this.glideInProgress && this.seats.length > 2500;
|
|
@@ -4981,8 +5341,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4981
5341
|
fitSectionRungLabels(sec, sx) {
|
|
4982
5342
|
const paddingPx = 8;
|
|
4983
5343
|
sec.subLabelFits = false;
|
|
4984
|
-
const maxPx = SECTION_LABEL_PX * sec.labelScale;
|
|
4985
|
-
const minPx = MIN_SECTION_LABEL_PX * sec.labelScale;
|
|
5344
|
+
const maxPx = SECTION_LABEL_PX * sec.labelScale * this.uiScale();
|
|
5345
|
+
const minPx = Math.min(MIN_SECTION_LABEL_PX * sec.labelScale, maxPx);
|
|
4986
5346
|
for (let fontPx = maxPx; fontPx >= minPx; fontPx -= 1) {
|
|
4987
5347
|
for (const rotation of [0, -90]) {
|
|
4988
5348
|
sec.nameLabel.rotation(rotation);
|
|
@@ -5008,13 +5368,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5008
5368
|
sec.nameLabel.rotation(0);
|
|
5009
5369
|
sec.nameLabelFits = false;
|
|
5010
5370
|
}
|
|
5371
|
+
/**
|
|
5372
|
+
* Screen-constant chrome must still respect the screen it is constant on:
|
|
5373
|
+
* an 18px zone cap sized for a 900px designer canvas eats a 360px phone map
|
|
5374
|
+
* (owner report, 2026-08-14). Scales the fixed-px chrome down to 66% on the
|
|
5375
|
+
* smallest containers; never up.
|
|
5376
|
+
*/
|
|
5377
|
+
uiScale() {
|
|
5378
|
+
const w = this.stage?.width() ?? 900;
|
|
5379
|
+
return Math.min(1, Math.max(0.66, w / 900));
|
|
5380
|
+
}
|
|
5011
5381
|
/** Size one screen-constant zone name/price pill around its shared anchor. */
|
|
5012
5382
|
sizeZonePill(zone, sx) {
|
|
5013
|
-
const
|
|
5014
|
-
const
|
|
5015
|
-
const
|
|
5016
|
-
|
|
5017
|
-
|
|
5383
|
+
const k = this.uiScale();
|
|
5384
|
+
const padX = 10 * k / sx;
|
|
5385
|
+
const padY = 6 * k / sx;
|
|
5386
|
+
const gap = zone.sub ? 3 * k / sx : 0;
|
|
5387
|
+
this.sizeLabel(zone.label, ZONE_LABEL_PX * k / sx, zone.anchor.y);
|
|
5388
|
+
if (zone.sub) this.sizeLabel(zone.sub, ZONE_SUB_PX * k / sx, zone.anchor.y);
|
|
5018
5389
|
const width = Math.max(zone.label.width(), zone.sub?.width() ?? 0) + padX * 2;
|
|
5019
5390
|
const height = zone.label.height() + (zone.sub ? gap + zone.sub.height() : 0) + padY * 2;
|
|
5020
5391
|
zone.label.y(zone.anchor.y - (zone.sub ? (gap + zone.sub.height()) / 2 : 0));
|
|
@@ -5022,8 +5393,16 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5022
5393
|
zone.back.position(zone.anchor);
|
|
5023
5394
|
zone.back.size({ width, height });
|
|
5024
5395
|
zone.back.offset({ x: width / 2, y: height / 2 });
|
|
5025
|
-
zone.back.cornerRadius(7 / sx);
|
|
5396
|
+
zone.back.cornerRadius(7 * this.uiScale() / sx);
|
|
5026
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
|
+
}
|
|
5027
5406
|
}
|
|
5028
5407
|
/**
|
|
5029
5408
|
* Map a container-relative screen point back to world coords. Inverts the
|
|
@@ -5248,7 +5627,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5248
5627
|
* must fall through and pick.
|
|
5249
5628
|
*/
|
|
5250
5629
|
sectionBounds(id) {
|
|
5251
|
-
const
|
|
5630
|
+
const matching = this.sections.filter((section) => section.id === id || section.logicalId === id);
|
|
5631
|
+
const bounds2 = matching.map((section) => {
|
|
5252
5632
|
if (this.viewMode === "perspective" && section.perspectiveAffine) {
|
|
5253
5633
|
return polyBounds(section.outline.map((point) => this.projectedSectionPoint(section, point)));
|
|
5254
5634
|
}
|
|
@@ -5260,11 +5640,77 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5260
5640
|
return polyBounds(points);
|
|
5261
5641
|
});
|
|
5262
5642
|
if (!bounds2.length) return null;
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
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
|
+
};
|
|
5268
5714
|
}
|
|
5269
5715
|
sectionFrameScale(id) {
|
|
5270
5716
|
const b = this.sectionBounds(id);
|
|
@@ -5275,7 +5721,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5275
5721
|
const margin = 1.12;
|
|
5276
5722
|
if (b.width <= 0 || b.height <= 0) return this.stage.scaleX();
|
|
5277
5723
|
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
5278
|
-
return clamp(
|
|
5724
|
+
return clamp(frameScale, min, max);
|
|
5279
5725
|
}
|
|
5280
5726
|
/**
|
|
5281
5727
|
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
@@ -5288,7 +5734,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5288
5734
|
* fall through and PICK. This is the invariant: seats are never unpickable.
|
|
5289
5735
|
*/
|
|
5290
5736
|
handleSeatTap(id) {
|
|
5291
|
-
if (this.stacked && this.opts.onDeckTap) {
|
|
5737
|
+
if ((this.stacked || this.floorOverview) && this.opts.onDeckTap) {
|
|
5292
5738
|
const floorId = this.objectFloor.get(this.seatById.get(id)?.rowId ?? "");
|
|
5293
5739
|
if (floorId) {
|
|
5294
5740
|
this.opts.onDeckTap(floorId);
|
|
@@ -5363,6 +5809,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5363
5809
|
});
|
|
5364
5810
|
this.stage.on("wheel", (e) => {
|
|
5365
5811
|
e.evt.preventDefault();
|
|
5812
|
+
this.releaseSectionFocusDim();
|
|
5366
5813
|
const r = this.container.getBoundingClientRect();
|
|
5367
5814
|
const pointer = { x: e.evt.clientX - r.left, y: e.evt.clientY - r.top };
|
|
5368
5815
|
const factor = Math.exp(-e.evt.deltaY * 2e-3);
|
|
@@ -5379,8 +5826,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5379
5826
|
if (near) this.handleSeatTap(near);
|
|
5380
5827
|
return;
|
|
5381
5828
|
}
|
|
5382
|
-
if (this.stacked && this.opts.onDeckTap) {
|
|
5383
|
-
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();
|
|
5384
5832
|
if (floorId) {
|
|
5385
5833
|
this.opts.onDeckTap(floorId);
|
|
5386
5834
|
return;
|
|
@@ -5778,6 +6226,28 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5778
6226
|
...hiddenReason ? { hiddenReason } : {}
|
|
5779
6227
|
};
|
|
5780
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
|
+
});
|
|
5781
6251
|
const palette = overviewPalette(this.canvasBackground);
|
|
5782
6252
|
const neutralSectionFills = new Set(
|
|
5783
6253
|
["normal", "nearly-gone", "sold-out", "closed"].map((state) => overviewStateFill(palette, state).toLowerCase())
|
|
@@ -5822,7 +6292,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5822
6292
|
labels,
|
|
5823
6293
|
gaAreas,
|
|
5824
6294
|
hierarchyLabels,
|
|
5825
|
-
freeTextLabels
|
|
6295
|
+
freeTextLabels,
|
|
6296
|
+
rowLabels
|
|
5826
6297
|
};
|
|
5827
6298
|
}
|
|
5828
6299
|
/** Jump the camera to a rung's zoom band (glided). */
|
|
@@ -5834,7 +6305,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5834
6305
|
}
|
|
5835
6306
|
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(
|
|
5836
6307
|
this.seatLabelTargetScale() * 1.05,
|
|
5837
|
-
|
|
6308
|
+
SEAT_DETAIL_SCALE,
|
|
5838
6309
|
CACHE_THRESHOLD * 1.3
|
|
5839
6310
|
);
|
|
5840
6311
|
const w = this.stage.width();
|
|
@@ -6121,14 +6592,25 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6121
6592
|
for (const { objectId, node, backdrop, categoryKey, kind } of this.freeTextById.values()) {
|
|
6122
6593
|
const gaDimmed = categoryKey != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaCategoryDimmed(categoryKey);
|
|
6123
6594
|
const gaOverviewHidden = objectId != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaById.get(objectId)?.sectionId != null && effectiveScale < CACHE_THRESHOLD;
|
|
6124
|
-
const
|
|
6595
|
+
const focusedAnnotation = kind === "free-text" && this.nodeBelongsToFocusedSection(node);
|
|
6596
|
+
const visible = !gaDimmed && !gaOverviewHidden && (focusedAnnotation || isBookableLabelLegibleAtScale(node.fontSize(), effectiveScale));
|
|
6125
6597
|
node.visible(visible);
|
|
6126
6598
|
backdrop?.visible(visible);
|
|
6127
6599
|
}
|
|
6128
6600
|
for (const { node, fontSize } of this.iconNodeById.values()) {
|
|
6129
|
-
node.visible(isBookableLabelLegibleAtScale(fontSize, effectiveScale));
|
|
6601
|
+
node.visible(this.nodeBelongsToFocusedSection(node) || isBookableLabelLegibleAtScale(fontSize, effectiveScale));
|
|
6130
6602
|
}
|
|
6131
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
|
+
}
|
|
6132
6614
|
/** OV-45(a): resolve authored row-letter labels for the buyer/preview map, in
|
|
6133
6615
|
* world space, matching the Designer's placement contract (start/end/both/none
|
|
6134
6616
|
* preset, a free-drag `position` that wins over the preset, rotation, and
|
|
@@ -6138,18 +6620,25 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6138
6620
|
this.rowLabelPlan = [];
|
|
6139
6621
|
const background = this.theme.background ?? "#0e1117";
|
|
6140
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
|
+
};
|
|
6141
6628
|
for (const seat of this.seats) {
|
|
6142
6629
|
if (seat.kind === "booth") continue;
|
|
6143
6630
|
const key = seat.logicalRowId ?? seat.rowId;
|
|
6144
|
-
(
|
|
6631
|
+
addSeat(key, seat);
|
|
6632
|
+
if (key !== seat.rowId) addSeat(seat.rowId, seat);
|
|
6145
6633
|
}
|
|
6146
6634
|
const seatOrder = (seat) => seat.logicalSeatIndex ?? Number(seat.id.split(":").pop() ?? 0);
|
|
6147
6635
|
for (const obj of view.objects) {
|
|
6148
6636
|
if (obj.type !== "row") continue;
|
|
6149
|
-
|
|
6150
|
-
|
|
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;
|
|
6151
6640
|
if (presentation?.visible === false || presentation?.positionPreset === "none") continue;
|
|
6152
|
-
const key = obj.segmentedRow ? obj.segmentedRow.groupId : obj.id;
|
|
6641
|
+
const key = obj.segmentedRow && !repeatOnComponents ? obj.segmentedRow.groupId : obj.id;
|
|
6153
6642
|
const seats = (seatsByRow.get(key) ?? []).slice().sort((a, b) => seatOrder(a) - seatOrder(b));
|
|
6154
6643
|
if (!seats.length) continue;
|
|
6155
6644
|
const first = seats[0];
|
|
@@ -6164,6 +6653,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6164
6653
|
const rotation = presentation?.rotation ?? 0;
|
|
6165
6654
|
const text = obj.segmentedRow?.displayLabel ?? obj.displayLabel ?? obj.label;
|
|
6166
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;
|
|
6167
6660
|
const push = (x, y, automaticAway) => this.rowLabelPlan.push({
|
|
6168
6661
|
text,
|
|
6169
6662
|
x,
|
|
@@ -6172,6 +6665,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6172
6665
|
fontSize,
|
|
6173
6666
|
ink,
|
|
6174
6667
|
opacity,
|
|
6668
|
+
...focusKeys?.length ? { focusKeys } : {},
|
|
6175
6669
|
...automaticAway ? { automaticAway } : {}
|
|
6176
6670
|
});
|
|
6177
6671
|
if (presentation?.position) {
|
|
@@ -6191,6 +6685,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6191
6685
|
push(last.x + away.x * gap, last.y + away.y * gap, away);
|
|
6192
6686
|
}
|
|
6193
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;
|
|
6194
6700
|
}
|
|
6195
6701
|
/** Axis-aligned world bounds of a centred, potentially rotated row label. */
|
|
6196
6702
|
rowLabelBox(label, position, padding = ROW_LABEL_CLEARANCE) {
|
|
@@ -6267,12 +6773,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6267
6773
|
this.labelGroup.destroyChildren();
|
|
6268
6774
|
this.labelLiftGroups.clear();
|
|
6269
6775
|
this.seatLabelById.clear();
|
|
6270
|
-
if (!show) {
|
|
6271
|
-
this.overlayLayer.batchDraw();
|
|
6272
|
-
return;
|
|
6273
|
-
}
|
|
6274
6776
|
let count = 0;
|
|
6275
|
-
for (const seat of this.visibleSeatCandidates()) {
|
|
6777
|
+
if (show) for (const seat of this.visibleSeatCandidates()) {
|
|
6276
6778
|
if (seat.kind === "booth") continue;
|
|
6277
6779
|
const screen = this.worldToScreen(seat);
|
|
6278
6780
|
if (screen.x < -20 || screen.x > this.stage.width() + 20 || screen.y < -20 || screen.y > this.stage.height() + 20) continue;
|
|
@@ -6363,13 +6865,16 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6363
6865
|
});
|
|
6364
6866
|
}
|
|
6365
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;
|
|
6366
6871
|
const screen = this.worldToScreen({ x: rl.x, y: rl.y });
|
|
6367
6872
|
if (screen.x < -40 || screen.x > this.stage.width() + 40 || screen.y < -40 || screen.y > this.stage.height() + 40) continue;
|
|
6368
6873
|
const t2 = new Text({
|
|
6369
6874
|
x: rl.x,
|
|
6370
6875
|
y: rl.y,
|
|
6371
6876
|
text: rl.text,
|
|
6372
|
-
fontSize:
|
|
6877
|
+
fontSize: renderedFontSize,
|
|
6373
6878
|
fontStyle: "700",
|
|
6374
6879
|
fontFamily: this.labelFont(),
|
|
6375
6880
|
fill: rl.ink,
|
|
@@ -6398,7 +6903,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6398
6903
|
const h = this.container.clientHeight || 1;
|
|
6399
6904
|
if (w === this.stage.width() && h === this.stage.height()) return;
|
|
6400
6905
|
this.stage.size({ width: w, height: h });
|
|
6401
|
-
this.
|
|
6906
|
+
this.refitCurrentView();
|
|
6402
6907
|
}
|
|
6403
6908
|
startFpsLoop() {
|
|
6404
6909
|
const tick = (now) => {
|
|
@@ -6556,6 +7061,99 @@ function pickBestAvailable(seats, available, opts) {
|
|
|
6556
7061
|
return { labels: ranked.slice(0, qty).map((r) => r.seat.label) };
|
|
6557
7062
|
}
|
|
6558
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
|
+
|
|
6559
7157
|
// src/core/tableInventory.ts
|
|
6560
7158
|
var GroupedTableProjectionError = class extends Error {
|
|
6561
7159
|
constructor(objectId, message) {
|
|
@@ -6610,6 +7208,15 @@ function groupedTableInventory(doc, modelVersion) {
|
|
|
6610
7208
|
// src/picker/PickerController.ts
|
|
6611
7209
|
var DEFAULT_MAX_SELECTION = 10;
|
|
6612
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
|
+
};
|
|
6613
7220
|
var MAP_THEME_KEYS = ["background", "rowLabelColor", "textColor", "selectionColor"];
|
|
6614
7221
|
function sameMapTheme(a, b) {
|
|
6615
7222
|
if (!a || !b) return !a && !b;
|
|
@@ -6739,7 +7346,9 @@ var PickerController = class {
|
|
|
6739
7346
|
if (!r) return true;
|
|
6740
7347
|
const selected = r.getSelection().map((seat) => seat.id);
|
|
6741
7348
|
const floorId = this.getActiveFloorId();
|
|
7349
|
+
const floorOverview = r.isFloorOverview?.() ?? false;
|
|
6742
7350
|
r.setChart(this.visibleDoc(), floorId ? { floorId } : void 0);
|
|
7351
|
+
if (floorOverview) r.setFloorOverview?.(true);
|
|
6743
7352
|
if (this.opts.colorblindSafe) r.setColorblindSafe?.(true);
|
|
6744
7353
|
if (this.closedSections.size) r.setClosedSections?.([...this.closedSections]);
|
|
6745
7354
|
this.applySeatsMap(Object.fromEntries(this.liveStatuses), this.liveDefault);
|
|
@@ -6751,7 +7360,9 @@ var PickerController = class {
|
|
|
6751
7360
|
const next = ids.filter((x) => typeof x === "string");
|
|
6752
7361
|
if (next.length === this.hidden.size && next.every((id) => this.hidden.has(id))) return false;
|
|
6753
7362
|
this.hidden = new Set(next);
|
|
7363
|
+
const floorOverview = this.renderer?.isFloorOverview?.() ?? false;
|
|
6754
7364
|
this.renderer?.setChart(this.visibleDoc());
|
|
7365
|
+
if (floorOverview) this.renderer?.setFloorOverview?.(true);
|
|
6755
7366
|
return true;
|
|
6756
7367
|
}
|
|
6757
7368
|
/** Adopt a new closed-section set; restyle (grey + non-pickable) if it differs.
|
|
@@ -6826,16 +7437,22 @@ var PickerController = class {
|
|
|
6826
7437
|
/** Fetch chart, build label maps, mount the renderer, seed statuses, go live. */
|
|
6827
7438
|
async render(host) {
|
|
6828
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;
|
|
6829
7443
|
this.closed = false;
|
|
6830
7444
|
let res;
|
|
7445
|
+
const chartStartedAt = performance.now();
|
|
6831
7446
|
try {
|
|
6832
7447
|
res = await this.api.chart(this.key);
|
|
6833
7448
|
} catch (err) {
|
|
6834
7449
|
this.emitError(err);
|
|
6835
7450
|
return null;
|
|
6836
7451
|
}
|
|
7452
|
+
profile.loadChart = duration(chartStartedAt);
|
|
6837
7453
|
if (this.closed) return null;
|
|
6838
7454
|
this._doc = res.doc;
|
|
7455
|
+
const normalizeStartedAt = performance.now();
|
|
6839
7456
|
try {
|
|
6840
7457
|
const groups = groupedTableInventory(
|
|
6841
7458
|
res.doc,
|
|
@@ -6900,13 +7517,16 @@ var PickerController = class {
|
|
|
6900
7517
|
this.labelToId.set(group.label, representative.id);
|
|
6901
7518
|
this.labelToSeat.set(group.label, representative);
|
|
6902
7519
|
}
|
|
7520
|
+
profile.normalizeBuyerModel = duration(normalizeStartedAt);
|
|
6903
7521
|
const currency = res.event.currency ?? this.opts.currency;
|
|
6904
7522
|
this.currency = currency ?? "USD";
|
|
7523
|
+
const rendererStartedAt = performance.now();
|
|
6905
7524
|
const renderer = createRenderer(host, {
|
|
6906
7525
|
// Group chairs are selected together for paint, but count as one logical
|
|
6907
7526
|
// inventory line. The controller enforces the guest-weighted cap.
|
|
6908
7527
|
maxSelection: this.rendererSelectionCap(),
|
|
6909
7528
|
confirmSelection: this.opts.confirmSelection,
|
|
7529
|
+
portraitFitCrop: true,
|
|
6910
7530
|
currency,
|
|
6911
7531
|
onSelect: (seat) => {
|
|
6912
7532
|
this.handleRendererSelect(seat);
|
|
@@ -6927,6 +7547,7 @@ var PickerController = class {
|
|
|
6927
7547
|
onDeckTap: (floorId) => this.handleDeckTap(floorId),
|
|
6928
7548
|
onFps: this.opts.onFps
|
|
6929
7549
|
});
|
|
7550
|
+
profile.createRenderer = duration(rendererStartedAt);
|
|
6930
7551
|
if (this.closed) {
|
|
6931
7552
|
renderer.destroy();
|
|
6932
7553
|
return null;
|
|
@@ -6935,6 +7556,7 @@ var PickerController = class {
|
|
|
6935
7556
|
let seats = null;
|
|
6936
7557
|
let seatsDefault = "free";
|
|
6937
7558
|
let closedIds = [];
|
|
7559
|
+
const statusesStartedAt = performance.now();
|
|
6938
7560
|
try {
|
|
6939
7561
|
const objs = await this.api.objects(this.key);
|
|
6940
7562
|
this.hidden = new Set(objs.hidden ?? []);
|
|
@@ -6943,18 +7565,30 @@ var PickerController = class {
|
|
|
6943
7565
|
seatsDefault = objs.default ?? "free";
|
|
6944
7566
|
} catch {
|
|
6945
7567
|
}
|
|
7568
|
+
profile.loadStatuses = duration(statusesStartedAt);
|
|
6946
7569
|
if (this.closed) {
|
|
6947
7570
|
renderer.destroy();
|
|
6948
7571
|
this.renderer = null;
|
|
6949
7572
|
return null;
|
|
6950
7573
|
}
|
|
7574
|
+
const sceneStartedAt = performance.now();
|
|
6951
7575
|
renderer.setChart(this.visibleDoc());
|
|
7576
|
+
if ((res.doc.floors?.length ?? 0) > 1) renderer.setFloorOverview?.(true);
|
|
6952
7577
|
if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
|
|
7578
|
+
profile.buildScene = duration(sceneStartedAt);
|
|
6953
7579
|
this.closedSections = new Set(closedIds);
|
|
7580
|
+
const initialStateStartedAt = performance.now();
|
|
6954
7581
|
if (closedIds.length) renderer.setClosedSections?.(closedIds);
|
|
6955
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();
|
|
6956
7588
|
this.attachVisibilityListener();
|
|
6957
7589
|
this.connect();
|
|
7590
|
+
profile.startRealtime = duration(realtimeStartedAt);
|
|
7591
|
+
profile.controllerTotal = duration(renderStartedAt);
|
|
6958
7592
|
return {
|
|
6959
7593
|
doc: res.doc,
|
|
6960
7594
|
salesClosed: !!res.event.salesClosed,
|
|
@@ -6965,7 +7599,8 @@ var PickerController = class {
|
|
|
6965
7599
|
currency,
|
|
6966
7600
|
// 'test' ⇒ sandbox event: hosts render a TEST MODE ribbon (Phase 11).
|
|
6967
7601
|
mode: res.event.mode ?? "live",
|
|
6968
|
-
posterUrl: res.event.posterUrl ?? null
|
|
7602
|
+
posterUrl: res.event.posterUrl ?? null,
|
|
7603
|
+
performanceProfile: profile
|
|
6969
7604
|
};
|
|
6970
7605
|
}
|
|
6971
7606
|
// ---- selection ------------------------------------------------------------
|
|
@@ -7021,11 +7656,23 @@ var PickerController = class {
|
|
|
7021
7656
|
select(ids) {
|
|
7022
7657
|
const before = new Set(this.getSelection().map((seat) => seat.label));
|
|
7023
7658
|
const expanded = /* @__PURE__ */ new Set();
|
|
7659
|
+
const rejected = [];
|
|
7024
7660
|
for (const id of ids) {
|
|
7025
7661
|
const table = this.groupedTableBySeatId.get(id) ?? this.groupedTablesByLabel.get(id);
|
|
7026
|
-
if (table)
|
|
7027
|
-
|
|
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
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
if (rejected.length) {
|
|
7673
|
+
this.emitError(new PickerSelectionError(ticketOptionNotApplicableIssue(rejected)));
|
|
7028
7674
|
}
|
|
7675
|
+
if (!expanded.size) return [];
|
|
7029
7676
|
this.renderer?.select?.([...expanded]);
|
|
7030
7677
|
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7031
7678
|
this.renderer?.deselect([...expanded]);
|
|
@@ -7093,6 +7740,13 @@ var PickerController = class {
|
|
|
7093
7740
|
}
|
|
7094
7741
|
handleRendererSelect(seat) {
|
|
7095
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
|
+
}
|
|
7096
7750
|
if (table) {
|
|
7097
7751
|
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7098
7752
|
this.renderer?.deselect(table.chairs.map((chair) => chair.id));
|
|
@@ -7174,6 +7828,8 @@ var PickerController = class {
|
|
|
7174
7828
|
const existingGA = (this.hold_?.items ?? []).filter((item) => item.objectType === "ga");
|
|
7175
7829
|
const combinedLabels = [.../* @__PURE__ */ new Set([...existingGA.map((item) => item.label), ...labels])];
|
|
7176
7830
|
if (!combinedLabels.length) return null;
|
|
7831
|
+
const restriction = this.ticketIssue(combinedLabels);
|
|
7832
|
+
if (restriction) throw new PickerSelectionError(restriction);
|
|
7177
7833
|
if (this.hold_ && this.holdCovers(combinedLabels)) return this.hold_;
|
|
7178
7834
|
try {
|
|
7179
7835
|
const selections = combinedLabels.map((label) => {
|
|
@@ -7345,6 +8001,7 @@ var PickerController = class {
|
|
|
7345
8001
|
const available = /* @__PURE__ */ new Set();
|
|
7346
8002
|
for (const seat of seats) {
|
|
7347
8003
|
if (held.has(seat.label)) continue;
|
|
8004
|
+
if (this.seatTierResolution(seat).kind === "invalid") continue;
|
|
7348
8005
|
const id = this.labelToId.get(seat.label);
|
|
7349
8006
|
const status = id ? this.getStatus(id) : void 0;
|
|
7350
8007
|
if ((status ?? "free") === "free") available.add(seat.label);
|
|
@@ -7426,6 +8083,8 @@ var PickerController = class {
|
|
|
7426
8083
|
if (!this.api.book) throw new Error("picker: transport has no book() \u2014 hold-only mode");
|
|
7427
8084
|
const labels = labelsArg ?? this.getSelection().map((seat) => seat.label);
|
|
7428
8085
|
if (!labels.length) return null;
|
|
8086
|
+
const restriction = this.ticketIssue(labels);
|
|
8087
|
+
if (restriction) throw new PickerSelectionError(restriction);
|
|
7429
8088
|
let holdId;
|
|
7430
8089
|
try {
|
|
7431
8090
|
if (this.hold_ && this.holdCovers(labels)) {
|
|
@@ -7552,6 +8211,20 @@ var PickerController = class {
|
|
|
7552
8211
|
getStatus(id) {
|
|
7553
8212
|
return this.renderer?.getStatus(id);
|
|
7554
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
|
+
}
|
|
7555
8228
|
flashSeat(id, color) {
|
|
7556
8229
|
this.renderer?.flashSeat(id, color);
|
|
7557
8230
|
}
|
|
@@ -7564,6 +8237,11 @@ var PickerController = class {
|
|
|
7564
8237
|
zoomToFit() {
|
|
7565
8238
|
this.renderer?.zoomToFit();
|
|
7566
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
|
+
}
|
|
7567
8245
|
worldToScreen(p) {
|
|
7568
8246
|
return this.renderer?.worldToScreen(p) ?? { x: 0, y: 0 };
|
|
7569
8247
|
}
|
|
@@ -7593,7 +8271,8 @@ var PickerController = class {
|
|
|
7593
8271
|
/** Switch the shown floor (2D), then re-apply live seat statuses onto it. */
|
|
7594
8272
|
setFloor(id) {
|
|
7595
8273
|
const r = this.renderer;
|
|
7596
|
-
if (!r?.setActiveFloor || id === r.getActiveFloorId?.()) return;
|
|
8274
|
+
if (!r?.setActiveFloor || id === r.getActiveFloorId?.() && !r.isFloorOverview?.()) return;
|
|
8275
|
+
r.setFloorOverview?.(false);
|
|
7597
8276
|
r.setStacked?.(false);
|
|
7598
8277
|
r.setActiveFloor(id);
|
|
7599
8278
|
void this.resnapshot();
|
|
@@ -7609,12 +8288,24 @@ var PickerController = class {
|
|
|
7609
8288
|
isMultiFloor() {
|
|
7610
8289
|
return (this._doc?.floors?.length ?? 0) > 1;
|
|
7611
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
|
+
}
|
|
7612
8302
|
/** Tap-a-deck-to-enter: leave the 3D stack, drop to flat 2D on `floorId`, and
|
|
7613
8303
|
* tell the host so it can sync its 2D/3D toggle + floor-switcher state. */
|
|
7614
8304
|
handleDeckTap(floorId) {
|
|
7615
8305
|
const r = this.renderer;
|
|
7616
8306
|
if (!r) return;
|
|
7617
8307
|
r.setViewMode?.("flat");
|
|
8308
|
+
r.setFloorOverview?.(false);
|
|
7618
8309
|
r.setStacked?.(false);
|
|
7619
8310
|
r.setActiveFloor?.(floorId);
|
|
7620
8311
|
void this.resnapshot();
|
|
@@ -7683,10 +8374,30 @@ var PickerController = class {
|
|
|
7683
8374
|
if (!r?.getVisibleWorldRect || !r.getWorldBounds) return null;
|
|
7684
8375
|
return { visible: r.getVisibleWorldRect(), bounds: r.getWorldBounds() };
|
|
7685
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
|
+
}
|
|
7686
8393
|
/** Glide in on a section and surface its summary (same path as a section tap). */
|
|
7687
8394
|
focusSection(id) {
|
|
7688
8395
|
this.handleSectionTap(id);
|
|
7689
8396
|
}
|
|
8397
|
+
/** Live seat membership count used by buyer section-navigation controls. */
|
|
8398
|
+
getSectionSeatCount(id) {
|
|
8399
|
+
return this.renderer?.sectionMembers?.(id)?.length ?? 0;
|
|
8400
|
+
}
|
|
7690
8401
|
/** Zoom back out to the whole chart and clear the section-summary card + focus. */
|
|
7691
8402
|
overview() {
|
|
7692
8403
|
this.renderer?.clearSectionFocus?.();
|
|
@@ -7739,15 +8450,21 @@ var PickerController = class {
|
|
|
7739
8450
|
}
|
|
7740
8451
|
const categories = [...byCat.entries()].map(([key, count]) => {
|
|
7741
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);
|
|
7742
8456
|
return {
|
|
7743
8457
|
key,
|
|
7744
8458
|
count,
|
|
7745
8459
|
label: cat?.label ?? key,
|
|
7746
8460
|
color: cat?.color ?? "#6e7bff",
|
|
7747
|
-
price:
|
|
8461
|
+
price: priceMin,
|
|
8462
|
+
priceMin,
|
|
8463
|
+
priceMax
|
|
7748
8464
|
};
|
|
7749
8465
|
}).sort((a, b) => a.price - b.price);
|
|
7750
|
-
const
|
|
8466
|
+
const minimums = categories.map((c) => c.priceMin);
|
|
8467
|
+
const maximums = categories.map((c) => c.priceMax);
|
|
7751
8468
|
const zone = sec.zone ? doc.zones?.find((z) => z.id === sec.zone) : void 0;
|
|
7752
8469
|
const color = sec.color ?? zone?.color ?? categories[0]?.color ?? "#6e7bff";
|
|
7753
8470
|
return {
|
|
@@ -7757,8 +8474,8 @@ var PickerController = class {
|
|
|
7757
8474
|
...sec.entrance && sec.entrance.trim() ? { entrance: sec.entrance.trim() } : {},
|
|
7758
8475
|
color,
|
|
7759
8476
|
seatsLeft,
|
|
7760
|
-
priceMin:
|
|
7761
|
-
priceMax:
|
|
8477
|
+
priceMin: minimums.length ? Math.min(...minimums) : 0,
|
|
8478
|
+
priceMax: maximums.length ? Math.max(...maximums) : 0,
|
|
7762
8479
|
categories
|
|
7763
8480
|
};
|
|
7764
8481
|
}
|
|
@@ -7799,11 +8516,12 @@ var PickerController = class {
|
|
|
7799
8516
|
const accessibility = s.accessibility?.length ? { accessibility: s.accessibility } : void 0;
|
|
7800
8517
|
const wheelchair = s.wheelchairSpaceType ? { wheelchairSpaceType: s.wheelchairSpaceType } : void 0;
|
|
7801
8518
|
const context = this.seatContext.get(s.id);
|
|
7802
|
-
const tiers = this.
|
|
7803
|
-
|
|
7804
|
-
|
|
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 };
|
|
7805
8523
|
}
|
|
7806
|
-
const chosen =
|
|
8524
|
+
const chosen = resolution.tier;
|
|
7807
8525
|
return {
|
|
7808
8526
|
id: s.id,
|
|
7809
8527
|
label: s.label,
|
|
@@ -7811,6 +8529,7 @@ var PickerController = class {
|
|
|
7811
8529
|
...context,
|
|
7812
8530
|
categoryKey: s.categoryKey,
|
|
7813
8531
|
price: chosen.price,
|
|
8532
|
+
currency: this.currency,
|
|
7814
8533
|
tiers,
|
|
7815
8534
|
tierId: chosen.id,
|
|
7816
8535
|
...commercial,
|
|
@@ -7820,8 +8539,9 @@ var PickerController = class {
|
|
|
7820
8539
|
}
|
|
7821
8540
|
toTableSeat(table) {
|
|
7822
8541
|
const representative = table.chairs[0];
|
|
7823
|
-
const tiers = this.tiersFor(table.categoryKey);
|
|
7824
|
-
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;
|
|
7825
8545
|
return {
|
|
7826
8546
|
id: representative?.id ?? table.objectId,
|
|
7827
8547
|
label: table.label,
|
|
@@ -7832,6 +8552,7 @@ var PickerController = class {
|
|
|
7832
8552
|
rowLabel: table.displayLabel ?? table.label,
|
|
7833
8553
|
categoryKey: table.categoryKey,
|
|
7834
8554
|
price: chosen?.price ?? this.priceFor(table.categoryKey),
|
|
8555
|
+
currency: this.currency,
|
|
7835
8556
|
...tiers ? { tiers, tierId: chosen?.id } : {},
|
|
7836
8557
|
objectType: "table",
|
|
7837
8558
|
bookingMode: table.mode,
|
|
@@ -7861,6 +8582,27 @@ var PickerController = class {
|
|
|
7861
8582
|
const t2 = this._doc?.categories.find((c) => c.key === categoryKey)?.tiers;
|
|
7862
8583
|
return t2 && t2.length ? t2 : void 0;
|
|
7863
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
|
+
}
|
|
7864
8606
|
/**
|
|
7865
8607
|
* Choose a ticket tier for a selected seat (e.g. Adult → Child). Re-emits the
|
|
7866
8608
|
* selection so the host's cart + the eventual hold carry the new tier + price.
|
|
@@ -7870,7 +8612,7 @@ var PickerController = class {
|
|
|
7870
8612
|
setSeatTier(seatId, tierId) {
|
|
7871
8613
|
const seat = this.seatById.get(seatId);
|
|
7872
8614
|
if (!seat) return;
|
|
7873
|
-
const tiers = this.
|
|
8615
|
+
const tiers = this.tiersForSeat(seat);
|
|
7874
8616
|
if (!tiers) return;
|
|
7875
8617
|
if (tierId == null) this.seatTiers.delete(seatId);
|
|
7876
8618
|
else if (tiers.some((t2) => t2.id === tierId)) this.seatTiers.set(seatId, tierId);
|
|
@@ -7891,6 +8633,16 @@ var PickerController = class {
|
|
|
7891
8633
|
this.opts.onSelectionChange?.(this.getSelection());
|
|
7892
8634
|
this.emitOrphanHint();
|
|
7893
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
|
+
}
|
|
7894
8646
|
/**
|
|
7895
8647
|
* Orphan-seat advice on manual selection: when the buyer's current picks
|
|
7896
8648
|
* strand one (or more) single free seats between unavailable same-row
|
|
@@ -7901,6 +8653,12 @@ var PickerController = class {
|
|
|
7901
8653
|
if (!this.opts.onHint) return;
|
|
7902
8654
|
const r = this.renderer;
|
|
7903
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
|
+
}
|
|
7904
8662
|
const selected = new Set(r.getSelection().map((s) => s.id));
|
|
7905
8663
|
const stranded = selected.size ? strandedSingles(this.seatById.values(), (id) => r.getStatus(id), selected) : [];
|
|
7906
8664
|
if (stranded.length > 0) {
|
|
@@ -8022,6 +8780,8 @@ var PickerController = class {
|
|
|
8022
8780
|
["free", "held", "booked", "not_for_sale"].forEach((st) => {
|
|
8023
8781
|
if (st !== defaultSeatStatus && byStatus[st].length) r.setStatus(byStatus[st], st);
|
|
8024
8782
|
});
|
|
8783
|
+
const notForSaleCategoryIds = this.notForSaleCategorySeatIds();
|
|
8784
|
+
if (notForSaleCategoryIds.length) r.setStatus(notForSaleCategoryIds, "not_for_sale");
|
|
8025
8785
|
this.clearBookedHoldIfSettled();
|
|
8026
8786
|
this.opts.onStatusChange?.();
|
|
8027
8787
|
}
|
|
@@ -9374,9 +10134,9 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
9374
10134
|
|
|
9375
10135
|
// src/i18n/bundles.ts
|
|
9376
10136
|
var LOADERS = {
|
|
9377
|
-
es: () => import("./es-
|
|
9378
|
-
de: () => import("./de-
|
|
9379
|
-
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 }))
|
|
9380
10140
|
};
|
|
9381
10141
|
var loaded = /* @__PURE__ */ new Set(["en"]);
|
|
9382
10142
|
async function loadLocale(code) {
|
|
@@ -9406,6 +10166,7 @@ export {
|
|
|
9406
10166
|
MAX_EVENT_INVENTORY,
|
|
9407
10167
|
MAX_GA_CAPACITY,
|
|
9408
10168
|
PickerController,
|
|
10169
|
+
PickerSelectionError,
|
|
9409
10170
|
RENDERED_QUALITY_REPORT_VERSION,
|
|
9410
10171
|
SEAT_COMMERCIAL_MARKS,
|
|
9411
10172
|
SUPPORTED_LOCALES,
|
|
@@ -9429,6 +10190,7 @@ export {
|
|
|
9429
10190
|
expandTable,
|
|
9430
10191
|
expandTableSlots,
|
|
9431
10192
|
floorObjects,
|
|
10193
|
+
floorOverviewTranslations,
|
|
9432
10194
|
floorsOf,
|
|
9433
10195
|
formatDate,
|
|
9434
10196
|
formatMinor,
|
|
@@ -9447,6 +10209,7 @@ export {
|
|
|
9447
10209
|
isGaUnitLabel,
|
|
9448
10210
|
isSectionHidden,
|
|
9449
10211
|
layerOf,
|
|
10212
|
+
layoutFloorOverview,
|
|
9450
10213
|
loadLocale,
|
|
9451
10214
|
objectCenter,
|
|
9452
10215
|
owningSectionForObject,
|
|
@@ -9466,6 +10229,7 @@ export {
|
|
|
9466
10229
|
tCount,
|
|
9467
10230
|
tableInventoryCount,
|
|
9468
10231
|
tableSeatCountsBySide,
|
|
10232
|
+
translateObject,
|
|
9469
10233
|
validGAInventorySegments
|
|
9470
10234
|
};
|
|
9471
10235
|
//# sourceMappingURL=index.js.map
|