@seatlayer/core 0.22.0 → 0.24.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/{de-KPTY7UOT.js → de-EIG65UFU.js} +5 -35
- package/dist/de-EIG65UFU.js.map +1 -0
- package/dist/{es-ALPR5FBB.js → es-47HFAWS6.js} +5 -35
- package/dist/es-47HFAWS6.js.map +1 -0
- package/dist/{fr-HVQLQD2N.js → fr-7IXNNFBS.js} +5 -35
- package/dist/fr-7IXNNFBS.js.map +1 -0
- package/dist/index.cjs +386 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -5
- package/dist/index.d.ts +119 -5
- package/dist/index.js +375 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/de-KPTY7UOT.js.map +0 -1
- package/dist/es-ALPR5FBB.js.map +0 -1
- package/dist/fr-HVQLQD2N.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -41,29 +41,17 @@ var init_es = __esm({
|
|
|
41
41
|
"common.done": "Listo",
|
|
42
42
|
"common.copied": "\u2713 Copiado",
|
|
43
43
|
// buyer picker (extraction in progress — P4 of the port plan)
|
|
44
|
-
"picker.holdSeats": "Retener asientos y comprar",
|
|
45
|
-
"picker.completeBooking": "Completar reserva",
|
|
46
|
-
"picker.seatsHeld": "Asientos retenidos \u2014 {time}",
|
|
47
44
|
"picker.holdExpired": "Tu retenci\xF3n expir\xF3 \u2014 los asientos fueron liberados. Elige de nuevo.",
|
|
48
|
-
"picker.seatTaken": "El asiento {label} fue tomado por otro comprador.",
|
|
49
45
|
"picker.poweredBy": "Con la tecnolog\xEDa de SeatLayer",
|
|
50
46
|
"picker.testMode": "MODO DE PRUEBA",
|
|
51
|
-
"picker.colorblind": "Colores aptos para daltonismo",
|
|
52
47
|
"picker.orphanHint": "Esto deja un asiento aislado \u2014 considera desplazarte un asiento.",
|
|
53
|
-
"picker.seats.one": "{count} asiento",
|
|
54
|
-
"picker.seats.other": "{count} asientos",
|
|
55
48
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
56
49
|
"map.aria": "Mapa de asientos. Usa las flechas para moverte entre asientos, Intro para seleccionar.",
|
|
57
50
|
"map.seatsLeft": "{count} LIBRES",
|
|
58
51
|
"map.statusHeld": "En espera",
|
|
59
52
|
"map.statusTaken": "Ocupado",
|
|
60
53
|
"map.fromPrice": "DESDE {price}",
|
|
61
|
-
// buyer picker
|
|
62
|
-
"picker.language": "Idioma",
|
|
63
|
-
"picker.zoomToFit": "Ajustar zoom",
|
|
64
|
-
"picker.seatCountLabel": "asientos",
|
|
65
|
-
"picker.capacity": "capacidad",
|
|
66
|
-
"picker.viewMode": "Modo de vista",
|
|
54
|
+
// buyer picker widget (src/picker/widget/SeatPicker.ts)
|
|
67
55
|
"picker.floor": "Piso",
|
|
68
56
|
"picker.zoomLevel": "Nivel de zoom",
|
|
69
57
|
"picker.rungTip.zones": "Vista general \u2014 grupos de secciones como Grada norte o VIP",
|
|
@@ -78,37 +66,19 @@ var init_es = __esm({
|
|
|
78
66
|
"picker.seatsLeftInSection.other": "{count} asientos disponibles",
|
|
79
67
|
"picker.overview": "Descripci\xF3n general",
|
|
80
68
|
"picker.tapSeatHint": "Toca cualquier asiento para ver su vista",
|
|
81
|
-
"picker.chartSize": "Tama\xF1o del gr\xE1fico",
|
|
82
|
-
"picker.custom": "Personalizado",
|
|
83
|
-
"picker.categories": "Categor\xEDas",
|
|
84
|
-
"picker.accessibility": "Accesibilidad",
|
|
85
|
-
"picker.showAnyAccessible": "Mostrar cualquier asiento accesible",
|
|
86
|
-
"picker.any": "Cualquiera",
|
|
87
|
-
"picker.yourSeats": "Tus asientos",
|
|
88
|
-
"picker.emptySeats": "Toca asientos en el mapa",
|
|
89
|
-
"picker.emptySeatsWithGa": "Toca asientos en el mapa \xB7 toca un \xE1rea de pie para entradas de entrada general",
|
|
90
|
-
"picker.oneFewer": "Uno menos",
|
|
91
|
-
"picker.oneMore": "Uno m\xE1s",
|
|
92
|
-
"picker.remove": "Eliminar {label}",
|
|
93
69
|
"picker.ticketTierFor": "Categor\xEDa de entrada para {label}",
|
|
94
|
-
"picker.total": "Total: {amount}",
|
|
95
70
|
"picker.viewFromSeat": "Vista desde el asiento {label}",
|
|
96
71
|
"picker.real360": "REAL 360\xB0",
|
|
97
72
|
"picker.preview": "VISTA PREVIA",
|
|
98
|
-
"picker.open360": "Abrir vista de 360\xB0",
|
|
99
73
|
"picker.sightline": "\u2248 {m} m al escenario \xB7 visi\xF3n despejada",
|
|
100
|
-
"picker.bookedDemo": "\xA1Reservado! (demostraci\xF3n)",
|
|
101
|
-
"picker.bookButton.one": "Reservar {count} entrada \u2014 {amount}",
|
|
102
|
-
"picker.bookButton.other": "Reservar {count} entradas \u2014 {amount}",
|
|
103
|
-
"picker.simulateCrowd": "Simular multitud: {state}",
|
|
104
|
-
"picker.on": "ACTIVADO",
|
|
105
|
-
"picker.off": "DESACTIVADO",
|
|
106
74
|
"picker.panorama360": "Foto de 360\xB0 del lugar",
|
|
107
75
|
"picker.illustrationCaption": "ilustraci\xF3n \xB7 \u2248 {m} m del escenario",
|
|
108
76
|
"picker.restrictedView": "Visibilidad restringida",
|
|
109
77
|
"picker.obstructedView": "Visibilidad obstruida",
|
|
110
78
|
"picker.premiumSeat": "Asiento premium",
|
|
111
|
-
"picker.hideLimitedView": "Ocultar asientos con visibilidad limitada"
|
|
79
|
+
"picker.hideLimitedView": "Ocultar asientos con visibilidad limitada",
|
|
80
|
+
"picker.bestSeatsPremium": "Mejores asientos",
|
|
81
|
+
"picker.premiumFallbackNote": "No hay un bloque premium de {count}: mostrando los mejores disponibles"
|
|
112
82
|
};
|
|
113
83
|
}
|
|
114
84
|
});
|
|
@@ -129,29 +99,17 @@ var init_de = __esm({
|
|
|
129
99
|
"common.done": "Fertig",
|
|
130
100
|
"common.copied": "\u2713 Kopiert",
|
|
131
101
|
// buyer picker (extraction in progress — P4 of the port plan)
|
|
132
|
-
"picker.holdSeats": "Pl\xE4tze reservieren & Kasse",
|
|
133
|
-
"picker.completeBooking": "Buchung abschlie\xDFen",
|
|
134
|
-
"picker.seatsHeld": "Pl\xE4tze reserviert \u2014 {time}",
|
|
135
102
|
"picker.holdExpired": "Ihre Reservierung ist abgelaufen \u2014 die Pl\xE4tze wurden freigegeben. Bitte erneut ausw\xE4hlen.",
|
|
136
|
-
"picker.seatTaken": "Platz {label} wurde gerade von einem anderen K\xE4ufer gebucht.",
|
|
137
103
|
"picker.poweredBy": "Bereitgestellt von SeatLayer",
|
|
138
104
|
"picker.testMode": "TESTMODUS",
|
|
139
|
-
"picker.colorblind": "Farbenblind-freundliche Farben",
|
|
140
105
|
"picker.orphanHint": "Dadurch bleibt ein einzelner Platz \xFCbrig \u2014 r\xFCcken Sie ggf. einen Platz weiter.",
|
|
141
|
-
"picker.seats.one": "{count} Platz",
|
|
142
|
-
"picker.seats.other": "{count} Pl\xE4tze",
|
|
143
106
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
144
107
|
"map.aria": "Sitzplan. Mit Pfeiltasten zwischen Pl\xE4tzen navigieren, Eingabe zum Ausw\xE4hlen.",
|
|
145
108
|
"map.seatsLeft": "{count} FREI",
|
|
146
109
|
"map.statusHeld": "Reserviert",
|
|
147
110
|
"map.statusTaken": "Vergeben",
|
|
148
111
|
"map.fromPrice": "AB {price}",
|
|
149
|
-
// buyer picker
|
|
150
|
-
"picker.language": "Sprache",
|
|
151
|
-
"picker.zoomToFit": "Auf Gr\xF6\xDFe anpassen",
|
|
152
|
-
"picker.seatCountLabel": "Pl\xE4tze",
|
|
153
|
-
"picker.capacity": "Kapazit\xE4t",
|
|
154
|
-
"picker.viewMode": "Ansichtsmodus",
|
|
112
|
+
// buyer picker widget (src/picker/widget/SeatPicker.ts)
|
|
155
113
|
"picker.floor": "Etage",
|
|
156
114
|
"picker.zoomLevel": "Zoomstufe",
|
|
157
115
|
"picker.rungTip.zones": "\xDCbersicht \u2014 Bereichsgruppen wie Nordtrib\xFCne oder VIP",
|
|
@@ -166,37 +124,19 @@ var init_de = __esm({
|
|
|
166
124
|
"picker.seatsLeftInSection.other": "{count} Pl\xE4tze verf\xFCgbar",
|
|
167
125
|
"picker.overview": "\xDCbersicht",
|
|
168
126
|
"picker.tapSeatHint": "Tippen Sie auf einen Platz, um die Ansicht zu pr\xFCfen",
|
|
169
|
-
"picker.chartSize": "Kartengr\xF6\xDFe",
|
|
170
|
-
"picker.custom": "Benutzerdefiniert",
|
|
171
|
-
"picker.categories": "Kategorien",
|
|
172
|
-
"picker.accessibility": "Barrierefreiheit",
|
|
173
|
-
"picker.showAnyAccessible": "Jeden barrierefreien Platz anzeigen",
|
|
174
|
-
"picker.any": "Beliebig",
|
|
175
|
-
"picker.yourSeats": "Ihre Pl\xE4tze",
|
|
176
|
-
"picker.emptySeats": "Tippen Sie auf die Pl\xE4tze auf der Karte",
|
|
177
|
-
"picker.emptySeatsWithGa": "Tippen Sie auf die Pl\xE4tze auf der Karte \xB7 tippen Sie auf einen Stehbereich f\xFCr Stehplatztickets",
|
|
178
|
-
"picker.oneFewer": "Einen weniger",
|
|
179
|
-
"picker.oneMore": "Einen mehr",
|
|
180
|
-
"picker.remove": "{label} entfernen",
|
|
181
127
|
"picker.ticketTierFor": "Ticketklasse f\xFCr {label}",
|
|
182
|
-
"picker.total": "Gesamt: {amount}",
|
|
183
128
|
"picker.viewFromSeat": "Ansicht von Platz {label}",
|
|
184
129
|
"picker.real360": "REAL 360\xB0",
|
|
185
130
|
"picker.preview": "VORSCHAU",
|
|
186
|
-
"picker.open360": "\xD6ffnen Sie die 360\xB0-Ansicht",
|
|
187
131
|
"picker.sightline": "\u2248 {m} m zur B\xFChne \xB7 freie Sicht",
|
|
188
|
-
"picker.bookedDemo": "Gebucht! (Demo)",
|
|
189
|
-
"picker.bookButton.one": "{count} Ticket buchen \u2014 {amount}",
|
|
190
|
-
"picker.bookButton.other": "{count} Tickets buchen \u2014 {amount}",
|
|
191
|
-
"picker.simulateCrowd": "Menschenmenge simulieren: {state}",
|
|
192
|
-
"picker.on": "AN",
|
|
193
|
-
"picker.off": "AUS",
|
|
194
132
|
"picker.panorama360": "360\xB0-Veranstaltungsfotos",
|
|
195
133
|
"picker.illustrationCaption": "Illustration \xB7 \u2248 {m} m von der B\xFChne",
|
|
196
134
|
"picker.restrictedView": "Eingeschr\xE4nkte Sicht",
|
|
197
135
|
"picker.obstructedView": "Sichtbehinderung",
|
|
198
136
|
"picker.premiumSeat": "Premium-Platz",
|
|
199
|
-
"picker.hideLimitedView": "Pl\xE4tze mit eingeschr\xE4nkter Sicht ausblenden"
|
|
137
|
+
"picker.hideLimitedView": "Pl\xE4tze mit eingeschr\xE4nkter Sicht ausblenden",
|
|
138
|
+
"picker.bestSeatsPremium": "Beste Pl\xE4tze",
|
|
139
|
+
"picker.premiumFallbackNote": "Kein Premium-Block mit {count} Pl\xE4tzen \u2014 beste verf\xFCgbare werden angezeigt"
|
|
200
140
|
};
|
|
201
141
|
}
|
|
202
142
|
});
|
|
@@ -217,29 +157,17 @@ var init_fr = __esm({
|
|
|
217
157
|
"common.done": "Termin\xE9",
|
|
218
158
|
"common.copied": "\u2713 Copi\xE9",
|
|
219
159
|
// buyer picker (extraction in progress — P4 of the port plan)
|
|
220
|
-
"picker.holdSeats": "R\xE9server les si\xE8ges et valider",
|
|
221
|
-
"picker.completeBooking": "Terminer la r\xE9servation",
|
|
222
|
-
"picker.seatsHeld": "Si\xE8ges r\xE9serv\xE9s \u2014 {time}",
|
|
223
160
|
"picker.holdExpired": "Votre r\xE9servation a expir\xE9 \u2014 les si\xE8ges ont \xE9t\xE9 lib\xE9r\xE9s. S\xE9lectionnez \xE0 nouveau.",
|
|
224
|
-
"picker.seatTaken": "Le si\xE8ge {label} vient d'\xEAtre r\xE9serv\xE9 par un autre acheteur.",
|
|
225
161
|
"picker.poweredBy": "Propuls\xE9 par SeatLayer",
|
|
226
162
|
"picker.testMode": "MODE TEST",
|
|
227
|
-
"picker.colorblind": "Couleurs adapt\xE9es au daltonisme",
|
|
228
163
|
"picker.orphanHint": "Cela laisse un si\xE8ge isol\xE9 \u2014 pensez \xE0 vous d\xE9caler d'un si\xE8ge.",
|
|
229
|
-
"picker.seats.one": "{count} si\xE8ge",
|
|
230
|
-
"picker.seats.other": "{count} si\xE8ges",
|
|
231
164
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
232
165
|
"map.aria": "Plan de salle. Utilisez les fl\xE8ches pour naviguer entre les si\xE8ges, Entr\xE9e pour s\xE9lectionner.",
|
|
233
166
|
"map.seatsLeft": "{count} LIBRES",
|
|
234
167
|
"map.statusHeld": "En attente",
|
|
235
168
|
"map.statusTaken": "Pris",
|
|
236
169
|
"map.fromPrice": "D\xC8S {price}",
|
|
237
|
-
// buyer picker
|
|
238
|
-
"picker.language": "Langue",
|
|
239
|
-
"picker.zoomToFit": "Ajuster le zoom",
|
|
240
|
-
"picker.seatCountLabel": "si\xE8ges",
|
|
241
|
-
"picker.capacity": "capacit\xE9",
|
|
242
|
-
"picker.viewMode": "Mode d'affichage",
|
|
170
|
+
// buyer picker widget (src/picker/widget/SeatPicker.ts)
|
|
243
171
|
"picker.floor": "\xC9tage",
|
|
244
172
|
"picker.zoomLevel": "Niveau de zoom",
|
|
245
173
|
"picker.rungTip.zones": "Vue g\xE9n\xE9rale \u2014 groupes de sections comme Tribune nord ou VIP",
|
|
@@ -254,37 +182,19 @@ var init_fr = __esm({
|
|
|
254
182
|
"picker.seatsLeftInSection.other": "{count} si\xE8ges restants",
|
|
255
183
|
"picker.overview": "Vue d'ensemble",
|
|
256
184
|
"picker.tapSeatHint": "Appuyez sur un si\xE8ge pour v\xE9rifier sa vue",
|
|
257
|
-
"picker.chartSize": "Taille du plan",
|
|
258
|
-
"picker.custom": "Personnalis\xE9",
|
|
259
|
-
"picker.categories": "Cat\xE9gories",
|
|
260
|
-
"picker.accessibility": "Accessibilit\xE9",
|
|
261
|
-
"picker.showAnyAccessible": "Afficher un si\xE8ge accessible",
|
|
262
|
-
"picker.any": "N'importe quel",
|
|
263
|
-
"picker.yourSeats": "Vos si\xE8ges",
|
|
264
|
-
"picker.emptySeats": "Appuyez sur les si\xE8ges sur le plan",
|
|
265
|
-
"picker.emptySeatsWithGa": "Appuyez sur les si\xE8ges sur le plan \xB7 appuyez sur une zone debout pour les billets GA",
|
|
266
|
-
"picker.oneFewer": "Un de moins",
|
|
267
|
-
"picker.oneMore": "Un de plus",
|
|
268
|
-
"picker.remove": "Supprimer {label}",
|
|
269
185
|
"picker.ticketTierFor": "Cat\xE9gorie de billet pour {label}",
|
|
270
|
-
"picker.total": "Total : {amount}",
|
|
271
186
|
"picker.viewFromSeat": "Vue depuis le si\xE8ge {label}",
|
|
272
187
|
"picker.real360": "VRAI 360\xB0",
|
|
273
188
|
"picker.preview": "APER\xC7U",
|
|
274
|
-
"picker.open360": "Ouvrir la vue \xE0 360\xB0",
|
|
275
189
|
"picker.sightline": "\u2248 {m} m de la sc\xE8ne \xB7 vue d\xE9gag\xE9e",
|
|
276
|
-
"picker.bookedDemo": "R\xE9serv\xE9 ! (d\xE9mo)",
|
|
277
|
-
"picker.bookButton.one": "R\xE9server {count} billet \u2014 {amount}",
|
|
278
|
-
"picker.bookButton.other": "R\xE9server {count} billets \u2014 {amount}",
|
|
279
|
-
"picker.simulateCrowd": "Simuler la foule : {state}",
|
|
280
|
-
"picker.on": "ACTIV\xC9",
|
|
281
|
-
"picker.off": "D\xC9SACTIV\xC9",
|
|
282
190
|
"picker.panorama360": "Photo panoramique 360\xB0 du lieu",
|
|
283
191
|
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m de la sc\xE8ne",
|
|
284
192
|
"picker.restrictedView": "Visibilit\xE9 r\xE9duite",
|
|
285
193
|
"picker.obstructedView": "Vue obstru\xE9e",
|
|
286
194
|
"picker.premiumSeat": "Place premium",
|
|
287
|
-
"picker.hideLimitedView": "Masquer les places \xE0 visibilit\xE9 r\xE9duite"
|
|
195
|
+
"picker.hideLimitedView": "Masquer les places \xE0 visibilit\xE9 r\xE9duite",
|
|
196
|
+
"picker.bestSeatsPremium": "Meilleures places",
|
|
197
|
+
"picker.premiumFallbackNote": "Aucun bloc premium de {count} \u2014 affichage des meilleures places disponibles"
|
|
288
198
|
};
|
|
289
199
|
}
|
|
290
200
|
});
|
|
@@ -337,6 +247,8 @@ __export(index_exports, {
|
|
|
337
247
|
pointInPolygonWithHoles: () => pointInPolygonWithHoles,
|
|
338
248
|
polygonLabelPoint: () => polygonLabelPoint,
|
|
339
249
|
resolveLocale: () => resolveLocale,
|
|
250
|
+
rowSeatPositions: () => rowSeatPositions,
|
|
251
|
+
seatLabelPart: () => seatLabelPart,
|
|
340
252
|
setLocale: () => setLocale,
|
|
341
253
|
setMoneyLocale: () => setMoneyLocale,
|
|
342
254
|
setStringOverrides: () => setStringOverrides,
|
|
@@ -459,6 +371,53 @@ function transformSectionOutlinePath(path, transform, radiusScale = 1, reflected
|
|
|
459
371
|
};
|
|
460
372
|
}
|
|
461
373
|
|
|
374
|
+
// src/core/labeling.ts
|
|
375
|
+
var FULL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
376
|
+
var LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
377
|
+
function toBijectiveBase(index, alphabet) {
|
|
378
|
+
const base = alphabet.length;
|
|
379
|
+
let n = index + 1;
|
|
380
|
+
let out = "";
|
|
381
|
+
while (n > 0) {
|
|
382
|
+
n -= 1;
|
|
383
|
+
const rem = n % base;
|
|
384
|
+
out = alphabet[rem] + out;
|
|
385
|
+
n = Math.floor(n / base);
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
function toLetters(value, lower = false) {
|
|
390
|
+
const alphabet = lower ? LOWER_ALPHABET : FULL_ALPHABET;
|
|
391
|
+
return toBijectiveBase(Math.max(0, Math.floor(value) - 1), alphabet);
|
|
392
|
+
}
|
|
393
|
+
var ROMAN_TABLE = [
|
|
394
|
+
[1e3, "M"],
|
|
395
|
+
[900, "CM"],
|
|
396
|
+
[500, "D"],
|
|
397
|
+
[400, "CD"],
|
|
398
|
+
[100, "C"],
|
|
399
|
+
[90, "XC"],
|
|
400
|
+
[50, "L"],
|
|
401
|
+
[40, "XL"],
|
|
402
|
+
[10, "X"],
|
|
403
|
+
[9, "IX"],
|
|
404
|
+
[5, "V"],
|
|
405
|
+
[4, "IV"],
|
|
406
|
+
[1, "I"]
|
|
407
|
+
];
|
|
408
|
+
function toRoman(value) {
|
|
409
|
+
if (!Number.isFinite(value) || value <= 0) return String(value);
|
|
410
|
+
let remaining = Math.floor(value);
|
|
411
|
+
let out = "";
|
|
412
|
+
for (const [n, sym] of ROMAN_TABLE) {
|
|
413
|
+
while (remaining >= n) {
|
|
414
|
+
out += sym;
|
|
415
|
+
remaining -= n;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
|
|
462
421
|
// src/core/layout.ts
|
|
463
422
|
function overrideAccessibility(o) {
|
|
464
423
|
if (!o) return [];
|
|
@@ -504,24 +463,62 @@ function overrideMap(row) {
|
|
|
504
463
|
if (row.overrides) for (const o of row.overrides) m.set(o.index, o);
|
|
505
464
|
return m;
|
|
506
465
|
}
|
|
507
|
-
function
|
|
508
|
-
const
|
|
466
|
+
function centerRank(n) {
|
|
467
|
+
const rank = new Array(n);
|
|
468
|
+
Array.from({ length: n }, (_, i) => i).sort((a, b) => Math.abs(2 * a - (n - 1)) - Math.abs(2 * b - (n - 1)) || a - b).forEach((idx, k) => rank[idx] = k);
|
|
469
|
+
return rank;
|
|
470
|
+
}
|
|
471
|
+
function seatLabelPart(row, i) {
|
|
472
|
+
const rawStart = row.seatLabelStart ?? 1;
|
|
509
473
|
const dir = row.seatNumbering?.direction ?? "ltr";
|
|
510
474
|
const step = row.seatNumbering?.step ?? 1;
|
|
475
|
+
const scheme = row.seatNumbering?.scheme ?? "decimal";
|
|
476
|
+
const prefix = row.seatNumbering?.prefix ?? "";
|
|
477
|
+
const endAt = row.seatNumbering?.endAt;
|
|
511
478
|
const n = row.seatCount;
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
479
|
+
if (scheme === "updown") {
|
|
480
|
+
const half = Math.ceil(n / 2);
|
|
481
|
+
const core2 = i < half ? rawStart + 2 * i : rawStart - 1 + 2 * (n - i);
|
|
482
|
+
return `${prefix}${core2}`;
|
|
483
|
+
}
|
|
484
|
+
const effStep = scheme === "odd" || scheme === "even" ? 2 : step;
|
|
485
|
+
const start = endAt != null && Number.isFinite(endAt) ? endAt - (n - 1) * effStep : rawStart;
|
|
486
|
+
const p = dir === "center" ? centerRank(n)[i] : dir === "rtl" ? n - 1 - i : i;
|
|
487
|
+
let core;
|
|
488
|
+
switch (scheme) {
|
|
489
|
+
case "odd": {
|
|
490
|
+
const firstOdd = start % 2 === 1 ? start : start + 1;
|
|
491
|
+
core = String(firstOdd + p * 2);
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
case "even": {
|
|
495
|
+
const firstEven = start % 2 === 0 ? start : start + 1;
|
|
496
|
+
core = String(firstEven + p * 2);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case "roman":
|
|
500
|
+
core = toRoman(start + p * step);
|
|
501
|
+
break;
|
|
502
|
+
case "letters-upper":
|
|
503
|
+
core = toLetters(start + p * step, false);
|
|
504
|
+
break;
|
|
505
|
+
case "letters-lower":
|
|
506
|
+
core = toLetters(start + p * step, true);
|
|
507
|
+
break;
|
|
508
|
+
case "decimal":
|
|
509
|
+
default:
|
|
510
|
+
core = String(start + p * step);
|
|
511
|
+
break;
|
|
519
512
|
}
|
|
513
|
+
return `${prefix}${core}`;
|
|
514
|
+
}
|
|
515
|
+
function expandRowSlots(row) {
|
|
520
516
|
const ov = overrideMap(row);
|
|
521
517
|
return rowSeatPositions(row).map((p, i) => {
|
|
522
518
|
const o = ov.get(i);
|
|
523
519
|
const accessibility = overrideAccessibility(o);
|
|
524
|
-
const
|
|
520
|
+
const part = seatLabelPart(row, i);
|
|
521
|
+
const inventoryLabel = o?.label ?? `${row.label}-${part}`;
|
|
525
522
|
const displayPrefix = row.displayLabel ?? row.label;
|
|
526
523
|
const commercial = { ...row.commercial, ...o?.commercial };
|
|
527
524
|
return {
|
|
@@ -529,7 +526,7 @@ function expandRowSlots(row) {
|
|
|
529
526
|
x: p.x + (o?.dx ?? 0),
|
|
530
527
|
y: p.y + (o?.dy ?? 0),
|
|
531
528
|
label: inventoryLabel,
|
|
532
|
-
displayLabel: o?.displayLabel ?? `${displayPrefix}-${
|
|
529
|
+
displayLabel: o?.displayLabel ?? `${displayPrefix}-${part}`,
|
|
533
530
|
categoryKey: o?.categoryKey ?? row.categoryKey,
|
|
534
531
|
skipped: !!o?.skip,
|
|
535
532
|
accessible: accessibility.length > 0,
|
|
@@ -671,8 +668,8 @@ function polygonLabelPoint(outer, holes) {
|
|
|
671
668
|
const xs = outer.map((point) => point.x);
|
|
672
669
|
const ys = outer.map((point) => point.y);
|
|
673
670
|
const bounds = { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) };
|
|
674
|
-
const
|
|
675
|
-
if (pointInPolygonWithHoles(
|
|
671
|
+
const centroid2 = polygonCentroid(outer);
|
|
672
|
+
if (pointInPolygonWithHoles(centroid2, outer, holes)) return centroid2;
|
|
676
673
|
let best = outer[0];
|
|
677
674
|
let bestScore = -Infinity;
|
|
678
675
|
const rings = [outer, ...holes ?? []];
|
|
@@ -996,6 +993,7 @@ var import_Rect = require("konva/lib/shapes/Rect");
|
|
|
996
993
|
var import_Ellipse = require("konva/lib/shapes/Ellipse");
|
|
997
994
|
var import_Line = require("konva/lib/shapes/Line");
|
|
998
995
|
var import_Text = require("konva/lib/shapes/Text");
|
|
996
|
+
var import_Path = require("konva/lib/shapes/Path");
|
|
999
997
|
var import_Image = require("konva/lib/shapes/Image");
|
|
1000
998
|
var import_Shape = require("konva/lib/Shape");
|
|
1001
999
|
|
|
@@ -1012,6 +1010,8 @@ var LIGHT_BOOKABLE_LABEL_INK = "#ffffff";
|
|
|
1012
1010
|
function isBookableLabelLegibleAtScale(fontSize, effectiveScale) {
|
|
1013
1011
|
return fontSize * effectiveScale >= MIN_VISIBLE_BOOKABLE_LABEL_PX;
|
|
1014
1012
|
}
|
|
1013
|
+
var ACCESS_GLYPH_VIEWBOX = 24;
|
|
1014
|
+
var ACCESS_GLYPH_PATH = "M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z";
|
|
1015
1015
|
function bookableMarkerLabel(publicLabel) {
|
|
1016
1016
|
return /-(\d{1,5})$/.exec(publicLabel)?.[1] ?? publicLabel;
|
|
1017
1017
|
}
|
|
@@ -1091,29 +1091,17 @@ var en = {
|
|
|
1091
1091
|
"common.done": "Done",
|
|
1092
1092
|
"common.copied": "\u2713 Copied",
|
|
1093
1093
|
// buyer picker
|
|
1094
|
-
"picker.holdSeats": "Hold seats & checkout",
|
|
1095
|
-
"picker.completeBooking": "Complete booking",
|
|
1096
|
-
"picker.seatsHeld": "Seats held \u2014 {time}",
|
|
1097
1094
|
"picker.holdExpired": "Your hold expired \u2014 the seats were released. Pick again.",
|
|
1098
|
-
"picker.seatTaken": "Seat {label} was just taken by another buyer.",
|
|
1099
1095
|
"picker.poweredBy": "Powered by SeatLayer",
|
|
1100
1096
|
"picker.testMode": "TEST MODE",
|
|
1101
|
-
"picker.colorblind": "Colorblind-friendly colors",
|
|
1102
1097
|
"picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
|
|
1103
|
-
"picker.seats.one": "{count} seat",
|
|
1104
|
-
"picker.seats.other": "{count} seats",
|
|
1105
1098
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
1106
1099
|
"map.aria": "Seating map. Use arrow keys to move between seats, Enter to select.",
|
|
1107
1100
|
"map.seatsLeft": "{count} LEFT",
|
|
1108
1101
|
"map.fromPrice": "FROM {price}",
|
|
1109
1102
|
"map.statusHeld": "On hold",
|
|
1110
1103
|
"map.statusTaken": "Taken",
|
|
1111
|
-
// buyer picker
|
|
1112
|
-
"picker.language": "Language",
|
|
1113
|
-
"picker.zoomToFit": "Zoom to fit",
|
|
1114
|
-
"picker.seatCountLabel": "seats",
|
|
1115
|
-
"picker.capacity": "capacity",
|
|
1116
|
-
"picker.viewMode": "View mode",
|
|
1104
|
+
// buyer picker widget (src/picker/widget/SeatPicker.ts)
|
|
1117
1105
|
"picker.floor": "Floor",
|
|
1118
1106
|
"picker.zoomLevel": "Zoom level",
|
|
1119
1107
|
"picker.rungTip.zones": "Venue overview \u2014 groups of sections such as North Stand or VIP",
|
|
@@ -1128,37 +1116,19 @@ var en = {
|
|
|
1128
1116
|
"picker.seatsLeftInSection.other": "{count} seats left",
|
|
1129
1117
|
"picker.overview": "Overview",
|
|
1130
1118
|
"picker.tapSeatHint": "Tap any seat to check its view",
|
|
1131
|
-
"picker.chartSize": "Chart size",
|
|
1132
|
-
"picker.custom": "Custom",
|
|
1133
|
-
"picker.categories": "Categories",
|
|
1134
|
-
"picker.accessibility": "Accessibility",
|
|
1135
|
-
"picker.showAnyAccessible": "Show any accessible seat",
|
|
1136
|
-
"picker.any": "Any",
|
|
1137
|
-
"picker.yourSeats": "Your seats",
|
|
1138
|
-
"picker.emptySeats": "Tap seats on the map",
|
|
1139
|
-
"picker.emptySeatsWithGa": "Tap seats on the map \xB7 tap a standing area for GA tickets",
|
|
1140
|
-
"picker.oneFewer": "One fewer",
|
|
1141
|
-
"picker.oneMore": "One more",
|
|
1142
|
-
"picker.remove": "Remove {label}",
|
|
1143
1119
|
"picker.ticketTierFor": "Ticket tier for {label}",
|
|
1144
|
-
"picker.total": "Total: {amount}",
|
|
1145
1120
|
"picker.viewFromSeat": "View from seat {label}",
|
|
1146
1121
|
"picker.real360": "REAL 360\xB0",
|
|
1147
1122
|
"picker.preview": "PREVIEW",
|
|
1148
|
-
"picker.open360": "Open 360\xB0 view",
|
|
1149
1123
|
"picker.sightline": "\u2248 {m} m to stage \xB7 clear sightline",
|
|
1150
|
-
"picker.bookedDemo": "Booked! (demo)",
|
|
1151
|
-
"picker.bookButton.one": "Book {count} ticket \u2014 {amount}",
|
|
1152
|
-
"picker.bookButton.other": "Book {count} tickets \u2014 {amount}",
|
|
1153
|
-
"picker.simulateCrowd": "Simulate crowd: {state}",
|
|
1154
|
-
"picker.on": "ON",
|
|
1155
|
-
"picker.off": "OFF",
|
|
1156
1124
|
"picker.panorama360": "360\xB0 venue photo",
|
|
1157
1125
|
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m from stage",
|
|
1158
1126
|
"picker.restrictedView": "Restricted view",
|
|
1159
1127
|
"picker.obstructedView": "Obstructed view",
|
|
1160
1128
|
"picker.premiumSeat": "Premium seat",
|
|
1161
|
-
"picker.hideLimitedView": "Hide limited-view seats"
|
|
1129
|
+
"picker.hideLimitedView": "Hide limited-view seats",
|
|
1130
|
+
"picker.bestSeatsPremium": "Best seats",
|
|
1131
|
+
"picker.premiumFallbackNote": "No premium block of {count} \u2014 showing best overall"
|
|
1162
1132
|
};
|
|
1163
1133
|
|
|
1164
1134
|
// src/i18n/index.ts
|
|
@@ -1209,6 +1179,7 @@ var CACHE_THRESHOLD = 0.55 * SEAT_LEGIBLE_SCALE;
|
|
|
1209
1179
|
var LABEL_SCALE = MIN_VISIBLE_BOOKABLE_LABEL_PX / SEAT_LABEL_FONT_SIZE;
|
|
1210
1180
|
var MIN_FITTED_SEAT_LABEL_FONT_SIZE = 4;
|
|
1211
1181
|
var SEAT_TAP_SLOP_PX = 14;
|
|
1182
|
+
var SEAT_GLYPH_MIN_PX = 6.5;
|
|
1212
1183
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
1213
1184
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
1214
1185
|
var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
@@ -1498,6 +1469,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1498
1469
|
this.boothLabelById = /* @__PURE__ */ new Map();
|
|
1499
1470
|
/** Viewport seat labels are rebuilt after each settled camera change. */
|
|
1500
1471
|
this.seatLabelById = /* @__PURE__ */ new Map();
|
|
1472
|
+
/** Coloured accommodation ring per accessible seat (few per chart). */
|
|
1473
|
+
this.accessRingById = /* @__PURE__ */ new Map();
|
|
1474
|
+
/** Centred accessibility glyph per accessible seat — shown once the seat is
|
|
1475
|
+
* big enough on-screen (see {@link SEAT_GLYPH_MIN_PX}); the ring is the
|
|
1476
|
+
* smaller-zoom fallback. Kept in a map so zoom toggles touch only the handful
|
|
1477
|
+
* of accessible seats, never all 13k nodes. */
|
|
1478
|
+
this.accessGlyphById = /* @__PURE__ */ new Map();
|
|
1479
|
+
/** Whether the accessibility glyph is legible at the current camera scale. */
|
|
1480
|
+
this.accessGlyphVisible = false;
|
|
1501
1481
|
/** Authored free-text nodes obey the same rendered-size visibility floor. */
|
|
1502
1482
|
this.freeTextById = /* @__PURE__ */ new Map();
|
|
1503
1483
|
/** Stage/rink landmarks retain a readable screen-space caption at overview. */
|
|
@@ -1810,6 +1790,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1810
1790
|
this.boothDims.clear();
|
|
1811
1791
|
this.boothLabelById.clear();
|
|
1812
1792
|
this.seatLabelById.clear();
|
|
1793
|
+
this.accessRingById.clear();
|
|
1794
|
+
this.accessGlyphById.clear();
|
|
1813
1795
|
this.freeTextById.clear();
|
|
1814
1796
|
this.primaryFocalLabels.clear();
|
|
1815
1797
|
this.gaById.clear();
|
|
@@ -2718,18 +2700,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2718
2700
|
this.paintSeat(c, seat.id);
|
|
2719
2701
|
target.add(c);
|
|
2720
2702
|
if (seat.accessible) {
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
);
|
|
2703
|
+
const ring = new import_Circle.Circle({
|
|
2704
|
+
x: seat.x,
|
|
2705
|
+
y: seat.y,
|
|
2706
|
+
radius: this.seatR + 1.5,
|
|
2707
|
+
stroke: accessibilityRingColor(seat.accessibility),
|
|
2708
|
+
strokeWidth: 2.5,
|
|
2709
|
+
listening: false,
|
|
2710
|
+
perfectDrawEnabled: false,
|
|
2711
|
+
shadowForStrokeEnabled: false
|
|
2712
|
+
});
|
|
2713
|
+
this.accessRingById.set(seat.id, ring);
|
|
2714
|
+
target.add(ring);
|
|
2715
|
+
const glyph = this.buildAccessGlyph(seat, typeof c.fill() === "string" ? c.fill() : "");
|
|
2716
|
+
this.accessGlyphById.set(seat.id, glyph);
|
|
2717
|
+
target.add(glyph);
|
|
2733
2718
|
}
|
|
2734
2719
|
}
|
|
2735
2720
|
}
|
|
@@ -2773,6 +2758,53 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2773
2758
|
target.add(t2);
|
|
2774
2759
|
this.paintSeat(rect, seat.id);
|
|
2775
2760
|
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Build the centred accessibility glyph for a seat. Sized relative to the seat
|
|
2763
|
+
* radius (so it always fills the marker at any zoom) and given a contrast-aware
|
|
2764
|
+
* fill against the seat's paint — recomputed per state in {@link paintSeat}.
|
|
2765
|
+
*/
|
|
2766
|
+
buildAccessGlyph(seat, seatFill) {
|
|
2767
|
+
const k = this.seatR * 1.5 / ACCESS_GLYPH_VIEWBOX;
|
|
2768
|
+
return new import_Path.Path({
|
|
2769
|
+
x: seat.x,
|
|
2770
|
+
y: seat.y,
|
|
2771
|
+
data: ACCESS_GLYPH_PATH,
|
|
2772
|
+
offsetX: ACCESS_GLYPH_VIEWBOX / 2,
|
|
2773
|
+
offsetY: ACCESS_GLYPH_VIEWBOX / 2,
|
|
2774
|
+
scaleX: k,
|
|
2775
|
+
scaleY: k,
|
|
2776
|
+
fill: stateAwareBookableLabelInk(seatFill, "#ffffff"),
|
|
2777
|
+
listening: false,
|
|
2778
|
+
visible: this.accessGlyphVisible,
|
|
2779
|
+
perfectDrawEnabled: false,
|
|
2780
|
+
shadowForStrokeEnabled: false
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Toggle the accessibility glyphs for the current camera scale: shown once the
|
|
2785
|
+
* effective on-screen seat radius clears {@link SEAT_GLYPH_MIN_PX}, otherwise
|
|
2786
|
+
* hidden so only the ring remains. Iterates the (small) accessible-seat set,
|
|
2787
|
+
* never the full node graph, so it is cheap to call on every view change.
|
|
2788
|
+
*/
|
|
2789
|
+
updateAccessGlyphs(scale) {
|
|
2790
|
+
if (!this.accessGlyphById.size) return;
|
|
2791
|
+
this.accessGlyphVisible = this.seatR * scale >= SEAT_GLYPH_MIN_PX;
|
|
2792
|
+
for (const [id, glyph] of this.accessGlyphById) {
|
|
2793
|
+
glyph.visible(this.accessGlyphVisible && this.accessGlyphEligible(id));
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Whether an accessible seat's glyph should show for its current status. It is
|
|
2798
|
+
* hidden on seats a buyer cannot take (sold, or another buyer's hold) where the
|
|
2799
|
+
* overlay status cue (diagonal mark / lock) carries the state instead — mirrors
|
|
2800
|
+
* the `unavailable` test in {@link updateLabels} so the two never collide.
|
|
2801
|
+
*/
|
|
2802
|
+
accessGlyphEligible(id) {
|
|
2803
|
+
const status = this.statusById.get(id) ?? "free";
|
|
2804
|
+
if (status === "booked") return false;
|
|
2805
|
+
if (status === "held" && !this.ownedHold.has(id) && !this.opts.manageMode) return false;
|
|
2806
|
+
return true;
|
|
2807
|
+
}
|
|
2776
2808
|
/** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
|
|
2777
2809
|
seatBaseColor(categoryKey) {
|
|
2778
2810
|
if (!this.colorblind) return this.catColor.get(categoryKey) ?? "#6e7bff";
|
|
@@ -2886,6 +2918,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2886
2918
|
isBookableLabelLegibleAtScale(bookableLabel.fontSize(), this.effScale()) && c.opacity() >= 0.5
|
|
2887
2919
|
);
|
|
2888
2920
|
}
|
|
2921
|
+
const accessGlyph = this.accessGlyphById.get(id);
|
|
2922
|
+
if (accessGlyph) {
|
|
2923
|
+
const fill = c.fill();
|
|
2924
|
+
accessGlyph.fill(stateAwareBookableLabelInk(typeof fill === "string" ? fill : "", "#ffffff"));
|
|
2925
|
+
accessGlyph.opacity(c.opacity());
|
|
2926
|
+
accessGlyph.visible(this.accessGlyphVisible && this.accessGlyphEligible(id));
|
|
2927
|
+
}
|
|
2928
|
+
this.accessRingById.get(id)?.opacity(c.opacity());
|
|
2889
2929
|
}
|
|
2890
2930
|
/** True when a seat sits in a section/zone currently marked `closed`. */
|
|
2891
2931
|
seatInClosedSection(id) {
|
|
@@ -3364,7 +3404,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3364
3404
|
* Membership and category mix are still precomputed for the detailed state.
|
|
3365
3405
|
*/
|
|
3366
3406
|
renderSection(obj) {
|
|
3367
|
-
const
|
|
3407
|
+
const centroid2 = polygonLabelPoint(obj.outline, obj.holes);
|
|
3368
3408
|
const palette = overviewPalette(this.canvasBackground);
|
|
3369
3409
|
const memberIds = [];
|
|
3370
3410
|
const catCounts = /* @__PURE__ */ new Map();
|
|
@@ -3426,8 +3466,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3426
3466
|
const preferredInk = labelStyle?.color ?? palette.sectionInk;
|
|
3427
3467
|
const labelScale = (labelStyle?.size ?? 18) / 18;
|
|
3428
3468
|
const nameLabel = new import_Text.Text({
|
|
3429
|
-
x: obj.labelPresentation?.position?.x ??
|
|
3430
|
-
y: obj.labelPresentation?.position?.y ??
|
|
3469
|
+
x: obj.labelPresentation?.position?.x ?? centroid2.x,
|
|
3470
|
+
y: obj.labelPresentation?.position?.y ?? centroid2.y,
|
|
3431
3471
|
text: obj.displayLabel ?? obj.label,
|
|
3432
3472
|
rotation: obj.labelPresentation?.rotation ?? 0,
|
|
3433
3473
|
visible: obj.labelPresentation?.visible !== false,
|
|
@@ -3442,8 +3482,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3442
3482
|
nameLabel.offsetY(nameLabel.height() / 2);
|
|
3443
3483
|
bgTarget.add(nameLabel);
|
|
3444
3484
|
const subLabel = new import_Text.Text({
|
|
3445
|
-
x:
|
|
3446
|
-
y:
|
|
3485
|
+
x: centroid2.x,
|
|
3486
|
+
y: centroid2.y,
|
|
3447
3487
|
text: t("map.seatsLeft", { count: free }),
|
|
3448
3488
|
fontSize: 12,
|
|
3449
3489
|
fontStyle: "700",
|
|
@@ -3462,8 +3502,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3462
3502
|
outline: obj.outline,
|
|
3463
3503
|
...obj.outlinePath ? { outlinePath: obj.outlinePath } : {},
|
|
3464
3504
|
holes: obj.holes ?? [],
|
|
3465
|
-
centroid,
|
|
3466
|
-
labelAnchors: polygonLabelCandidates(obj.outline, obj.holes ?? [],
|
|
3505
|
+
centroid: centroid2,
|
|
3506
|
+
labelAnchors: polygonLabelCandidates(obj.outline, obj.holes ?? [], centroid2),
|
|
3467
3507
|
zone: obj.zone,
|
|
3468
3508
|
memberIds,
|
|
3469
3509
|
total: memberIds.length,
|
|
@@ -4611,6 +4651,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4611
4651
|
if (this.hasSections) this.applySectionLod(scale);
|
|
4612
4652
|
else if (this.primaryFocalLabels.size) this.bgLayer.batchDraw();
|
|
4613
4653
|
this.paintGAStateForView();
|
|
4654
|
+
this.updateAccessGlyphs(scale);
|
|
4614
4655
|
const shouldCache = scale < CACHE_THRESHOLD;
|
|
4615
4656
|
if (shouldCache && !this.cached) {
|
|
4616
4657
|
this.cacheSeatLayer();
|
|
@@ -4693,6 +4734,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4693
4734
|
for (const seat of this.seats) {
|
|
4694
4735
|
if (seat.kind === "booth") continue;
|
|
4695
4736
|
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
4737
|
+
if (seat.accessible && this.accessGlyphVisible) continue;
|
|
4696
4738
|
const shape = this.circleById.get(seat.id);
|
|
4697
4739
|
if ((shape?.opacity() ?? 1) < 0.5) continue;
|
|
4698
4740
|
const status = this.statusById.get(seat.id) ?? "free";
|
|
@@ -4823,6 +4865,103 @@ function strandedSingles(seats, statusOf, selectedIds) {
|
|
|
4823
4865
|
return out;
|
|
4824
4866
|
}
|
|
4825
4867
|
|
|
4868
|
+
// src/core/bestAvailable.ts
|
|
4869
|
+
function seatIndex2(seat, fallback) {
|
|
4870
|
+
const i = seat.id.lastIndexOf(":");
|
|
4871
|
+
if (i < 0) return fallback;
|
|
4872
|
+
const n = Number(seat.id.slice(i + 1));
|
|
4873
|
+
return Number.isFinite(n) ? n : fallback;
|
|
4874
|
+
}
|
|
4875
|
+
function dist2(a, b) {
|
|
4876
|
+
const dx = a.x - b.x;
|
|
4877
|
+
const dy = a.y - b.y;
|
|
4878
|
+
return dx * dx + dy * dy;
|
|
4879
|
+
}
|
|
4880
|
+
function centroid(seats) {
|
|
4881
|
+
let x = 0;
|
|
4882
|
+
let y = 0;
|
|
4883
|
+
for (const s of seats) {
|
|
4884
|
+
x += s.x;
|
|
4885
|
+
y += s.y;
|
|
4886
|
+
}
|
|
4887
|
+
return { x: x / seats.length, y: y / seats.length };
|
|
4888
|
+
}
|
|
4889
|
+
function isPremium(seat) {
|
|
4890
|
+
return seat.commercial?.premium === true;
|
|
4891
|
+
}
|
|
4892
|
+
function pickBestAvailable(seats, available, opts) {
|
|
4893
|
+
const qty = Math.floor(opts.qty);
|
|
4894
|
+
if (!Number.isFinite(qty) || qty <= 0) return { labels: [], reason: "sold_out" };
|
|
4895
|
+
const { categoryKey, focal, preferPremium } = opts;
|
|
4896
|
+
const rows = /* @__PURE__ */ new Map();
|
|
4897
|
+
const eligibleAll = [];
|
|
4898
|
+
seats.forEach((seat, i) => {
|
|
4899
|
+
const elig = available.has(seat.label) && (!categoryKey || seat.categoryKey === categoryKey);
|
|
4900
|
+
let arr = rows.get(seat.rowId);
|
|
4901
|
+
if (!arr) {
|
|
4902
|
+
arr = [];
|
|
4903
|
+
rows.set(seat.rowId, arr);
|
|
4904
|
+
}
|
|
4905
|
+
arr.push({ seat, index: seatIndex2(seat, i), elig });
|
|
4906
|
+
if (elig) eligibleAll.push(seat);
|
|
4907
|
+
});
|
|
4908
|
+
if (eligibleAll.length === 0) return { labels: [], reason: "sold_out" };
|
|
4909
|
+
let best = null;
|
|
4910
|
+
const better = (c) => {
|
|
4911
|
+
if (!best) return true;
|
|
4912
|
+
if (preferPremium && c.nonPremium !== best.nonPremium) return c.nonPremium < best.nonPremium;
|
|
4913
|
+
if (c.orphan !== best.orphan) return c.orphan < best.orphan;
|
|
4914
|
+
if (c.d2 !== best.d2) return c.d2 < best.d2;
|
|
4915
|
+
const r = c.rowId.localeCompare(best.rowId);
|
|
4916
|
+
if (r !== 0) return r < 0;
|
|
4917
|
+
return c.startIndex < best.startIndex;
|
|
4918
|
+
};
|
|
4919
|
+
for (const [rowId, slotsUnsorted] of rows) {
|
|
4920
|
+
const slots = [...slotsUnsorted].sort((a, b) => a.index - b.index);
|
|
4921
|
+
let i = 0;
|
|
4922
|
+
while (i < slots.length) {
|
|
4923
|
+
if (!slots[i].elig) {
|
|
4924
|
+
i++;
|
|
4925
|
+
continue;
|
|
4926
|
+
}
|
|
4927
|
+
let j = i;
|
|
4928
|
+
while (j < slots.length && slots[j].elig) j++;
|
|
4929
|
+
const segLen = j - i;
|
|
4930
|
+
for (let p = 0; p + qty <= segLen; p++) {
|
|
4931
|
+
const leftRem = p;
|
|
4932
|
+
const rightRem = segLen - (p + qty);
|
|
4933
|
+
const orphan = leftRem === 1 || rightRem === 1 ? 1 : 0;
|
|
4934
|
+
const runSeats = slots.slice(i + p, i + p + qty).map((s) => s.seat);
|
|
4935
|
+
const c = {
|
|
4936
|
+
labels: runSeats.map((s) => s.label),
|
|
4937
|
+
seats: runSeats,
|
|
4938
|
+
rowId,
|
|
4939
|
+
startIndex: slots[i + p].index,
|
|
4940
|
+
orphan,
|
|
4941
|
+
nonPremium: preferPremium ? runSeats.reduce((n, s) => n + (isPremium(s) ? 0 : 1), 0) : 0,
|
|
4942
|
+
d2: dist2(centroid(runSeats), focal)
|
|
4943
|
+
};
|
|
4944
|
+
if (better(c)) best = c;
|
|
4945
|
+
}
|
|
4946
|
+
i = j;
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
if (best) return { labels: best.labels };
|
|
4950
|
+
if (eligibleAll.length < qty) return { labels: [], reason: "not_enough_together" };
|
|
4951
|
+
const ranked = eligibleAll.map((seat, i) => ({ seat, i, d2: dist2(seat, focal) })).sort((a, b) => {
|
|
4952
|
+
if (preferPremium) {
|
|
4953
|
+
const pa = isPremium(a.seat) ? 0 : 1;
|
|
4954
|
+
const pb = isPremium(b.seat) ? 0 : 1;
|
|
4955
|
+
if (pa !== pb) return pa - pb;
|
|
4956
|
+
}
|
|
4957
|
+
if (a.d2 !== b.d2) return a.d2 - b.d2;
|
|
4958
|
+
const r = a.seat.rowId.localeCompare(b.seat.rowId);
|
|
4959
|
+
if (r !== 0) return r;
|
|
4960
|
+
return seatIndex2(a.seat, a.i) - seatIndex2(b.seat, b.i);
|
|
4961
|
+
});
|
|
4962
|
+
return { labels: ranked.slice(0, qty).map((r) => r.seat.label) };
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4826
4965
|
// src/picker/PickerController.ts
|
|
4827
4966
|
var DEFAULT_MAX_SELECTION = 10;
|
|
4828
4967
|
var MAX_BACKOFF_MS = 15e3;
|
|
@@ -4945,9 +5084,11 @@ var PickerController = class {
|
|
|
4945
5084
|
this.allIds.push(s.id);
|
|
4946
5085
|
const source = chartObjects.get(s.rowId);
|
|
4947
5086
|
const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
|
|
4948
|
-
const
|
|
4949
|
-
const
|
|
4950
|
-
const
|
|
5087
|
+
const sourceDisplayLabel = source && "displayLabel" in source && typeof source.displayLabel === "string" && source.displayLabel ? source.displayLabel : sourceLabel;
|
|
5088
|
+
const rowLabel = s.kind === "booth" ? void 0 : sourceDisplayLabel;
|
|
5089
|
+
const visibleSeatLabel = s.displayLabel ?? s.label;
|
|
5090
|
+
const labelParts = visibleSeatLabel.split("-");
|
|
5091
|
+
const seatNumber = sourceDisplayLabel && visibleSeatLabel.startsWith(`${sourceDisplayLabel}-`) ? visibleSeatLabel.slice(sourceDisplayLabel.length + 1) : s.kind === "booth" ? visibleSeatLabel : labelParts[labelParts.length - 1] ?? visibleSeatLabel;
|
|
4951
5092
|
this.seatContext.set(s.id, {
|
|
4952
5093
|
sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
|
|
4953
5094
|
rowLabel,
|
|
@@ -5174,10 +5315,77 @@ var PickerController = class {
|
|
|
5174
5315
|
this.setHold({ holdId: result.holdId, labels: combined.map((s) => s.label), expiresAt: result.expiresAt, items: result.items });
|
|
5175
5316
|
return this.hold_;
|
|
5176
5317
|
}
|
|
5177
|
-
/**
|
|
5178
|
-
|
|
5318
|
+
/** True when any seat on the visible chart carries the `premium` commercial
|
|
5319
|
+
* flag — gates the buyer widget's "★ Best seats" premium quick-pick (chip is
|
|
5320
|
+
* present-only, exactly like the accessibility filter chips). */
|
|
5321
|
+
hasPremiumSeats() {
|
|
5322
|
+
for (const seat of this.labelToSeat.values()) {
|
|
5323
|
+
if (seat.commercial?.premium) return true;
|
|
5324
|
+
}
|
|
5325
|
+
return false;
|
|
5326
|
+
}
|
|
5327
|
+
/**
|
|
5328
|
+
* Client-side premium pre-pass: find the best contiguous block of `qty`
|
|
5329
|
+
* PREMIUM-flagged free seats (orphan-avoiding, closest to the focal point)
|
|
5330
|
+
* using the local seat geometry + live availability, mirroring the server's
|
|
5331
|
+
* held-back exclusion via the visible (hidden-stripped) chart and current
|
|
5332
|
+
* seat status. Returns a FULL premium block of `qty`, or null when none
|
|
5333
|
+
* exists (the caller then falls back to the normal server pick).
|
|
5334
|
+
*/
|
|
5335
|
+
pickPremiumBlock(qty, categoryKey) {
|
|
5336
|
+
const doc = this.visibleDoc();
|
|
5337
|
+
if (!doc?.objects?.length) return null;
|
|
5338
|
+
const focal = doc.focalPoint ?? { x: 0, y: 0 };
|
|
5339
|
+
const seats = expandChart(doc);
|
|
5340
|
+
const held = new Set(this.hold_?.labels ?? []);
|
|
5341
|
+
const available = /* @__PURE__ */ new Set();
|
|
5342
|
+
for (const seat of seats) {
|
|
5343
|
+
if (held.has(seat.label)) continue;
|
|
5344
|
+
const id = this.labelToId.get(seat.label);
|
|
5345
|
+
const status = id ? this.getStatus(id) : void 0;
|
|
5346
|
+
if ((status ?? "free") === "free") available.add(seat.label);
|
|
5347
|
+
}
|
|
5348
|
+
const pick = pickBestAvailable(seats, available, { qty, categoryKey, focal, preferPremium: true });
|
|
5349
|
+
if (pick.labels.length !== qty) return null;
|
|
5350
|
+
const allPremium = pick.labels.every((l) => this.labelToSeat.get(l)?.commercial?.premium);
|
|
5351
|
+
return allPremium ? [...pick.labels] : null;
|
|
5352
|
+
}
|
|
5353
|
+
/**
|
|
5354
|
+
* Server-picks `qty` best free seats and holds them atomically. Throws on
|
|
5355
|
+
* failure. With `preferPremium`, first tries to hold the best PREMIUM block
|
|
5356
|
+
* locally; when none matches the requested quantity it falls back to the
|
|
5357
|
+
* normal server pick (the widget surfaces a subtle note on that fallback).
|
|
5358
|
+
*/
|
|
5359
|
+
async bestAvailable(qty, categoryKey, opts = {}) {
|
|
5179
5360
|
const r = this.renderer;
|
|
5180
5361
|
if (!r) return null;
|
|
5362
|
+
if (opts.preferPremium) {
|
|
5363
|
+
const block = this.pickPremiumBlock(qty, categoryKey);
|
|
5364
|
+
if (block) {
|
|
5365
|
+
if (this.hold_ && !await this.release()) return null;
|
|
5366
|
+
try {
|
|
5367
|
+
const selection = block.map((label) => {
|
|
5368
|
+
const seat = this.labelToSeat.get(label);
|
|
5369
|
+
const resolved = seat ? this.toSeat(seat) : null;
|
|
5370
|
+
return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
|
|
5371
|
+
});
|
|
5372
|
+
const result = await this.api.hold(this.key, selection);
|
|
5373
|
+
r.clearSelection();
|
|
5374
|
+
const ids = block.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
5375
|
+
if (ids.length) r.setStatus(ids, "held");
|
|
5376
|
+
this.setHold({ holdId: result.holdId, labels: [...block], expiresAt: result.expiresAt, items: result.items });
|
|
5377
|
+
const seats = block.map((l) => this.labelToSeat.get(l)).filter((s) => !!s).map((s) => this.toSeat(s));
|
|
5378
|
+
this.opts.onSelectionChange?.(seats);
|
|
5379
|
+
return this.hold_;
|
|
5380
|
+
} catch (err) {
|
|
5381
|
+
const { status, reason } = errInfo(err);
|
|
5382
|
+
if (status === 409 && reason === "event_closed") {
|
|
5383
|
+
this.opts.onSalesClosed?.();
|
|
5384
|
+
throw err;
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5181
5389
|
if (this.hold_ && !await this.release()) return null;
|
|
5182
5390
|
try {
|
|
5183
5391
|
const result = await this.api.bestAvailable(this.key, qty, categoryKey);
|
|
@@ -5550,14 +5758,16 @@ var PickerController = class {
|
|
|
5550
5758
|
// ---- internals ------------------------------------------------------------
|
|
5551
5759
|
toSeat(s) {
|
|
5552
5760
|
const commercial = s.commercial ? { commercial: s.commercial } : void 0;
|
|
5761
|
+
const display = s.displayLabel ? { displayLabel: s.displayLabel } : void 0;
|
|
5553
5762
|
const tiers = this.tiersFor(s.categoryKey);
|
|
5554
5763
|
if (!tiers) {
|
|
5555
|
-
return { id: s.id, label: s.label, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial };
|
|
5764
|
+
return { id: s.id, label: s.label, ...display, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial };
|
|
5556
5765
|
}
|
|
5557
5766
|
const chosen = tiers.find((t2) => t2.id === this.seatTiers.get(s.id)) ?? tiers[0];
|
|
5558
5767
|
return {
|
|
5559
5768
|
id: s.id,
|
|
5560
5769
|
label: s.label,
|
|
5770
|
+
...display,
|
|
5561
5771
|
categoryKey: s.categoryKey,
|
|
5562
5772
|
price: chosen.price,
|
|
5563
5773
|
tiers,
|
|
@@ -6118,6 +6328,8 @@ async function loadLocale(code) {
|
|
|
6118
6328
|
pointInPolygonWithHoles,
|
|
6119
6329
|
polygonLabelPoint,
|
|
6120
6330
|
resolveLocale,
|
|
6331
|
+
rowSeatPositions,
|
|
6332
|
+
seatLabelPart,
|
|
6121
6333
|
setLocale,
|
|
6122
6334
|
setMoneyLocale,
|
|
6123
6335
|
setStringOverrides,
|