@seatlayer/core 0.23.0 → 0.25.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-DRWMZ2FV.js → de-73TIXYJH.js} +3 -34
- package/dist/de-73TIXYJH.js.map +1 -0
- package/dist/{es-N73CIVTC.js → es-SKAODLTR.js} +3 -34
- package/dist/es-SKAODLTR.js.map +1 -0
- package/dist/{fr-SOY2OB4P.js → fr-J4637T6V.js} +3 -34
- package/dist/fr-J4637T6V.js.map +1 -0
- package/dist/index.cjs +674 -173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +232 -3
- package/dist/index.d.ts +232 -3
- package/dist/index.js +667 -77
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/de-DRWMZ2FV.js.map +0 -1
- package/dist/es-N73CIVTC.js.map +0 -1
- package/dist/fr-SOY2OB4P.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",
|
|
@@ -77,32 +65,13 @@ var init_es = __esm({
|
|
|
77
65
|
"picker.seatsLeftInSection.one": "{count} asiento disponible",
|
|
78
66
|
"picker.seatsLeftInSection.other": "{count} asientos disponibles",
|
|
79
67
|
"picker.overview": "Descripci\xF3n general",
|
|
68
|
+
"picker.entrance": "Entrada",
|
|
80
69
|
"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
70
|
"picker.ticketTierFor": "Categor\xEDa de entrada para {label}",
|
|
94
|
-
"picker.total": "Total: {amount}",
|
|
95
71
|
"picker.viewFromSeat": "Vista desde el asiento {label}",
|
|
96
72
|
"picker.real360": "REAL 360\xB0",
|
|
97
73
|
"picker.preview": "VISTA PREVIA",
|
|
98
|
-
"picker.open360": "Abrir vista de 360\xB0",
|
|
99
74
|
"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
75
|
"picker.panorama360": "Foto de 360\xB0 del lugar",
|
|
107
76
|
"picker.illustrationCaption": "ilustraci\xF3n \xB7 \u2248 {m} m del escenario",
|
|
108
77
|
"picker.restrictedView": "Visibilidad restringida",
|
|
@@ -131,29 +100,17 @@ var init_de = __esm({
|
|
|
131
100
|
"common.done": "Fertig",
|
|
132
101
|
"common.copied": "\u2713 Kopiert",
|
|
133
102
|
// buyer picker (extraction in progress — P4 of the port plan)
|
|
134
|
-
"picker.holdSeats": "Pl\xE4tze reservieren & Kasse",
|
|
135
|
-
"picker.completeBooking": "Buchung abschlie\xDFen",
|
|
136
|
-
"picker.seatsHeld": "Pl\xE4tze reserviert \u2014 {time}",
|
|
137
103
|
"picker.holdExpired": "Ihre Reservierung ist abgelaufen \u2014 die Pl\xE4tze wurden freigegeben. Bitte erneut ausw\xE4hlen.",
|
|
138
|
-
"picker.seatTaken": "Platz {label} wurde gerade von einem anderen K\xE4ufer gebucht.",
|
|
139
104
|
"picker.poweredBy": "Bereitgestellt von SeatLayer",
|
|
140
105
|
"picker.testMode": "TESTMODUS",
|
|
141
|
-
"picker.colorblind": "Farbenblind-freundliche Farben",
|
|
142
106
|
"picker.orphanHint": "Dadurch bleibt ein einzelner Platz \xFCbrig \u2014 r\xFCcken Sie ggf. einen Platz weiter.",
|
|
143
|
-
"picker.seats.one": "{count} Platz",
|
|
144
|
-
"picker.seats.other": "{count} Pl\xE4tze",
|
|
145
107
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
146
108
|
"map.aria": "Sitzplan. Mit Pfeiltasten zwischen Pl\xE4tzen navigieren, Eingabe zum Ausw\xE4hlen.",
|
|
147
109
|
"map.seatsLeft": "{count} FREI",
|
|
148
110
|
"map.statusHeld": "Reserviert",
|
|
149
111
|
"map.statusTaken": "Vergeben",
|
|
150
112
|
"map.fromPrice": "AB {price}",
|
|
151
|
-
// buyer picker
|
|
152
|
-
"picker.language": "Sprache",
|
|
153
|
-
"picker.zoomToFit": "Auf Gr\xF6\xDFe anpassen",
|
|
154
|
-
"picker.seatCountLabel": "Pl\xE4tze",
|
|
155
|
-
"picker.capacity": "Kapazit\xE4t",
|
|
156
|
-
"picker.viewMode": "Ansichtsmodus",
|
|
113
|
+
// buyer picker widget (src/picker/widget/SeatPicker.ts)
|
|
157
114
|
"picker.floor": "Etage",
|
|
158
115
|
"picker.zoomLevel": "Zoomstufe",
|
|
159
116
|
"picker.rungTip.zones": "\xDCbersicht \u2014 Bereichsgruppen wie Nordtrib\xFCne oder VIP",
|
|
@@ -167,32 +124,13 @@ var init_de = __esm({
|
|
|
167
124
|
"picker.seatsLeftInSection.one": "{count} Platz verf\xFCgbar",
|
|
168
125
|
"picker.seatsLeftInSection.other": "{count} Pl\xE4tze verf\xFCgbar",
|
|
169
126
|
"picker.overview": "\xDCbersicht",
|
|
127
|
+
"picker.entrance": "Eingang",
|
|
170
128
|
"picker.tapSeatHint": "Tippen Sie auf einen Platz, um die Ansicht zu pr\xFCfen",
|
|
171
|
-
"picker.chartSize": "Kartengr\xF6\xDFe",
|
|
172
|
-
"picker.custom": "Benutzerdefiniert",
|
|
173
|
-
"picker.categories": "Kategorien",
|
|
174
|
-
"picker.accessibility": "Barrierefreiheit",
|
|
175
|
-
"picker.showAnyAccessible": "Jeden barrierefreien Platz anzeigen",
|
|
176
|
-
"picker.any": "Beliebig",
|
|
177
|
-
"picker.yourSeats": "Ihre Pl\xE4tze",
|
|
178
|
-
"picker.emptySeats": "Tippen Sie auf die Pl\xE4tze auf der Karte",
|
|
179
|
-
"picker.emptySeatsWithGa": "Tippen Sie auf die Pl\xE4tze auf der Karte \xB7 tippen Sie auf einen Stehbereich f\xFCr Stehplatztickets",
|
|
180
|
-
"picker.oneFewer": "Einen weniger",
|
|
181
|
-
"picker.oneMore": "Einen mehr",
|
|
182
|
-
"picker.remove": "{label} entfernen",
|
|
183
129
|
"picker.ticketTierFor": "Ticketklasse f\xFCr {label}",
|
|
184
|
-
"picker.total": "Gesamt: {amount}",
|
|
185
130
|
"picker.viewFromSeat": "Ansicht von Platz {label}",
|
|
186
131
|
"picker.real360": "REAL 360\xB0",
|
|
187
132
|
"picker.preview": "VORSCHAU",
|
|
188
|
-
"picker.open360": "\xD6ffnen Sie die 360\xB0-Ansicht",
|
|
189
133
|
"picker.sightline": "\u2248 {m} m zur B\xFChne \xB7 freie Sicht",
|
|
190
|
-
"picker.bookedDemo": "Gebucht! (Demo)",
|
|
191
|
-
"picker.bookButton.one": "{count} Ticket buchen \u2014 {amount}",
|
|
192
|
-
"picker.bookButton.other": "{count} Tickets buchen \u2014 {amount}",
|
|
193
|
-
"picker.simulateCrowd": "Menschenmenge simulieren: {state}",
|
|
194
|
-
"picker.on": "AN",
|
|
195
|
-
"picker.off": "AUS",
|
|
196
134
|
"picker.panorama360": "360\xB0-Veranstaltungsfotos",
|
|
197
135
|
"picker.illustrationCaption": "Illustration \xB7 \u2248 {m} m von der B\xFChne",
|
|
198
136
|
"picker.restrictedView": "Eingeschr\xE4nkte Sicht",
|
|
@@ -221,29 +159,17 @@ var init_fr = __esm({
|
|
|
221
159
|
"common.done": "Termin\xE9",
|
|
222
160
|
"common.copied": "\u2713 Copi\xE9",
|
|
223
161
|
// buyer picker (extraction in progress — P4 of the port plan)
|
|
224
|
-
"picker.holdSeats": "R\xE9server les si\xE8ges et valider",
|
|
225
|
-
"picker.completeBooking": "Terminer la r\xE9servation",
|
|
226
|
-
"picker.seatsHeld": "Si\xE8ges r\xE9serv\xE9s \u2014 {time}",
|
|
227
162
|
"picker.holdExpired": "Votre r\xE9servation a expir\xE9 \u2014 les si\xE8ges ont \xE9t\xE9 lib\xE9r\xE9s. S\xE9lectionnez \xE0 nouveau.",
|
|
228
|
-
"picker.seatTaken": "Le si\xE8ge {label} vient d'\xEAtre r\xE9serv\xE9 par un autre acheteur.",
|
|
229
163
|
"picker.poweredBy": "Propuls\xE9 par SeatLayer",
|
|
230
164
|
"picker.testMode": "MODE TEST",
|
|
231
|
-
"picker.colorblind": "Couleurs adapt\xE9es au daltonisme",
|
|
232
165
|
"picker.orphanHint": "Cela laisse un si\xE8ge isol\xE9 \u2014 pensez \xE0 vous d\xE9caler d'un si\xE8ge.",
|
|
233
|
-
"picker.seats.one": "{count} si\xE8ge",
|
|
234
|
-
"picker.seats.other": "{count} si\xE8ges",
|
|
235
166
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
236
167
|
"map.aria": "Plan de salle. Utilisez les fl\xE8ches pour naviguer entre les si\xE8ges, Entr\xE9e pour s\xE9lectionner.",
|
|
237
168
|
"map.seatsLeft": "{count} LIBRES",
|
|
238
169
|
"map.statusHeld": "En attente",
|
|
239
170
|
"map.statusTaken": "Pris",
|
|
240
171
|
"map.fromPrice": "D\xC8S {price}",
|
|
241
|
-
// buyer picker
|
|
242
|
-
"picker.language": "Langue",
|
|
243
|
-
"picker.zoomToFit": "Ajuster le zoom",
|
|
244
|
-
"picker.seatCountLabel": "si\xE8ges",
|
|
245
|
-
"picker.capacity": "capacit\xE9",
|
|
246
|
-
"picker.viewMode": "Mode d'affichage",
|
|
172
|
+
// buyer picker widget (src/picker/widget/SeatPicker.ts)
|
|
247
173
|
"picker.floor": "\xC9tage",
|
|
248
174
|
"picker.zoomLevel": "Niveau de zoom",
|
|
249
175
|
"picker.rungTip.zones": "Vue g\xE9n\xE9rale \u2014 groupes de sections comme Tribune nord ou VIP",
|
|
@@ -257,32 +183,13 @@ var init_fr = __esm({
|
|
|
257
183
|
"picker.seatsLeftInSection.one": "{count} si\xE8ge restant",
|
|
258
184
|
"picker.seatsLeftInSection.other": "{count} si\xE8ges restants",
|
|
259
185
|
"picker.overview": "Vue d'ensemble",
|
|
186
|
+
"picker.entrance": "Entr\xE9e",
|
|
260
187
|
"picker.tapSeatHint": "Appuyez sur un si\xE8ge pour v\xE9rifier sa vue",
|
|
261
|
-
"picker.chartSize": "Taille du plan",
|
|
262
|
-
"picker.custom": "Personnalis\xE9",
|
|
263
|
-
"picker.categories": "Cat\xE9gories",
|
|
264
|
-
"picker.accessibility": "Accessibilit\xE9",
|
|
265
|
-
"picker.showAnyAccessible": "Afficher un si\xE8ge accessible",
|
|
266
|
-
"picker.any": "N'importe quel",
|
|
267
|
-
"picker.yourSeats": "Vos si\xE8ges",
|
|
268
|
-
"picker.emptySeats": "Appuyez sur les si\xE8ges sur le plan",
|
|
269
|
-
"picker.emptySeatsWithGa": "Appuyez sur les si\xE8ges sur le plan \xB7 appuyez sur une zone debout pour les billets GA",
|
|
270
|
-
"picker.oneFewer": "Un de moins",
|
|
271
|
-
"picker.oneMore": "Un de plus",
|
|
272
|
-
"picker.remove": "Supprimer {label}",
|
|
273
188
|
"picker.ticketTierFor": "Cat\xE9gorie de billet pour {label}",
|
|
274
|
-
"picker.total": "Total : {amount}",
|
|
275
189
|
"picker.viewFromSeat": "Vue depuis le si\xE8ge {label}",
|
|
276
190
|
"picker.real360": "VRAI 360\xB0",
|
|
277
191
|
"picker.preview": "APER\xC7U",
|
|
278
|
-
"picker.open360": "Ouvrir la vue \xE0 360\xB0",
|
|
279
192
|
"picker.sightline": "\u2248 {m} m de la sc\xE8ne \xB7 vue d\xE9gag\xE9e",
|
|
280
|
-
"picker.bookedDemo": "R\xE9serv\xE9 ! (d\xE9mo)",
|
|
281
|
-
"picker.bookButton.one": "R\xE9server {count} billet \u2014 {amount}",
|
|
282
|
-
"picker.bookButton.other": "R\xE9server {count} billets \u2014 {amount}",
|
|
283
|
-
"picker.simulateCrowd": "Simuler la foule : {state}",
|
|
284
|
-
"picker.on": "ACTIV\xC9",
|
|
285
|
-
"picker.off": "D\xC9SACTIV\xC9",
|
|
286
193
|
"picker.panorama360": "Photo panoramique 360\xB0 du lieu",
|
|
287
194
|
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m de la sc\xE8ne",
|
|
288
195
|
"picker.restrictedView": "Visibilit\xE9 r\xE9duite",
|
|
@@ -307,6 +214,7 @@ __export(index_exports, {
|
|
|
307
214
|
MAX_EVENT_INVENTORY: () => MAX_EVENT_INVENTORY,
|
|
308
215
|
MAX_GA_CAPACITY: () => MAX_GA_CAPACITY,
|
|
309
216
|
PickerController: () => PickerController,
|
|
217
|
+
RENDERED_QUALITY_REPORT_VERSION: () => RENDERED_QUALITY_REPORT_VERSION,
|
|
310
218
|
SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
|
|
311
219
|
SeatmapRenderer: () => SeatmapRenderer,
|
|
312
220
|
UNGROUPED_ID: () => UNGROUPED_ID,
|
|
@@ -334,6 +242,7 @@ __export(index_exports, {
|
|
|
334
242
|
generateSeatThumb: () => generateSeatThumb,
|
|
335
243
|
getLocale: () => getLocale,
|
|
336
244
|
hiddenObjectIds: () => hiddenObjectIds,
|
|
245
|
+
inspectRenderedQualityEvidence: () => inspectRenderedQualityEvidence,
|
|
337
246
|
isGaUnitLabel: () => isGaUnitLabel,
|
|
338
247
|
isSectionHidden: () => isSectionHidden,
|
|
339
248
|
layerOf: () => layerOf,
|
|
@@ -343,6 +252,8 @@ __export(index_exports, {
|
|
|
343
252
|
pointInPolygonWithHoles: () => pointInPolygonWithHoles,
|
|
344
253
|
polygonLabelPoint: () => polygonLabelPoint,
|
|
345
254
|
resolveLocale: () => resolveLocale,
|
|
255
|
+
rowSeatPositions: () => rowSeatPositions,
|
|
256
|
+
seatLabelPart: () => seatLabelPart,
|
|
346
257
|
setLocale: () => setLocale,
|
|
347
258
|
setMoneyLocale: () => setMoneyLocale,
|
|
348
259
|
setStringOverrides: () => setStringOverrides,
|
|
@@ -465,6 +376,53 @@ function transformSectionOutlinePath(path, transform, radiusScale = 1, reflected
|
|
|
465
376
|
};
|
|
466
377
|
}
|
|
467
378
|
|
|
379
|
+
// src/core/labeling.ts
|
|
380
|
+
var FULL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
381
|
+
var LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
382
|
+
function toBijectiveBase(index, alphabet) {
|
|
383
|
+
const base = alphabet.length;
|
|
384
|
+
let n = index + 1;
|
|
385
|
+
let out = "";
|
|
386
|
+
while (n > 0) {
|
|
387
|
+
n -= 1;
|
|
388
|
+
const rem = n % base;
|
|
389
|
+
out = alphabet[rem] + out;
|
|
390
|
+
n = Math.floor(n / base);
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
function toLetters(value, lower = false) {
|
|
395
|
+
const alphabet = lower ? LOWER_ALPHABET : FULL_ALPHABET;
|
|
396
|
+
return toBijectiveBase(Math.max(0, Math.floor(value) - 1), alphabet);
|
|
397
|
+
}
|
|
398
|
+
var ROMAN_TABLE = [
|
|
399
|
+
[1e3, "M"],
|
|
400
|
+
[900, "CM"],
|
|
401
|
+
[500, "D"],
|
|
402
|
+
[400, "CD"],
|
|
403
|
+
[100, "C"],
|
|
404
|
+
[90, "XC"],
|
|
405
|
+
[50, "L"],
|
|
406
|
+
[40, "XL"],
|
|
407
|
+
[10, "X"],
|
|
408
|
+
[9, "IX"],
|
|
409
|
+
[5, "V"],
|
|
410
|
+
[4, "IV"],
|
|
411
|
+
[1, "I"]
|
|
412
|
+
];
|
|
413
|
+
function toRoman(value) {
|
|
414
|
+
if (!Number.isFinite(value) || value <= 0) return String(value);
|
|
415
|
+
let remaining = Math.floor(value);
|
|
416
|
+
let out = "";
|
|
417
|
+
for (const [n, sym] of ROMAN_TABLE) {
|
|
418
|
+
while (remaining >= n) {
|
|
419
|
+
out += sym;
|
|
420
|
+
remaining -= n;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
|
|
468
426
|
// src/core/layout.ts
|
|
469
427
|
function overrideAccessibility(o) {
|
|
470
428
|
if (!o) return [];
|
|
@@ -510,24 +468,62 @@ function overrideMap(row) {
|
|
|
510
468
|
if (row.overrides) for (const o of row.overrides) m.set(o.index, o);
|
|
511
469
|
return m;
|
|
512
470
|
}
|
|
513
|
-
function
|
|
514
|
-
const
|
|
471
|
+
function centerRank(n) {
|
|
472
|
+
const rank = new Array(n);
|
|
473
|
+
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);
|
|
474
|
+
return rank;
|
|
475
|
+
}
|
|
476
|
+
function seatLabelPart(row, i) {
|
|
477
|
+
const rawStart = row.seatLabelStart ?? 1;
|
|
515
478
|
const dir = row.seatNumbering?.direction ?? "ltr";
|
|
516
479
|
const step = row.seatNumbering?.step ?? 1;
|
|
480
|
+
const scheme = row.seatNumbering?.scheme ?? "decimal";
|
|
481
|
+
const prefix = row.seatNumbering?.prefix ?? "";
|
|
482
|
+
const endAt = row.seatNumbering?.endAt;
|
|
517
483
|
const n = row.seatCount;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
484
|
+
if (scheme === "updown") {
|
|
485
|
+
const half = Math.ceil(n / 2);
|
|
486
|
+
const core2 = i < half ? rawStart + 2 * i : rawStart - 1 + 2 * (n - i);
|
|
487
|
+
return `${prefix}${core2}`;
|
|
488
|
+
}
|
|
489
|
+
const effStep = scheme === "odd" || scheme === "even" ? 2 : step;
|
|
490
|
+
const start = endAt != null && Number.isFinite(endAt) ? endAt - (n - 1) * effStep : rawStart;
|
|
491
|
+
const p = dir === "center" ? centerRank(n)[i] : dir === "rtl" ? n - 1 - i : i;
|
|
492
|
+
let core;
|
|
493
|
+
switch (scheme) {
|
|
494
|
+
case "odd": {
|
|
495
|
+
const firstOdd = start % 2 === 1 ? start : start + 1;
|
|
496
|
+
core = String(firstOdd + p * 2);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case "even": {
|
|
500
|
+
const firstEven = start % 2 === 0 ? start : start + 1;
|
|
501
|
+
core = String(firstEven + p * 2);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
case "roman":
|
|
505
|
+
core = toRoman(start + p * step);
|
|
506
|
+
break;
|
|
507
|
+
case "letters-upper":
|
|
508
|
+
core = toLetters(start + p * step, false);
|
|
509
|
+
break;
|
|
510
|
+
case "letters-lower":
|
|
511
|
+
core = toLetters(start + p * step, true);
|
|
512
|
+
break;
|
|
513
|
+
case "decimal":
|
|
514
|
+
default:
|
|
515
|
+
core = String(start + p * step);
|
|
516
|
+
break;
|
|
525
517
|
}
|
|
518
|
+
return `${prefix}${core}`;
|
|
519
|
+
}
|
|
520
|
+
function expandRowSlots(row) {
|
|
526
521
|
const ov = overrideMap(row);
|
|
527
522
|
return rowSeatPositions(row).map((p, i) => {
|
|
528
523
|
const o = ov.get(i);
|
|
529
524
|
const accessibility = overrideAccessibility(o);
|
|
530
|
-
const
|
|
525
|
+
const part = seatLabelPart(row, i);
|
|
526
|
+
const inventoryLabel = o?.label ?? `${row.label}-${part}`;
|
|
531
527
|
const displayPrefix = row.displayLabel ?? row.label;
|
|
532
528
|
const commercial = { ...row.commercial, ...o?.commercial };
|
|
533
529
|
return {
|
|
@@ -535,7 +531,7 @@ function expandRowSlots(row) {
|
|
|
535
531
|
x: p.x + (o?.dx ?? 0),
|
|
536
532
|
y: p.y + (o?.dy ?? 0),
|
|
537
533
|
label: inventoryLabel,
|
|
538
|
-
displayLabel: o?.displayLabel ?? `${displayPrefix}-${
|
|
534
|
+
displayLabel: o?.displayLabel ?? `${displayPrefix}-${part}`,
|
|
539
535
|
categoryKey: o?.categoryKey ?? row.categoryKey,
|
|
540
536
|
skipped: !!o?.skip,
|
|
541
537
|
accessible: accessibility.length > 0,
|
|
@@ -992,19 +988,6 @@ function applyHidden(doc, hidden) {
|
|
|
992
988
|
return { ...doc, objects };
|
|
993
989
|
}
|
|
994
990
|
|
|
995
|
-
// src/engine/SeatmapRenderer.ts
|
|
996
|
-
var import_Core = require("konva/lib/Core");
|
|
997
|
-
var import_Stage = require("konva/lib/Stage");
|
|
998
|
-
var import_Layer = require("konva/lib/Layer");
|
|
999
|
-
var import_Group = require("konva/lib/Group");
|
|
1000
|
-
var import_Circle = require("konva/lib/shapes/Circle");
|
|
1001
|
-
var import_Rect = require("konva/lib/shapes/Rect");
|
|
1002
|
-
var import_Ellipse = require("konva/lib/shapes/Ellipse");
|
|
1003
|
-
var import_Line = require("konva/lib/shapes/Line");
|
|
1004
|
-
var import_Text = require("konva/lib/shapes/Text");
|
|
1005
|
-
var import_Image = require("konva/lib/shapes/Image");
|
|
1006
|
-
var import_Shape = require("konva/lib/Shape");
|
|
1007
|
-
|
|
1008
991
|
// src/core/chartRenderRules.ts
|
|
1009
992
|
var SEAT_LABEL_FONT_SIZE = 7;
|
|
1010
993
|
var BOOTH_LABEL_FONT_SIZE = 10;
|
|
@@ -1018,6 +1001,8 @@ var LIGHT_BOOKABLE_LABEL_INK = "#ffffff";
|
|
|
1018
1001
|
function isBookableLabelLegibleAtScale(fontSize, effectiveScale) {
|
|
1019
1002
|
return fontSize * effectiveScale >= MIN_VISIBLE_BOOKABLE_LABEL_PX;
|
|
1020
1003
|
}
|
|
1004
|
+
var ACCESS_GLYPH_VIEWBOX = 24;
|
|
1005
|
+
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";
|
|
1021
1006
|
function bookableMarkerLabel(publicLabel) {
|
|
1022
1007
|
return /-(\d{1,5})$/.exec(publicLabel)?.[1] ?? publicLabel;
|
|
1023
1008
|
}
|
|
@@ -1059,6 +1044,470 @@ function stateAwareBookableLabelInk(fill, preferred) {
|
|
|
1059
1044
|
return darkContrast >= lightContrast ? DARK_BOOKABLE_LABEL_INK : LIGHT_BOOKABLE_LABEL_INK;
|
|
1060
1045
|
}
|
|
1061
1046
|
|
|
1047
|
+
// src/core/renderedQuality.ts
|
|
1048
|
+
var RENDERED_QUALITY_REPORT_VERSION = 3;
|
|
1049
|
+
var MIN_TEXT_CONTRAST = 4.5;
|
|
1050
|
+
var MIN_GRAPHICAL_CONTRAST = 3;
|
|
1051
|
+
var MIN_POINTER_TARGET_PX = 24;
|
|
1052
|
+
var MAX_SAMPLES_PER_FINDING = 20;
|
|
1053
|
+
function visibleWithBox(items) {
|
|
1054
|
+
return items.filter((item) => item.visible && Boolean(item.screenBox));
|
|
1055
|
+
}
|
|
1056
|
+
function intersects(left, right) {
|
|
1057
|
+
return left.x < right.x + right.width && left.x + left.width > right.x && left.y < right.y + right.height && left.y + left.height > right.y;
|
|
1058
|
+
}
|
|
1059
|
+
function minimum(values) {
|
|
1060
|
+
return values.length ? Math.round(Math.min(...values) * 100) / 100 : null;
|
|
1061
|
+
}
|
|
1062
|
+
function finding(code, message, samples) {
|
|
1063
|
+
if (!samples.length) return null;
|
|
1064
|
+
return {
|
|
1065
|
+
code,
|
|
1066
|
+
message,
|
|
1067
|
+
count: samples.length,
|
|
1068
|
+
samples: samples.slice(0, MAX_SAMPLES_PER_FINDING)
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function labelSample(label, measured, minimumValue) {
|
|
1072
|
+
return {
|
|
1073
|
+
primaryId: label.seatId,
|
|
1074
|
+
primaryLabel: label.label,
|
|
1075
|
+
...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
|
|
1076
|
+
...minimumValue == null ? {} : { minimum: minimumValue }
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
function hierarchySample(label, measured, minimumValue) {
|
|
1080
|
+
return {
|
|
1081
|
+
primaryId: label.id,
|
|
1082
|
+
primaryLabel: label.label,
|
|
1083
|
+
...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
|
|
1084
|
+
...minimumValue == null ? {} : { minimum: minimumValue }
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function freeTextSample(label, measured, minimumValue) {
|
|
1088
|
+
return {
|
|
1089
|
+
primaryId: label.objectId,
|
|
1090
|
+
primaryLabel: label.text,
|
|
1091
|
+
...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
|
|
1092
|
+
...minimumValue == null ? {} : { minimum: minimumValue }
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function collisionSample(primaryId, primaryLabel, secondaryId, secondaryLabel) {
|
|
1096
|
+
return { primaryId, primaryLabel, secondaryId, secondaryLabel };
|
|
1097
|
+
}
|
|
1098
|
+
function inspectRenderedQualityEvidence(evidence, state = "overview", targetCategoryKey = null) {
|
|
1099
|
+
const bookable = visibleWithBox(evidence.labels);
|
|
1100
|
+
const hierarchy = visibleWithBox(evidence.hierarchyLabels);
|
|
1101
|
+
const freeText = visibleWithBox(evidence.freeTextLabels);
|
|
1102
|
+
const visibleGA = evidence.gaAreas.filter((area) => area.visible);
|
|
1103
|
+
const bookableContrast = bookable.map((label) => renderedTextContrast(label.ink, label.fill) ?? 0);
|
|
1104
|
+
const hierarchyContrast = hierarchy.map((label) => renderedTextContrast(label.ink, label.fill) ?? 0);
|
|
1105
|
+
const freeTextContrast = freeText.map((label) => renderedTextContrast(label.ink, label.background) ?? 0);
|
|
1106
|
+
const gaContrast = visibleGA.map((area) => renderedTextContrast(area.effectiveBackground, evidence.canvasBackground) ?? 0);
|
|
1107
|
+
const targetLabels = targetCategoryKey ? evidence.labels.filter((label) => label.categoryKey === targetCategoryKey) : evidence.labels;
|
|
1108
|
+
const targetGAAreas = targetCategoryKey ? evidence.gaAreas.filter((area) => area.categoryKey === targetCategoryKey) : evidence.gaAreas;
|
|
1109
|
+
const selected = targetLabels.filter((label) => label.selected);
|
|
1110
|
+
const held = targetLabels.filter((label) => label.status === "held");
|
|
1111
|
+
const booked = targetLabels.filter((label) => label.status === "booked");
|
|
1112
|
+
const activePointerTargets = targetLabels.filter((label) => label.pointerTarget.active);
|
|
1113
|
+
const activeCategoryKeys = /* @__PURE__ */ new Set([
|
|
1114
|
+
...evidence.labels.filter((label) => label.visible && (label.opacity > 0.25 || label.selected || label.status !== "free")).map((label) => label.categoryKey),
|
|
1115
|
+
...evidence.gaAreas.filter((area) => area.visible && area.opacity > 0.1).map((area) => area.categoryKey)
|
|
1116
|
+
]);
|
|
1117
|
+
const selectedRingContrast = selected.length ? minimum(selected.flatMap((label) => [
|
|
1118
|
+
renderedTextContrast(evidence.selectionRingColor, evidence.canvasBackground) ?? 0,
|
|
1119
|
+
renderedTextContrast(evidence.selectionRingColor, label.fill) ?? 0
|
|
1120
|
+
])) : null;
|
|
1121
|
+
const findings = [];
|
|
1122
|
+
const overviewCountSamples = (count, label) => Array.from({ length: count }, (_, index) => ({
|
|
1123
|
+
primaryId: `overview:${index + 1}`,
|
|
1124
|
+
primaryLabel: label
|
|
1125
|
+
}));
|
|
1126
|
+
if (state === "overview" && evidence.rung === "sections") {
|
|
1127
|
+
findings.push(finding(
|
|
1128
|
+
"overview-section-category-paint",
|
|
1129
|
+
"Section overview shells must use the neutral hierarchy palette, not category paint",
|
|
1130
|
+
overviewCountSamples(evidence.overviewStyle.categoryPaintedSectionShells, "category-painted section shell")
|
|
1131
|
+
));
|
|
1132
|
+
findings.push(finding(
|
|
1133
|
+
"overview-category-detail-visible",
|
|
1134
|
+
"Category-tinted section detail must wait until section focus or seat zoom",
|
|
1135
|
+
overviewCountSamples(evidence.overviewStyle.visibleCategoryDetailOutlines, "category detail outline")
|
|
1136
|
+
));
|
|
1137
|
+
findings.push(finding(
|
|
1138
|
+
"overview-row-hints-visible",
|
|
1139
|
+
"Row and seat patterns must not clutter the section overview",
|
|
1140
|
+
overviewCountSamples(evidence.overviewStyle.visibleSectionRowHints, "section row hint")
|
|
1141
|
+
));
|
|
1142
|
+
findings.push(finding(
|
|
1143
|
+
"overview-availability-clutter",
|
|
1144
|
+
"Live availability counts belong in focused detail, not on section overview shells",
|
|
1145
|
+
overviewCountSamples(evidence.overviewStyle.visibleSectionAvailabilityLabels, "section availability label")
|
|
1146
|
+
));
|
|
1147
|
+
findings.push(finding(
|
|
1148
|
+
"overview-ga-detail-visible",
|
|
1149
|
+
"Section-contained standing paint belongs in focused detail, not on section overview shells",
|
|
1150
|
+
overviewCountSamples(evidence.overviewStyle.visibleSectionGADetails, "section-contained GA detail")
|
|
1151
|
+
));
|
|
1152
|
+
}
|
|
1153
|
+
findings.push(finding(
|
|
1154
|
+
"bookable-label-undersized",
|
|
1155
|
+
"Visible bookable labels must meet the rendered small-text size floor",
|
|
1156
|
+
bookable.filter((label) => label.renderedFontPx < evidence.minimumVisibleLabelPx).map((label) => labelSample(label, label.renderedFontPx, evidence.minimumVisibleLabelPx))
|
|
1157
|
+
));
|
|
1158
|
+
if (state === "interaction") {
|
|
1159
|
+
const labelledInventory = targetLabels.length > 0;
|
|
1160
|
+
const gaInventory = targetGAAreas.length > 0;
|
|
1161
|
+
const detailInventory = labelledInventory || gaInventory;
|
|
1162
|
+
const visibleTargetGA = targetGAAreas.filter((area) => area.visible);
|
|
1163
|
+
const sections = /* @__PURE__ */ new Set([
|
|
1164
|
+
...targetLabels.flatMap((label) => label.sectionId ? [label.sectionId] : []),
|
|
1165
|
+
...targetGAAreas.flatMap((area) => area.sectionId ? [area.sectionId] : [])
|
|
1166
|
+
]);
|
|
1167
|
+
const categories = /* @__PURE__ */ new Set([
|
|
1168
|
+
...evidence.labels.map((label) => label.categoryKey),
|
|
1169
|
+
...evidence.gaAreas.map((area) => area.categoryKey)
|
|
1170
|
+
]);
|
|
1171
|
+
const inViewport = (label) => label.screenCenter.x >= 0 && label.screenCenter.x <= evidence.viewport.width && label.screenCenter.y >= 0 && label.screenCenter.y <= evidence.viewport.height;
|
|
1172
|
+
findings.push(finding(
|
|
1173
|
+
"detail-rung-missing",
|
|
1174
|
+
"Interaction evidence with bookable inventory must render the seat-detail rung",
|
|
1175
|
+
detailInventory && evidence.rung !== "seats" ? [{ primaryId: "renderer", primaryLabel: evidence.rung }] : []
|
|
1176
|
+
));
|
|
1177
|
+
findings.push(finding(
|
|
1178
|
+
"detail-inventory-not-visible",
|
|
1179
|
+
"Interaction evidence must frame at least one real bookable unit",
|
|
1180
|
+
detailInventory && !targetLabels.some(inViewport) && !visibleTargetGA.length ? [{ primaryId: "renderer", primaryLabel: "no target inventory in viewport" }] : []
|
|
1181
|
+
));
|
|
1182
|
+
findings.push(finding(
|
|
1183
|
+
"pointer-target-inactive",
|
|
1184
|
+
"Interaction evidence must expose a live production pointer target",
|
|
1185
|
+
labelledInventory && !activePointerTargets.some(inViewport) || !labelledInventory && gaInventory && !visibleTargetGA.some((area) => area.interactive) ? [{ primaryId: "renderer", primaryLabel: "no active pointer target in viewport" }] : []
|
|
1186
|
+
));
|
|
1187
|
+
findings.push(finding(
|
|
1188
|
+
"pointer-target-undersized",
|
|
1189
|
+
"Every active production pointer target must reach at least 24 CSS pixels",
|
|
1190
|
+
activePointerTargets.filter((label) => inViewport(label) && label.pointerTarget.effectiveMinimumPx < MIN_POINTER_TARGET_PX).map((label) => labelSample(label, label.pointerTarget.effectiveMinimumPx, MIN_POINTER_TARGET_PX))
|
|
1191
|
+
));
|
|
1192
|
+
findings.push(finding(
|
|
1193
|
+
"selected-state-missing",
|
|
1194
|
+
"Interaction evidence must paint a selected unit and its renderer-owned ring",
|
|
1195
|
+
labelledInventory && (!selected.length || !selected.some((label) => evidence.selectionRingSeatIds.includes(label.seatId))) ? [{ primaryId: "renderer", primaryLabel: "selected state" }] : []
|
|
1196
|
+
));
|
|
1197
|
+
findings.push(finding(
|
|
1198
|
+
"selected-state-contrast-low",
|
|
1199
|
+
"The selected-state ring must maintain 3:1 graphical contrast with the canvas",
|
|
1200
|
+
selected.length && (selectedRingContrast ?? 0) < MIN_GRAPHICAL_CONTRAST ? [{
|
|
1201
|
+
primaryId: selected[0].seatId,
|
|
1202
|
+
primaryLabel: selected[0].label,
|
|
1203
|
+
measured: Math.round((selectedRingContrast ?? 0) * 100) / 100,
|
|
1204
|
+
minimum: MIN_GRAPHICAL_CONTRAST
|
|
1205
|
+
}] : []
|
|
1206
|
+
));
|
|
1207
|
+
findings.push(finding(
|
|
1208
|
+
"held-state-missing",
|
|
1209
|
+
"Interaction evidence must paint a held state when the floor has at least two status-managed units",
|
|
1210
|
+
targetLabels.length >= 2 && !held.length ? [{ primaryId: "renderer", primaryLabel: "held state" }] : []
|
|
1211
|
+
));
|
|
1212
|
+
findings.push(finding(
|
|
1213
|
+
"booked-state-missing",
|
|
1214
|
+
"Interaction evidence must paint a taken state when the floor has at least three status-managed units",
|
|
1215
|
+
targetLabels.length >= 3 && !booked.length ? [{ primaryId: "renderer", primaryLabel: "booked state" }] : []
|
|
1216
|
+
));
|
|
1217
|
+
const heldSignatures = new Set(held.map((label) => `${label.fill.toLowerCase()}:${label.opacity}`));
|
|
1218
|
+
const bookedSignatures = new Set(booked.map((label) => `${label.fill.toLowerCase()}:${label.opacity}`));
|
|
1219
|
+
findings.push(finding(
|
|
1220
|
+
"status-state-indistinct",
|
|
1221
|
+
"Held and taken evidence must resolve to distinct renderer paint",
|
|
1222
|
+
held.length && booked.length && [...heldSignatures].some((signature) => bookedSignatures.has(signature)) ? [{ primaryId: held[0].seatId, primaryLabel: held[0].label, secondaryId: booked[0].seatId, secondaryLabel: booked[0].label }] : []
|
|
1223
|
+
));
|
|
1224
|
+
findings.push(finding(
|
|
1225
|
+
"section-focus-missing",
|
|
1226
|
+
"Interaction evidence must exercise section focus and its backdrop when section membership exists",
|
|
1227
|
+
sections.size && (!evidence.focusedSectionId || !evidence.focusBackdropVisible) ? [{ primaryId: "renderer", primaryLabel: "section focus" }] : []
|
|
1228
|
+
));
|
|
1229
|
+
findings.push(finding(
|
|
1230
|
+
"category-filter-missing",
|
|
1231
|
+
"Interaction evidence must exercise a category filter when multiple categories exist",
|
|
1232
|
+
categories.size >= 2 && (!evidence.categoryFilterKeys || !evidence.categoryFilterKeys.length) ? [{ primaryId: "renderer", primaryLabel: "category filter" }] : []
|
|
1233
|
+
));
|
|
1234
|
+
const excludedFree = evidence.labels.filter((label) => label.status === "free" && !label.selected && evidence.categoryFilterKeys != null && !evidence.categoryFilterKeys.includes(label.categoryKey));
|
|
1235
|
+
const excludedGA = evidence.gaAreas.filter((area) => evidence.categoryFilterKeys != null && !evidence.categoryFilterKeys.includes(area.categoryKey));
|
|
1236
|
+
findings.push(finding(
|
|
1237
|
+
"category-filter-ineffective",
|
|
1238
|
+
"The active category filter must visibly dim excluded free inventory",
|
|
1239
|
+
(excludedFree.length || excludedGA.length) && !excludedFree.some((label) => label.opacity <= 0.25) && !excludedGA.some((area) => area.opacity <= 0.1) ? [
|
|
1240
|
+
...excludedFree.map((label) => labelSample(label, label.opacity)),
|
|
1241
|
+
...excludedGA.map((area) => ({ primaryId: area.areaId, primaryLabel: area.label, measured: area.opacity }))
|
|
1242
|
+
] : []
|
|
1243
|
+
));
|
|
1244
|
+
findings.push(finding(
|
|
1245
|
+
"target-category-not-visible",
|
|
1246
|
+
"A category-specific interaction scene must visibly paint its exact target category",
|
|
1247
|
+
targetCategoryKey && !activeCategoryKeys.has(targetCategoryKey) ? [{ primaryId: targetCategoryKey, primaryLabel: targetCategoryKey }] : []
|
|
1248
|
+
));
|
|
1249
|
+
findings.push(finding(
|
|
1250
|
+
"target-category-filter-mismatch",
|
|
1251
|
+
"A category-specific interaction scene must bind its filter to only the exact target category",
|
|
1252
|
+
targetCategoryKey && (evidence.categoryFilterKeys?.length !== 1 || evidence.categoryFilterKeys[0] !== targetCategoryKey) ? [{ primaryId: targetCategoryKey, primaryLabel: targetCategoryKey }] : []
|
|
1253
|
+
));
|
|
1254
|
+
}
|
|
1255
|
+
findings.push(finding(
|
|
1256
|
+
"bookable-label-contrast-low",
|
|
1257
|
+
"Visible bookable labels must meet 4.5:1 contrast against their actual paint",
|
|
1258
|
+
bookable.flatMap((label) => {
|
|
1259
|
+
const ratio = renderedTextContrast(label.ink, label.fill) ?? 0;
|
|
1260
|
+
return ratio < MIN_TEXT_CONTRAST ? [labelSample(label, ratio, MIN_TEXT_CONTRAST)] : [];
|
|
1261
|
+
})
|
|
1262
|
+
));
|
|
1263
|
+
const bookableCollisions = [];
|
|
1264
|
+
for (let index = 0; index < bookable.length; index += 1) {
|
|
1265
|
+
for (let other = 0; other < index; other += 1) {
|
|
1266
|
+
if (intersects(bookable[index].screenBox, bookable[other].screenBox)) {
|
|
1267
|
+
bookableCollisions.push(collisionSample(
|
|
1268
|
+
bookable[other].seatId,
|
|
1269
|
+
bookable[other].label,
|
|
1270
|
+
bookable[index].seatId,
|
|
1271
|
+
bookable[index].label
|
|
1272
|
+
));
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
findings.push(finding(
|
|
1277
|
+
"bookable-label-collision",
|
|
1278
|
+
"Visible bookable labels must not overlap",
|
|
1279
|
+
bookableCollisions
|
|
1280
|
+
));
|
|
1281
|
+
findings.push(finding(
|
|
1282
|
+
"hierarchy-label-undersized",
|
|
1283
|
+
"Visible section and zone labels must meet the rendered small-text size floor",
|
|
1284
|
+
hierarchy.filter((label) => label.renderedFontPx < MIN_VISIBLE_BOOKABLE_LABEL_PX).map((label) => hierarchySample(label, label.renderedFontPx, MIN_VISIBLE_BOOKABLE_LABEL_PX))
|
|
1285
|
+
));
|
|
1286
|
+
findings.push(finding(
|
|
1287
|
+
"hierarchy-label-contrast-low",
|
|
1288
|
+
"Visible section and zone labels must meet 4.5:1 contrast against their backing paint",
|
|
1289
|
+
hierarchy.flatMap((label) => {
|
|
1290
|
+
const ratio = renderedTextContrast(label.ink, label.fill) ?? 0;
|
|
1291
|
+
return ratio < MIN_TEXT_CONTRAST ? [hierarchySample(label, ratio, MIN_TEXT_CONTRAST)] : [];
|
|
1292
|
+
})
|
|
1293
|
+
));
|
|
1294
|
+
findings.push(finding(
|
|
1295
|
+
"hierarchy-label-outside-section",
|
|
1296
|
+
"Visible section labels must remain inside the filled section surface and outside holes",
|
|
1297
|
+
hierarchy.filter((label) => label.kind === "section" && label.fitsContainer === false).map((label) => hierarchySample(label))
|
|
1298
|
+
));
|
|
1299
|
+
const hierarchyCollisions = [];
|
|
1300
|
+
for (let index = 0; index < hierarchy.length; index += 1) {
|
|
1301
|
+
for (let other = 0; other < index; other += 1) {
|
|
1302
|
+
if (intersects(hierarchy[index].screenBox, hierarchy[other].screenBox)) {
|
|
1303
|
+
hierarchyCollisions.push(collisionSample(
|
|
1304
|
+
hierarchy[other].id,
|
|
1305
|
+
hierarchy[other].label,
|
|
1306
|
+
hierarchy[index].id,
|
|
1307
|
+
hierarchy[index].label
|
|
1308
|
+
));
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
findings.push(finding(
|
|
1313
|
+
"hierarchy-label-collision",
|
|
1314
|
+
"Visible hierarchy labels must not overlap each other",
|
|
1315
|
+
hierarchyCollisions
|
|
1316
|
+
));
|
|
1317
|
+
const hierarchyBookableCollisions = [];
|
|
1318
|
+
for (const upper of hierarchy) {
|
|
1319
|
+
for (const unit of bookable) {
|
|
1320
|
+
if (intersects(upper.screenBox, unit.screenBox)) {
|
|
1321
|
+
hierarchyBookableCollisions.push(collisionSample(upper.id, upper.label, unit.seatId, unit.label));
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
findings.push(finding(
|
|
1326
|
+
"hierarchy-bookable-collision",
|
|
1327
|
+
"Visible hierarchy labels must not overlap bookable labels",
|
|
1328
|
+
hierarchyBookableCollisions
|
|
1329
|
+
));
|
|
1330
|
+
findings.push(finding(
|
|
1331
|
+
"free-text-undersized",
|
|
1332
|
+
"Visible chart text must meet the rendered small-text size floor",
|
|
1333
|
+
freeText.filter((label) => label.renderedFontPx < evidence.minimumVisibleLabelPx).map((label) => freeTextSample(label, label.renderedFontPx, evidence.minimumVisibleLabelPx))
|
|
1334
|
+
));
|
|
1335
|
+
findings.push(finding(
|
|
1336
|
+
"free-text-contrast-low",
|
|
1337
|
+
"Visible chart text must meet 4.5:1 contrast against its measured background",
|
|
1338
|
+
freeText.flatMap((label) => {
|
|
1339
|
+
const ratio = renderedTextContrast(label.ink, label.background) ?? 0;
|
|
1340
|
+
return ratio < MIN_TEXT_CONTRAST ? [freeTextSample(label, ratio, MIN_TEXT_CONTRAST)] : [];
|
|
1341
|
+
})
|
|
1342
|
+
));
|
|
1343
|
+
const freeTextCollisions = [];
|
|
1344
|
+
for (let index = 0; index < freeText.length; index += 1) {
|
|
1345
|
+
for (let other = 0; other < index; other += 1) {
|
|
1346
|
+
if (intersects(freeText[index].screenBox, freeText[other].screenBox)) {
|
|
1347
|
+
freeTextCollisions.push(collisionSample(
|
|
1348
|
+
freeText[other].objectId,
|
|
1349
|
+
freeText[other].text,
|
|
1350
|
+
freeText[index].objectId,
|
|
1351
|
+
freeText[index].text
|
|
1352
|
+
));
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
findings.push(finding(
|
|
1357
|
+
"free-text-collision",
|
|
1358
|
+
"Visible chart text labels must not overlap each other",
|
|
1359
|
+
freeTextCollisions
|
|
1360
|
+
));
|
|
1361
|
+
const freeTextBookableCollisions = [];
|
|
1362
|
+
for (const text of freeText) {
|
|
1363
|
+
for (const unit of bookable) {
|
|
1364
|
+
if (intersects(text.screenBox, unit.screenBox)) {
|
|
1365
|
+
freeTextBookableCollisions.push(collisionSample(text.objectId, text.text, unit.seatId, unit.label));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
findings.push(finding(
|
|
1370
|
+
"free-text-bookable-collision",
|
|
1371
|
+
"Visible chart text must not overlap bookable labels",
|
|
1372
|
+
freeTextBookableCollisions
|
|
1373
|
+
));
|
|
1374
|
+
const freeTextHierarchyCollisions = [];
|
|
1375
|
+
for (const text of freeText) {
|
|
1376
|
+
for (const upper of hierarchy) {
|
|
1377
|
+
if (intersects(text.screenBox, upper.screenBox)) {
|
|
1378
|
+
freeTextHierarchyCollisions.push(collisionSample(text.objectId, text.text, upper.id, upper.label));
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
findings.push(finding(
|
|
1383
|
+
"free-text-hierarchy-collision",
|
|
1384
|
+
"Visible chart text must not overlap hierarchy labels",
|
|
1385
|
+
freeTextHierarchyCollisions
|
|
1386
|
+
));
|
|
1387
|
+
findings.push(finding(
|
|
1388
|
+
"ga-contrast-low",
|
|
1389
|
+
"Visible GA surfaces must maintain 3:1 graphical contrast with the canvas",
|
|
1390
|
+
visibleGA.flatMap((area) => {
|
|
1391
|
+
const ratio = renderedTextContrast(area.effectiveBackground, evidence.canvasBackground) ?? 0;
|
|
1392
|
+
return ratio < MIN_GRAPHICAL_CONTRAST ? [{
|
|
1393
|
+
primaryId: area.areaId,
|
|
1394
|
+
primaryLabel: area.label,
|
|
1395
|
+
measured: Math.round(ratio * 100) / 100,
|
|
1396
|
+
minimum: MIN_GRAPHICAL_CONTRAST
|
|
1397
|
+
}] : [];
|
|
1398
|
+
})
|
|
1399
|
+
));
|
|
1400
|
+
const materialFindings = findings.filter((item) => Boolean(item));
|
|
1401
|
+
return {
|
|
1402
|
+
version: RENDERED_QUALITY_REPORT_VERSION,
|
|
1403
|
+
passed: materialFindings.length === 0,
|
|
1404
|
+
state,
|
|
1405
|
+
targetCategoryKey,
|
|
1406
|
+
resolvedRules: [
|
|
1407
|
+
"rendered-overview-label-size",
|
|
1408
|
+
"rendered-overview-label-contrast",
|
|
1409
|
+
"rendered-overview-label-collision",
|
|
1410
|
+
"rendered-overview-hierarchy-containment",
|
|
1411
|
+
"rendered-overview-section-first-style",
|
|
1412
|
+
"rendered-overview-ga-contrast",
|
|
1413
|
+
...state === "interaction" ? [
|
|
1414
|
+
"rendered-detail-inventory",
|
|
1415
|
+
"rendered-pointer-target",
|
|
1416
|
+
"rendered-selected-held-taken-states",
|
|
1417
|
+
"rendered-section-focus",
|
|
1418
|
+
"rendered-category-filter"
|
|
1419
|
+
] : []
|
|
1420
|
+
],
|
|
1421
|
+
viewport: evidence.viewport,
|
|
1422
|
+
canvasBackground: evidence.canvasBackground,
|
|
1423
|
+
effectiveScale: evidence.effectiveScale,
|
|
1424
|
+
rung: evidence.rung,
|
|
1425
|
+
inventory: {
|
|
1426
|
+
totalBookableUnits: evidence.totalBookableUnits,
|
|
1427
|
+
totalLabelledBookableUnits: evidence.totalLabelledBookableUnits,
|
|
1428
|
+
visibleBookableLabels: bookable.length,
|
|
1429
|
+
hiddenBookableLabels: evidence.hiddenLabels,
|
|
1430
|
+
visibleHierarchyLabels: hierarchy.length,
|
|
1431
|
+
visibleFreeTextLabels: freeText.length,
|
|
1432
|
+
visibleGAAreas: visibleGA.length
|
|
1433
|
+
},
|
|
1434
|
+
overviewStyle: evidence.overviewStyle,
|
|
1435
|
+
composition: {
|
|
1436
|
+
hierarchy: evidence.hierarchyLabels.filter((label) => label.role === "name").map((label) => ({
|
|
1437
|
+
id: label.id,
|
|
1438
|
+
kind: label.kind,
|
|
1439
|
+
label: label.label,
|
|
1440
|
+
visible: label.visible
|
|
1441
|
+
})).sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id)),
|
|
1442
|
+
labelledObjects: evidence.freeTextLabels.map((label) => ({
|
|
1443
|
+
objectId: label.objectId,
|
|
1444
|
+
kind: label.kind,
|
|
1445
|
+
text: label.text,
|
|
1446
|
+
visible: label.visible
|
|
1447
|
+
})).sort((left, right) => left.objectId.localeCompare(right.objectId) || left.kind.localeCompare(right.kind)),
|
|
1448
|
+
gaAreas: evidence.gaAreas.map((area) => ({
|
|
1449
|
+
areaId: area.areaId,
|
|
1450
|
+
label: area.label,
|
|
1451
|
+
categoryKey: area.categoryKey,
|
|
1452
|
+
...area.sectionId ? { sectionId: area.sectionId } : {},
|
|
1453
|
+
visible: area.visible
|
|
1454
|
+
})).sort((left, right) => left.areaId.localeCompare(right.areaId)),
|
|
1455
|
+
bookableSectionIds: [...new Set(evidence.labels.flatMap((label) => label.sectionId ? [label.sectionId] : []))].sort(),
|
|
1456
|
+
categoryKeys: [.../* @__PURE__ */ new Set([
|
|
1457
|
+
...evidence.labels.map((label) => label.categoryKey),
|
|
1458
|
+
...evidence.gaAreas.map((area) => area.categoryKey)
|
|
1459
|
+
])].sort(),
|
|
1460
|
+
activeCategoryKeys: [...activeCategoryKeys].sort()
|
|
1461
|
+
},
|
|
1462
|
+
metrics: {
|
|
1463
|
+
minimumRenderedBookableLabelPx: minimum(bookable.map((label) => label.renderedFontPx)),
|
|
1464
|
+
minimumBookableLabelContrast: minimum(bookableContrast),
|
|
1465
|
+
minimumRenderedHierarchyLabelPx: minimum(hierarchy.map((label) => label.renderedFontPx)),
|
|
1466
|
+
minimumHierarchyLabelContrast: minimum(hierarchyContrast),
|
|
1467
|
+
minimumRenderedFreeTextPx: minimum(freeText.map((label) => label.renderedFontPx)),
|
|
1468
|
+
minimumFreeTextContrast: minimum(freeTextContrast),
|
|
1469
|
+
minimumGAContrast: minimum(gaContrast),
|
|
1470
|
+
minimumEffectivePointerTargetPx: minimum(activePointerTargets.map((label) => label.pointerTarget.effectiveMinimumPx)),
|
|
1471
|
+
selectedRingContrast: selectedRingContrast == null ? null : Math.round(selectedRingContrast * 100) / 100
|
|
1472
|
+
},
|
|
1473
|
+
interaction: {
|
|
1474
|
+
applicable: {
|
|
1475
|
+
detail: targetLabels.length > 0 || targetGAAreas.length > 0,
|
|
1476
|
+
pointer: targetLabels.length > 0 || targetGAAreas.length > 0,
|
|
1477
|
+
held: targetLabels.length >= 2,
|
|
1478
|
+
booked: targetLabels.length >= 3,
|
|
1479
|
+
sectionFocus: targetLabels.some((label) => Boolean(label.sectionId)) || targetGAAreas.some((area) => Boolean(area.sectionId)),
|
|
1480
|
+
categoryFilter: (/* @__PURE__ */ new Set([
|
|
1481
|
+
...evidence.labels.map((label) => label.categoryKey),
|
|
1482
|
+
...evidence.gaAreas.map((area) => area.categoryKey)
|
|
1483
|
+
])).size >= 2
|
|
1484
|
+
},
|
|
1485
|
+
selectedUnits: selected.length,
|
|
1486
|
+
heldUnits: held.length,
|
|
1487
|
+
bookedUnits: booked.length,
|
|
1488
|
+
activePointerTargets: activePointerTargets.length,
|
|
1489
|
+
focusedSectionId: evidence.focusedSectionId,
|
|
1490
|
+
focusBackdropVisible: evidence.focusBackdropVisible,
|
|
1491
|
+
categoryFilterKeys: evidence.categoryFilterKeys
|
|
1492
|
+
},
|
|
1493
|
+
findings: materialFindings
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// src/engine/SeatmapRenderer.ts
|
|
1498
|
+
var import_Core = require("konva/lib/Core");
|
|
1499
|
+
var import_Stage = require("konva/lib/Stage");
|
|
1500
|
+
var import_Layer = require("konva/lib/Layer");
|
|
1501
|
+
var import_Group = require("konva/lib/Group");
|
|
1502
|
+
var import_Circle = require("konva/lib/shapes/Circle");
|
|
1503
|
+
var import_Rect = require("konva/lib/shapes/Rect");
|
|
1504
|
+
var import_Ellipse = require("konva/lib/shapes/Ellipse");
|
|
1505
|
+
var import_Line = require("konva/lib/shapes/Line");
|
|
1506
|
+
var import_Text = require("konva/lib/shapes/Text");
|
|
1507
|
+
var import_Path = require("konva/lib/shapes/Path");
|
|
1508
|
+
var import_Image = require("konva/lib/shapes/Image");
|
|
1509
|
+
var import_Shape = require("konva/lib/Shape");
|
|
1510
|
+
|
|
1062
1511
|
// src/lib/money.ts
|
|
1063
1512
|
var DEFAULT_CURRENCY = "USD";
|
|
1064
1513
|
var displayLocale;
|
|
@@ -1097,29 +1546,17 @@ var en = {
|
|
|
1097
1546
|
"common.done": "Done",
|
|
1098
1547
|
"common.copied": "\u2713 Copied",
|
|
1099
1548
|
// buyer picker
|
|
1100
|
-
"picker.holdSeats": "Hold seats & checkout",
|
|
1101
|
-
"picker.completeBooking": "Complete booking",
|
|
1102
|
-
"picker.seatsHeld": "Seats held \u2014 {time}",
|
|
1103
1549
|
"picker.holdExpired": "Your hold expired \u2014 the seats were released. Pick again.",
|
|
1104
|
-
"picker.seatTaken": "Seat {label} was just taken by another buyer.",
|
|
1105
1550
|
"picker.poweredBy": "Powered by SeatLayer",
|
|
1106
1551
|
"picker.testMode": "TEST MODE",
|
|
1107
|
-
"picker.colorblind": "Colorblind-friendly colors",
|
|
1108
1552
|
"picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
|
|
1109
|
-
"picker.seats.one": "{count} seat",
|
|
1110
|
-
"picker.seats.other": "{count} seats",
|
|
1111
1553
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
1112
1554
|
"map.aria": "Seating map. Use arrow keys to move between seats, Enter to select.",
|
|
1113
1555
|
"map.seatsLeft": "{count} LEFT",
|
|
1114
1556
|
"map.fromPrice": "FROM {price}",
|
|
1115
1557
|
"map.statusHeld": "On hold",
|
|
1116
1558
|
"map.statusTaken": "Taken",
|
|
1117
|
-
// buyer picker
|
|
1118
|
-
"picker.language": "Language",
|
|
1119
|
-
"picker.zoomToFit": "Zoom to fit",
|
|
1120
|
-
"picker.seatCountLabel": "seats",
|
|
1121
|
-
"picker.capacity": "capacity",
|
|
1122
|
-
"picker.viewMode": "View mode",
|
|
1559
|
+
// buyer picker widget (src/picker/widget/SeatPicker.ts)
|
|
1123
1560
|
"picker.floor": "Floor",
|
|
1124
1561
|
"picker.zoomLevel": "Zoom level",
|
|
1125
1562
|
"picker.rungTip.zones": "Venue overview \u2014 groups of sections such as North Stand or VIP",
|
|
@@ -1133,32 +1570,13 @@ var en = {
|
|
|
1133
1570
|
"picker.seatsLeftInSection.one": "{count} seat left",
|
|
1134
1571
|
"picker.seatsLeftInSection.other": "{count} seats left",
|
|
1135
1572
|
"picker.overview": "Overview",
|
|
1573
|
+
"picker.entrance": "Entrance",
|
|
1136
1574
|
"picker.tapSeatHint": "Tap any seat to check its view",
|
|
1137
|
-
"picker.chartSize": "Chart size",
|
|
1138
|
-
"picker.custom": "Custom",
|
|
1139
|
-
"picker.categories": "Categories",
|
|
1140
|
-
"picker.accessibility": "Accessibility",
|
|
1141
|
-
"picker.showAnyAccessible": "Show any accessible seat",
|
|
1142
|
-
"picker.any": "Any",
|
|
1143
|
-
"picker.yourSeats": "Your seats",
|
|
1144
|
-
"picker.emptySeats": "Tap seats on the map",
|
|
1145
|
-
"picker.emptySeatsWithGa": "Tap seats on the map \xB7 tap a standing area for GA tickets",
|
|
1146
|
-
"picker.oneFewer": "One fewer",
|
|
1147
|
-
"picker.oneMore": "One more",
|
|
1148
|
-
"picker.remove": "Remove {label}",
|
|
1149
1575
|
"picker.ticketTierFor": "Ticket tier for {label}",
|
|
1150
|
-
"picker.total": "Total: {amount}",
|
|
1151
1576
|
"picker.viewFromSeat": "View from seat {label}",
|
|
1152
1577
|
"picker.real360": "REAL 360\xB0",
|
|
1153
1578
|
"picker.preview": "PREVIEW",
|
|
1154
|
-
"picker.open360": "Open 360\xB0 view",
|
|
1155
1579
|
"picker.sightline": "\u2248 {m} m to stage \xB7 clear sightline",
|
|
1156
|
-
"picker.bookedDemo": "Booked! (demo)",
|
|
1157
|
-
"picker.bookButton.one": "Book {count} ticket \u2014 {amount}",
|
|
1158
|
-
"picker.bookButton.other": "Book {count} tickets \u2014 {amount}",
|
|
1159
|
-
"picker.simulateCrowd": "Simulate crowd: {state}",
|
|
1160
|
-
"picker.on": "ON",
|
|
1161
|
-
"picker.off": "OFF",
|
|
1162
1580
|
"picker.panorama360": "360\xB0 venue photo",
|
|
1163
1581
|
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m from stage",
|
|
1164
1582
|
"picker.restrictedView": "Restricted view",
|
|
@@ -1217,6 +1635,7 @@ var CACHE_THRESHOLD = 0.55 * SEAT_LEGIBLE_SCALE;
|
|
|
1217
1635
|
var LABEL_SCALE = MIN_VISIBLE_BOOKABLE_LABEL_PX / SEAT_LABEL_FONT_SIZE;
|
|
1218
1636
|
var MIN_FITTED_SEAT_LABEL_FONT_SIZE = 4;
|
|
1219
1637
|
var SEAT_TAP_SLOP_PX = 14;
|
|
1638
|
+
var SEAT_GLYPH_MIN_PX = 6.5;
|
|
1220
1639
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
1221
1640
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
1222
1641
|
var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
@@ -1506,6 +1925,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1506
1925
|
this.boothLabelById = /* @__PURE__ */ new Map();
|
|
1507
1926
|
/** Viewport seat labels are rebuilt after each settled camera change. */
|
|
1508
1927
|
this.seatLabelById = /* @__PURE__ */ new Map();
|
|
1928
|
+
/** Coloured accommodation ring per accessible seat (few per chart). */
|
|
1929
|
+
this.accessRingById = /* @__PURE__ */ new Map();
|
|
1930
|
+
/** Centred accessibility glyph per accessible seat — shown once the seat is
|
|
1931
|
+
* big enough on-screen (see {@link SEAT_GLYPH_MIN_PX}); the ring is the
|
|
1932
|
+
* smaller-zoom fallback. Kept in a map so zoom toggles touch only the handful
|
|
1933
|
+
* of accessible seats, never all 13k nodes. */
|
|
1934
|
+
this.accessGlyphById = /* @__PURE__ */ new Map();
|
|
1935
|
+
/** Whether the accessibility glyph is legible at the current camera scale. */
|
|
1936
|
+
this.accessGlyphVisible = false;
|
|
1509
1937
|
/** Authored free-text nodes obey the same rendered-size visibility floor. */
|
|
1510
1938
|
this.freeTextById = /* @__PURE__ */ new Map();
|
|
1511
1939
|
/** Stage/rink landmarks retain a readable screen-space caption at overview. */
|
|
@@ -1818,6 +2246,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1818
2246
|
this.boothDims.clear();
|
|
1819
2247
|
this.boothLabelById.clear();
|
|
1820
2248
|
this.seatLabelById.clear();
|
|
2249
|
+
this.accessRingById.clear();
|
|
2250
|
+
this.accessGlyphById.clear();
|
|
1821
2251
|
this.freeTextById.clear();
|
|
1822
2252
|
this.primaryFocalLabels.clear();
|
|
1823
2253
|
this.gaById.clear();
|
|
@@ -2726,18 +3156,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2726
3156
|
this.paintSeat(c, seat.id);
|
|
2727
3157
|
target.add(c);
|
|
2728
3158
|
if (seat.accessible) {
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
);
|
|
3159
|
+
const ring = new import_Circle.Circle({
|
|
3160
|
+
x: seat.x,
|
|
3161
|
+
y: seat.y,
|
|
3162
|
+
radius: this.seatR + 1.5,
|
|
3163
|
+
stroke: accessibilityRingColor(seat.accessibility),
|
|
3164
|
+
strokeWidth: 2.5,
|
|
3165
|
+
listening: false,
|
|
3166
|
+
perfectDrawEnabled: false,
|
|
3167
|
+
shadowForStrokeEnabled: false
|
|
3168
|
+
});
|
|
3169
|
+
this.accessRingById.set(seat.id, ring);
|
|
3170
|
+
target.add(ring);
|
|
3171
|
+
const glyph = this.buildAccessGlyph(seat, typeof c.fill() === "string" ? c.fill() : "");
|
|
3172
|
+
this.accessGlyphById.set(seat.id, glyph);
|
|
3173
|
+
target.add(glyph);
|
|
2741
3174
|
}
|
|
2742
3175
|
}
|
|
2743
3176
|
}
|
|
@@ -2781,6 +3214,53 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2781
3214
|
target.add(t2);
|
|
2782
3215
|
this.paintSeat(rect, seat.id);
|
|
2783
3216
|
}
|
|
3217
|
+
/**
|
|
3218
|
+
* Build the centred accessibility glyph for a seat. Sized relative to the seat
|
|
3219
|
+
* radius (so it always fills the marker at any zoom) and given a contrast-aware
|
|
3220
|
+
* fill against the seat's paint — recomputed per state in {@link paintSeat}.
|
|
3221
|
+
*/
|
|
3222
|
+
buildAccessGlyph(seat, seatFill) {
|
|
3223
|
+
const k = this.seatR * 1.5 / ACCESS_GLYPH_VIEWBOX;
|
|
3224
|
+
return new import_Path.Path({
|
|
3225
|
+
x: seat.x,
|
|
3226
|
+
y: seat.y,
|
|
3227
|
+
data: ACCESS_GLYPH_PATH,
|
|
3228
|
+
offsetX: ACCESS_GLYPH_VIEWBOX / 2,
|
|
3229
|
+
offsetY: ACCESS_GLYPH_VIEWBOX / 2,
|
|
3230
|
+
scaleX: k,
|
|
3231
|
+
scaleY: k,
|
|
3232
|
+
fill: stateAwareBookableLabelInk(seatFill, "#ffffff"),
|
|
3233
|
+
listening: false,
|
|
3234
|
+
visible: this.accessGlyphVisible,
|
|
3235
|
+
perfectDrawEnabled: false,
|
|
3236
|
+
shadowForStrokeEnabled: false
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Toggle the accessibility glyphs for the current camera scale: shown once the
|
|
3241
|
+
* effective on-screen seat radius clears {@link SEAT_GLYPH_MIN_PX}, otherwise
|
|
3242
|
+
* hidden so only the ring remains. Iterates the (small) accessible-seat set,
|
|
3243
|
+
* never the full node graph, so it is cheap to call on every view change.
|
|
3244
|
+
*/
|
|
3245
|
+
updateAccessGlyphs(scale) {
|
|
3246
|
+
if (!this.accessGlyphById.size) return;
|
|
3247
|
+
this.accessGlyphVisible = this.seatR * scale >= SEAT_GLYPH_MIN_PX;
|
|
3248
|
+
for (const [id, glyph] of this.accessGlyphById) {
|
|
3249
|
+
glyph.visible(this.accessGlyphVisible && this.accessGlyphEligible(id));
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Whether an accessible seat's glyph should show for its current status. It is
|
|
3254
|
+
* hidden on seats a buyer cannot take (sold, or another buyer's hold) where the
|
|
3255
|
+
* overlay status cue (diagonal mark / lock) carries the state instead — mirrors
|
|
3256
|
+
* the `unavailable` test in {@link updateLabels} so the two never collide.
|
|
3257
|
+
*/
|
|
3258
|
+
accessGlyphEligible(id) {
|
|
3259
|
+
const status = this.statusById.get(id) ?? "free";
|
|
3260
|
+
if (status === "booked") return false;
|
|
3261
|
+
if (status === "held" && !this.ownedHold.has(id) && !this.opts.manageMode) return false;
|
|
3262
|
+
return true;
|
|
3263
|
+
}
|
|
2784
3264
|
/** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
|
|
2785
3265
|
seatBaseColor(categoryKey) {
|
|
2786
3266
|
if (!this.colorblind) return this.catColor.get(categoryKey) ?? "#6e7bff";
|
|
@@ -2894,6 +3374,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2894
3374
|
isBookableLabelLegibleAtScale(bookableLabel.fontSize(), this.effScale()) && c.opacity() >= 0.5
|
|
2895
3375
|
);
|
|
2896
3376
|
}
|
|
3377
|
+
const accessGlyph = this.accessGlyphById.get(id);
|
|
3378
|
+
if (accessGlyph) {
|
|
3379
|
+
const fill = c.fill();
|
|
3380
|
+
accessGlyph.fill(stateAwareBookableLabelInk(typeof fill === "string" ? fill : "", "#ffffff"));
|
|
3381
|
+
accessGlyph.opacity(c.opacity());
|
|
3382
|
+
accessGlyph.visible(this.accessGlyphVisible && this.accessGlyphEligible(id));
|
|
3383
|
+
}
|
|
3384
|
+
this.accessRingById.get(id)?.opacity(c.opacity());
|
|
2897
3385
|
}
|
|
2898
3386
|
/** True when a seat sits in a section/zone currently marked `closed`. */
|
|
2899
3387
|
seatInClosedSection(id) {
|
|
@@ -4619,6 +5107,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4619
5107
|
if (this.hasSections) this.applySectionLod(scale);
|
|
4620
5108
|
else if (this.primaryFocalLabels.size) this.bgLayer.batchDraw();
|
|
4621
5109
|
this.paintGAStateForView();
|
|
5110
|
+
this.updateAccessGlyphs(scale);
|
|
4622
5111
|
const shouldCache = scale < CACHE_THRESHOLD;
|
|
4623
5112
|
if (shouldCache && !this.cached) {
|
|
4624
5113
|
this.cacheSeatLayer();
|
|
@@ -4701,6 +5190,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4701
5190
|
for (const seat of this.seats) {
|
|
4702
5191
|
if (seat.kind === "booth") continue;
|
|
4703
5192
|
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
5193
|
+
if (seat.accessible && this.accessGlyphVisible) continue;
|
|
4704
5194
|
const shape = this.circleById.get(seat.id);
|
|
4705
5195
|
if ((shape?.opacity() ?? 1) < 0.5) continue;
|
|
4706
5196
|
const status = this.statusById.get(seat.id) ?? "free";
|
|
@@ -5050,13 +5540,17 @@ var PickerController = class {
|
|
|
5050
5540
|
this.allIds.push(s.id);
|
|
5051
5541
|
const source = chartObjects.get(s.rowId);
|
|
5052
5542
|
const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
|
|
5053
|
-
const
|
|
5054
|
-
const
|
|
5055
|
-
const
|
|
5543
|
+
const sourceDisplayLabel = source && "displayLabel" in source && typeof source.displayLabel === "string" && source.displayLabel ? source.displayLabel : sourceLabel;
|
|
5544
|
+
const rowLabel = s.kind === "booth" ? void 0 : sourceDisplayLabel;
|
|
5545
|
+
const rowType = source && "displayType" in source && typeof source.displayType === "string" && source.displayType.trim() ? source.displayType.trim() : void 0;
|
|
5546
|
+
const visibleSeatLabel = s.displayLabel ?? s.label;
|
|
5547
|
+
const labelParts = visibleSeatLabel.split("-");
|
|
5548
|
+
const seatNumber = sourceDisplayLabel && visibleSeatLabel.startsWith(`${sourceDisplayLabel}-`) ? visibleSeatLabel.slice(sourceDisplayLabel.length + 1) : s.kind === "booth" ? visibleSeatLabel : labelParts[labelParts.length - 1] ?? visibleSeatLabel;
|
|
5056
5549
|
this.seatContext.set(s.id, {
|
|
5057
5550
|
sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
|
|
5058
5551
|
rowLabel,
|
|
5059
|
-
seatNumber
|
|
5552
|
+
seatNumber,
|
|
5553
|
+
rowType
|
|
5060
5554
|
});
|
|
5061
5555
|
}
|
|
5062
5556
|
const currency = res.event.currency ?? this.opts.currency;
|
|
@@ -5691,6 +6185,7 @@ var PickerController = class {
|
|
|
5691
6185
|
id,
|
|
5692
6186
|
label: sec.label,
|
|
5693
6187
|
zoneLabel: zone?.label ?? "",
|
|
6188
|
+
...sec.entrance && sec.entrance.trim() ? { entrance: sec.entrance.trim() } : {},
|
|
5694
6189
|
color,
|
|
5695
6190
|
seatsLeft,
|
|
5696
6191
|
priceMin: prices.length ? prices[0] : 0,
|
|
@@ -5722,14 +6217,16 @@ var PickerController = class {
|
|
|
5722
6217
|
// ---- internals ------------------------------------------------------------
|
|
5723
6218
|
toSeat(s) {
|
|
5724
6219
|
const commercial = s.commercial ? { commercial: s.commercial } : void 0;
|
|
6220
|
+
const display = s.displayLabel ? { displayLabel: s.displayLabel } : void 0;
|
|
5725
6221
|
const tiers = this.tiersFor(s.categoryKey);
|
|
5726
6222
|
if (!tiers) {
|
|
5727
|
-
return { id: s.id, label: s.label, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial };
|
|
6223
|
+
return { id: s.id, label: s.label, ...display, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial };
|
|
5728
6224
|
}
|
|
5729
6225
|
const chosen = tiers.find((t2) => t2.id === this.seatTiers.get(s.id)) ?? tiers[0];
|
|
5730
6226
|
return {
|
|
5731
6227
|
id: s.id,
|
|
5732
6228
|
label: s.label,
|
|
6229
|
+
...display,
|
|
5733
6230
|
categoryKey: s.categoryKey,
|
|
5734
6231
|
price: chosen.price,
|
|
5735
6232
|
tiers,
|
|
@@ -6254,6 +6751,7 @@ async function loadLocale(code) {
|
|
|
6254
6751
|
MAX_EVENT_INVENTORY,
|
|
6255
6752
|
MAX_GA_CAPACITY,
|
|
6256
6753
|
PickerController,
|
|
6754
|
+
RENDERED_QUALITY_REPORT_VERSION,
|
|
6257
6755
|
SUPPORTED_LOCALES,
|
|
6258
6756
|
SeatmapRenderer,
|
|
6259
6757
|
UNGROUPED_ID,
|
|
@@ -6281,6 +6779,7 @@ async function loadLocale(code) {
|
|
|
6281
6779
|
generateSeatThumb,
|
|
6282
6780
|
getLocale,
|
|
6283
6781
|
hiddenObjectIds,
|
|
6782
|
+
inspectRenderedQualityEvidence,
|
|
6284
6783
|
isGaUnitLabel,
|
|
6285
6784
|
isSectionHidden,
|
|
6286
6785
|
layerOf,
|
|
@@ -6290,6 +6789,8 @@ async function loadLocale(code) {
|
|
|
6290
6789
|
pointInPolygonWithHoles,
|
|
6291
6790
|
polygonLabelPoint,
|
|
6292
6791
|
resolveLocale,
|
|
6792
|
+
rowSeatPositions,
|
|
6793
|
+
seatLabelPart,
|
|
6293
6794
|
setLocale,
|
|
6294
6795
|
setMoneyLocale,
|
|
6295
6796
|
setStringOverrides,
|