@seatlayer/core 0.46.0 → 0.47.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.
Files changed (40) hide show
  1. package/dist/chunk-AZODENEL.js +97 -0
  2. package/dist/chunk-AZODENEL.js.map +1 -0
  3. package/dist/chunk-FVCOOLJO.js +335 -0
  4. package/dist/chunk-FVCOOLJO.js.map +1 -0
  5. package/dist/{chunk-5MRJCIZP.js → chunk-TCTAS4Z2.js} +88 -14
  6. package/dist/chunk-TCTAS4Z2.js.map +1 -0
  7. package/dist/{de-UHAOVDS3.js → de-ST4H423D.js} +6 -3
  8. package/dist/{de-UHAOVDS3.js.map → de-ST4H423D.js.map} +1 -1
  9. package/dist/{es-6TJS2L7S.js → es-WEC5NHRT.js} +6 -3
  10. package/dist/{es-6TJS2L7S.js.map → es-WEC5NHRT.js.map} +1 -1
  11. package/dist/{fr-MMSY7FPE.js → fr-SGBFNAQV.js} +6 -3
  12. package/dist/{fr-MMSY7FPE.js.map → fr-SGBFNAQV.js.map} +1 -1
  13. package/dist/index.cjs +251 -39
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +119 -4
  16. package/dist/index.d.ts +119 -4
  17. package/dist/index.js +152 -24
  18. package/dist/index.js.map +1 -1
  19. package/dist/{types-DBnRO2hX.d.cts → types-CE63BK0j.d.cts} +88 -10
  20. package/dist/{types-DBnRO2hX.d.ts → types-CE63BK0j.d.ts} +88 -10
  21. package/dist/view/panoramaDelivery.cjs +126 -0
  22. package/dist/view/panoramaDelivery.cjs.map +1 -0
  23. package/dist/view/panoramaDelivery.d.cts +34 -0
  24. package/dist/view/panoramaDelivery.d.ts +34 -0
  25. package/dist/view/panoramaDelivery.js +17 -0
  26. package/dist/view/panoramaDelivery.js.map +1 -0
  27. package/dist/view3d/crossfade/panorama.cjs +453 -0
  28. package/dist/view3d/crossfade/panorama.cjs.map +1 -0
  29. package/dist/view3d/crossfade/panorama.d.cts +110 -0
  30. package/dist/view3d/crossfade/panorama.d.ts +110 -0
  31. package/dist/view3d/crossfade/panorama.js +30 -0
  32. package/dist/view3d/crossfade/panorama.js.map +1 -0
  33. package/dist/view3d/index.cjs +1161 -128
  34. package/dist/view3d/index.cjs.map +1 -1
  35. package/dist/view3d/index.d.cts +60 -35
  36. package/dist/view3d/index.d.ts +60 -35
  37. package/dist/view3d/index.js +945 -300
  38. package/dist/view3d/index.js.map +1 -1
  39. package/package.json +28 -1
  40. package/dist/chunk-5MRJCIZP.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -72,7 +72,7 @@ var init_es = __esm({
72
72
  "picker.viewFromSeat": "Vista desde el asiento {label}",
73
73
  "picker.real360": "REAL 360\xB0",
74
74
  "picker.preview": "VISTA PREVIA",
75
- "picker.sightline": "\u2248 {m} m al escenario \xB7 visi\xF3n despejada",
75
+ "picker.sightline": "\u2248 {m} m al escenario",
76
76
  "picker.panorama360": "Foto de 360\xB0 del lugar",
77
77
  "picker.illustrationCaption": "ilustraci\xF3n \xB7 \u2248 {m} m del escenario",
78
78
  "picker.restrictedView": "Visibilidad restringida",
@@ -80,7 +80,10 @@ var init_es = __esm({
80
80
  "picker.premiumSeat": "Asiento premium",
81
81
  "picker.hideLimitedView": "Ocultar asientos con visibilidad limitada",
82
82
  "picker.bestSeatsPremium": "Mejores asientos",
83
- "picker.premiumFallbackNote": "No hay un bloque premium de {count}: mostrando los mejores disponibles"
83
+ "picker.premiumFallbackNote": "No hay un bloque premium de {count}: mostrando los mejores disponibles",
84
+ "picker.loading3d": "Creando el recinto en 3D\u2026",
85
+ "picker.unavailable3d": "No se pudo iniciar la vista 3D. El mapa de asientos sigue disponible.",
86
+ "picker.jumpToSection": "Ir a una secci\xF3n"
84
87
  };
85
88
  }
86
89
  });
@@ -132,7 +135,7 @@ var init_de = __esm({
132
135
  "picker.viewFromSeat": "Ansicht von Platz {label}",
133
136
  "picker.real360": "REAL 360\xB0",
134
137
  "picker.preview": "VORSCHAU",
135
- "picker.sightline": "\u2248 {m} m zur B\xFChne \xB7 freie Sicht",
138
+ "picker.sightline": "\u2248 {m} m zur B\xFChne",
136
139
  "picker.panorama360": "360\xB0-Veranstaltungsfotos",
137
140
  "picker.illustrationCaption": "Illustration \xB7 \u2248 {m} m von der B\xFChne",
138
141
  "picker.restrictedView": "Eingeschr\xE4nkte Sicht",
@@ -140,7 +143,10 @@ var init_de = __esm({
140
143
  "picker.premiumSeat": "Premium-Platz",
141
144
  "picker.hideLimitedView": "Pl\xE4tze mit eingeschr\xE4nkter Sicht ausblenden",
142
145
  "picker.bestSeatsPremium": "Beste Pl\xE4tze",
143
- "picker.premiumFallbackNote": "Kein Premium-Block mit {count} Pl\xE4tzen \u2014 beste verf\xFCgbare werden angezeigt"
146
+ "picker.premiumFallbackNote": "Kein Premium-Block mit {count} Pl\xE4tzen \u2014 beste verf\xFCgbare werden angezeigt",
147
+ "picker.loading3d": "3D-Veranstaltungsort wird aufgebaut\u2026",
148
+ "picker.unavailable3d": "3D konnte nicht gestartet werden. Der Sitzplan bleibt verf\xFCgbar.",
149
+ "picker.jumpToSection": "Zum Bereich springen"
144
150
  };
145
151
  }
146
152
  });
@@ -192,7 +198,7 @@ var init_fr = __esm({
192
198
  "picker.viewFromSeat": "Vue depuis le si\xE8ge {label}",
193
199
  "picker.real360": "VRAI 360\xB0",
194
200
  "picker.preview": "APER\xC7U",
195
- "picker.sightline": "\u2248 {m} m de la sc\xE8ne \xB7 vue d\xE9gag\xE9e",
201
+ "picker.sightline": "\u2248 {m} m de la sc\xE8ne",
196
202
  "picker.panorama360": "Photo panoramique 360\xB0 du lieu",
197
203
  "picker.illustrationCaption": "illustration \xB7 \u2248 {m} m de la sc\xE8ne",
198
204
  "picker.restrictedView": "Visibilit\xE9 r\xE9duite",
@@ -200,7 +206,10 @@ var init_fr = __esm({
200
206
  "picker.premiumSeat": "Place premium",
201
207
  "picker.hideLimitedView": "Masquer les places \xE0 visibilit\xE9 r\xE9duite",
202
208
  "picker.bestSeatsPremium": "Meilleures places",
203
- "picker.premiumFallbackNote": "Aucun bloc premium de {count} \u2014 affichage des meilleures places disponibles"
209
+ "picker.premiumFallbackNote": "Aucun bloc premium de {count} \u2014 affichage des meilleures places disponibles",
210
+ "picker.loading3d": "Cr\xE9ation de la salle en 3D\u2026",
211
+ "picker.unavailable3d": "La 3D n\u2019a pas pu d\xE9marrer. Le plan des si\xE8ges reste disponible.",
212
+ "picker.jumpToSection": "Acc\xE9der \xE0 une section"
204
213
  };
205
214
  }
206
215
  });
@@ -211,6 +220,7 @@ __export(index_exports, {
211
220
  ACCESSIBILITY_RING_COLOR: () => ACCESSIBILITY_RING_COLOR,
212
221
  ACCESSIBILITY_TYPES: () => ACCESSIBILITY_TYPES,
213
222
  CHART_STORAGE_KEY: () => CHART_STORAGE_KEY,
223
+ CURRENCY_CHOICES: () => CURRENCY_CHOICES,
214
224
  DEFAULT_CURRENCY: () => DEFAULT_CURRENCY,
215
225
  LABEL_STYLE_MAX_SIZE: () => LABEL_STYLE_MAX_SIZE,
216
226
  LABEL_STYLE_MIN_SIZE: () => LABEL_STYLE_MIN_SIZE,
@@ -331,7 +341,10 @@ var SURROUNDINGS_SHAPE_ROLES = [
331
341
  "sound",
332
342
  "concession",
333
343
  "coat",
334
- "wall"
344
+ "wall",
345
+ "rail",
346
+ "suite",
347
+ "obstruction"
335
348
  ];
336
349
  var SURROUNDINGS_SHAPE_ROLE_SET = new Set(SURROUNDINGS_SHAPE_ROLES);
337
350
  function layerOf(obj) {
@@ -360,6 +373,48 @@ function layerOf(obj) {
360
373
  }
361
374
  var CHART_STORAGE_KEY = "seatmap.chart";
362
375
 
376
+ // src/core/textLayout.ts
377
+ var glyphAdvance = (text) => Math.max(1, text.fontSize * 0.6 + (text.letterSpacing ?? 0));
378
+ function wrappedLineCount(text, usableWidth) {
379
+ const explicit = text.text.split(/\r?\n/);
380
+ if (!text.width) return Math.max(1, explicit.length);
381
+ const perLine = Math.max(1, Math.floor(usableWidth / glyphAdvance(text)));
382
+ let lines = 0;
383
+ for (const source of explicit) {
384
+ if (!source.length) {
385
+ lines += 1;
386
+ continue;
387
+ }
388
+ let used = 0;
389
+ for (const word of source.split(/\s+/)) {
390
+ const length = word.length;
391
+ if (!used) {
392
+ lines += Math.max(1, Math.ceil(length / perLine));
393
+ used = length % perLine;
394
+ } else if (used + 1 + length <= perLine) used += 1 + length;
395
+ else {
396
+ lines += Math.max(1, Math.ceil(length / perLine));
397
+ used = length % perLine;
398
+ }
399
+ }
400
+ }
401
+ return Math.max(1, lines);
402
+ }
403
+ function textLayoutMetrics(text) {
404
+ const padding = text.background?.padding ?? 0;
405
+ const explicitLines = text.text.split(/\r?\n/);
406
+ const naturalWidth = Math.max(1, ...explicitLines.map((line) => line.length)) * glyphAdvance(text);
407
+ const width = text.width ?? naturalWidth + padding * 2;
408
+ const usableWidth = Math.max(1, width - padding * 2);
409
+ const lineCount = wrappedLineCount(text, usableWidth);
410
+ return {
411
+ width,
412
+ height: lineCount * text.fontSize * (text.lineHeight ?? 1.2) + padding * 2,
413
+ lineCount,
414
+ padding
415
+ };
416
+ }
417
+
363
418
  // src/core/complexGeometry.ts
364
419
  function cubicPoint(path, t2) {
365
420
  const u = 1 - t2;
@@ -596,7 +651,8 @@ function resolveSegmentedRowGroups(objects) {
596
651
  displayLabel: first.displayLabel,
597
652
  totalSeats,
598
653
  canonical: ordered[0],
599
- viewFromSeatUrl: first.viewFromSeatUrl
654
+ viewFromSeatUrl: first.viewFromSeatUrl,
655
+ viewFromSeatMeta: first.viewFromSeatMeta
600
656
  });
601
657
  adjacencyOffset += row.seatCount;
602
658
  displayOffset += row.seatCount;
@@ -1179,6 +1235,7 @@ function expandRowSlots(row, placement) {
1179
1235
  wheelchairSpaceType: o?.wheelchairSpaceType,
1180
1236
  commercial: Object.values(commercial).some((value) => value !== void 0 && value !== false && value !== "") ? commercial : void 0,
1181
1237
  viewUrl: o?.viewFromSeatUrl ?? row.viewFromSeatUrl,
1238
+ viewMeta: o?.viewFromSeatUrl ? o.viewFromSeatMeta : row.viewFromSeatMeta,
1182
1239
  labelStyle: o?.labelStyle
1183
1240
  };
1184
1241
  });
@@ -1200,6 +1257,7 @@ function expandRow(row) {
1200
1257
  wheelchairSpaceType: slot.wheelchairSpaceType,
1201
1258
  commercial: slot.commercial,
1202
1259
  viewUrl: slot.viewUrl,
1260
+ viewMeta: slot.viewMeta,
1203
1261
  labelStyle: slot.labelStyle
1204
1262
  });
1205
1263
  }
@@ -1238,6 +1296,7 @@ function expandTableSlots(t2) {
1238
1296
  wheelchairSpaceType: override?.wheelchairSpaceType,
1239
1297
  commercial: override?.commercial,
1240
1298
  viewUrl: override?.viewFromSeatUrl,
1299
+ viewMeta: override?.viewFromSeatUrl ? override.viewFromSeatMeta : void 0,
1241
1300
  labelStyle: override?.labelStyle,
1242
1301
  ...side ? { side } : {}
1243
1302
  };
@@ -1309,6 +1368,7 @@ function expandTable(t2) {
1309
1368
  ...slot.wheelchairSpaceType ? { wheelchairSpaceType: slot.wheelchairSpaceType } : {},
1310
1369
  ...slot.commercial ? { commercial: slot.commercial } : {},
1311
1370
  ...slot.viewUrl ? { viewUrl: slot.viewUrl } : {},
1371
+ ...slot.viewUrl && slot.viewMeta ? { viewMeta: slot.viewMeta } : {},
1312
1372
  ...slot.labelStyle ? { labelStyle: slot.labelStyle } : {}
1313
1373
  }));
1314
1374
  }
@@ -1491,7 +1551,10 @@ function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
1491
1551
  if (!overrides2.get(physicalIndex)?.displayLabel) {
1492
1552
  seat.displayLabel = seatDisplayLabel(obj, physicalIndex, logical);
1493
1553
  }
1494
- seat.viewUrl ??= logical.viewFromSeatUrl;
1554
+ if (!seat.viewUrl && logical.viewFromSeatUrl) {
1555
+ seat.viewUrl = logical.viewFromSeatUrl;
1556
+ seat.viewMeta = logical.viewFromSeatMeta;
1557
+ }
1495
1558
  }
1496
1559
  }
1497
1560
  }
@@ -1500,8 +1563,14 @@ function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
1500
1563
  const zone = owner?.zone ? zones?.find((candidate) => candidate.id === owner.zone) : void 0;
1501
1564
  const resolvedFocal = zone?.focalPoint ?? fallbackFocal;
1502
1565
  for (const seat of seats) {
1503
- if (inheritedView) seat.viewUrl ??= inheritedView;
1504
- if (viewFallback) seat.viewUrl ??= viewFallback;
1566
+ if (!seat.viewUrl && inheritedView) {
1567
+ seat.viewUrl = inheritedView;
1568
+ seat.viewMeta = owner?.viewFromSeatMeta;
1569
+ }
1570
+ if (!seat.viewUrl && viewFallback) {
1571
+ seat.viewUrl = viewFallback.url;
1572
+ seat.viewMeta = viewFallback.meta;
1573
+ }
1505
1574
  if (owner) seat.sectionId = owner.logicalSectionId ?? owner.id;
1506
1575
  if (owner?.zone) seat.zoneId = owner.zone;
1507
1576
  if (resolvedFocal) seat.focalPoint = { ...resolvedFocal };
@@ -1515,15 +1584,24 @@ function expandChart(doc, options = {}) {
1515
1584
  const out2 = [];
1516
1585
  for (const floor of doc.floors) {
1517
1586
  const floorFocal = floor.focalPoint ?? doc.focalPoint;
1518
- const floorView = floor.viewFromSeatUrl ?? doc.viewFromSeatUrl;
1587
+ const floorView = floor.viewFromSeatUrl ? { url: floor.viewFromSeatUrl, meta: floor.viewFromSeatMeta } : doc.viewFromSeatUrl ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta } : void 0;
1519
1588
  const seats = expandFloorObjects(floor.objects, doc.zones, floorFocal, floorView);
1520
- assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);
1589
+ if (options.resolveEyeHeights !== false) {
1590
+ assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);
1591
+ }
1521
1592
  out2.push(...seats);
1522
1593
  }
1523
1594
  return out2;
1524
1595
  }
1525
- const out = expandFloorObjects(doc.objects, doc.zones, doc.focalPoint, doc.viewFromSeatUrl);
1526
- assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);
1596
+ const out = expandFloorObjects(
1597
+ doc.objects,
1598
+ doc.zones,
1599
+ doc.focalPoint,
1600
+ doc.viewFromSeatUrl ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta } : void 0
1601
+ );
1602
+ if (options.resolveEyeHeights !== false) {
1603
+ assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);
1604
+ }
1527
1605
  return out;
1528
1606
  }
1529
1607
  function assignEyeHeights(objects, focal, floorBaseHeightM, seats) {
@@ -1581,9 +1659,15 @@ function chartBounds(doc) {
1581
1659
  if (x > maxX) maxX = x;
1582
1660
  if (y > maxY) maxY = y;
1583
1661
  };
1584
- for (const s of expandChart(doc)) acc(s.x, s.y);
1585
1662
  for (const obj of allObjects(doc)) {
1586
- if (obj.type === "gaArea") {
1663
+ if (obj.type === "row") {
1664
+ const overrides2 = new Map((obj.overrides ?? []).map((override) => [override.index, override]));
1665
+ rowSeatPositions(obj).forEach((point, index) => {
1666
+ const override = overrides2.get(index);
1667
+ if (override?.skip) return;
1668
+ acc(point.x + (override?.dx ?? 0), point.y + (override?.dy ?? 0));
1669
+ });
1670
+ } else if (obj.type === "gaArea") {
1587
1671
  for (const p of obj.points) acc(p.x, p.y);
1588
1672
  } else if (obj.type === "section") {
1589
1673
  for (const p of obj.outline) acc(p.x, p.y);
@@ -1607,9 +1691,9 @@ function chartBounds(doc) {
1607
1691
  acc(obj.center.x - ext, obj.center.y - ext);
1608
1692
  acc(obj.center.x + ext, obj.center.y + ext);
1609
1693
  } else if (obj.type === "text") {
1610
- const w = obj.fontSize * obj.text.length * 0.6;
1694
+ const box = textLayoutMetrics(obj);
1611
1695
  acc(obj.position.x, obj.position.y);
1612
- acc(obj.position.x + w, obj.position.y + obj.fontSize);
1696
+ acc(obj.position.x + box.width, obj.position.y + box.height);
1613
1697
  }
1614
1698
  }
1615
1699
  for (const image of [doc.referenceImage, doc.backgroundImage]) {
@@ -2823,6 +2907,7 @@ function decomposeAffineLinear(affine) {
2823
2907
 
2824
2908
  // src/lib/money.ts
2825
2909
  var DEFAULT_CURRENCY = "USD";
2910
+ var CURRENCY_CHOICES = ["USD", "EUR", "GBP", "CAD", "AUD", "INR", "AED", "SGD", "JPY", "CHF", "SEK", "NZD", "ZAR", "MXN", "BRL"];
2826
2911
  var displayLocale;
2827
2912
  function setMoneyLocale(locale) {
2828
2913
  displayLocale = locale;
@@ -2903,7 +2988,7 @@ var en = {
2903
2988
  "picker.viewFromSeat": "View from seat {label}",
2904
2989
  "picker.real360": "REAL 360\xB0",
2905
2990
  "picker.preview": "PREVIEW",
2906
- "picker.sightline": "\u2248 {m} m to stage \xB7 clear sightline",
2991
+ "picker.sightline": "\u2248 {m} m to stage",
2907
2992
  "picker.panorama360": "360\xB0 venue photo",
2908
2993
  "picker.illustrationCaption": "illustration \xB7 \u2248 {m} m from stage",
2909
2994
  "picker.restrictedView": "Restricted view",
@@ -2911,7 +2996,10 @@ var en = {
2911
2996
  "picker.premiumSeat": "Premium seat",
2912
2997
  "picker.hideLimitedView": "Hide limited-view seats",
2913
2998
  "picker.bestSeatsPremium": "Best seats",
2914
- "picker.premiumFallbackNote": "No premium block of {count} \u2014 showing best overall"
2999
+ "picker.premiumFallbackNote": "No premium block of {count} \u2014 showing best overall",
3000
+ "picker.loading3d": "Building the 3D venue\u2026",
3001
+ "picker.unavailable3d": "3D could not start. The seat map is still available.",
3002
+ "picker.jumpToSection": "Jump to section"
2915
3003
  };
2916
3004
 
2917
3005
  // src/i18n/index.ts
@@ -5854,7 +5942,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5854
5942
  this.freeTextById.set(obj.id, { node: label, background: "#232c40", kind: "table" });
5855
5943
  }
5856
5944
  renderText(obj) {
5857
- const background = this.canvasBackground;
5945
+ const background = obj.background?.color ?? this.canvasBackground;
5858
5946
  const preferredInk = obj.color ?? this.theme.textColor ?? DEF_TEXT;
5859
5947
  const iconPath = obj.semanticKind === "icon" ? venueIconPath(obj.iconKey) : void 0;
5860
5948
  if (iconPath) {
@@ -5878,23 +5966,54 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5878
5966
  this.bgLayer.add(iconNode);
5879
5967
  return;
5880
5968
  }
5969
+ const padding = obj.background?.padding ?? 0;
5970
+ const opacity = obj.opacity ?? 1;
5881
5971
  const node = new import_Text.Text({
5882
5972
  x: obj.position.x,
5883
5973
  y: obj.position.y,
5884
5974
  text: obj.text,
5885
5975
  fontSize: obj.fontSize,
5886
5976
  rotation: obj.rotation,
5887
- fontStyle: konvaFontStyle(obj.bold, obj.italic, "normal"),
5977
+ // Keep the buyer glyph metrics identical to Designer: wrapping must not
5978
+ // gain or lose a line when the authored chart is published.
5979
+ fontStyle: konvaFontStyle(obj.bold, obj.italic, "600"),
5888
5980
  // Authored ink remains preferred, but an embed/theme surface can change
5889
5981
  // the actual canvas. Fail over to readable black/white instead of
5890
5982
  // painting an otherwise valid caption invisibly on that active surface.
5891
5983
  fill: stateAwareBookableLabelInk(background, preferredInk),
5892
5984
  fontFamily: obj.fontFamily ?? this.labelFont(),
5985
+ ...obj.width != null ? { width: obj.width } : {},
5986
+ align: obj.align ?? "left",
5987
+ lineHeight: obj.lineHeight ?? 1.2,
5988
+ letterSpacing: obj.letterSpacing ?? 0,
5989
+ textDecoration: obj.underline ? "underline" : "",
5990
+ padding,
5991
+ opacity,
5992
+ wrap: "word",
5893
5993
  listening: false,
5894
- perfectDrawEnabled: false
5994
+ perfectDrawEnabled: false,
5995
+ name: "buyer-free-text"
5895
5996
  });
5997
+ let backdrop;
5998
+ if (obj.background) {
5999
+ backdrop = new import_Rect.Rect({
6000
+ x: obj.position.x,
6001
+ y: obj.position.y,
6002
+ width: node.width(),
6003
+ height: node.height(),
6004
+ rotation: obj.rotation,
6005
+ fill: obj.background.color,
6006
+ opacity: opacity * (obj.background.opacity ?? 0.9),
6007
+ cornerRadius: obj.background.cornerRadius ?? 4,
6008
+ listening: false,
6009
+ perfectDrawEnabled: false,
6010
+ name: "buyer-text-background"
6011
+ });
6012
+ this.bgLayer.add(backdrop);
6013
+ }
5896
6014
  this.freeTextById.set(obj.id, {
5897
6015
  node,
6016
+ backdrop,
5898
6017
  background,
5899
6018
  kind: "free-text"
5900
6019
  });
@@ -7674,10 +7793,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
7674
7793
  }
7675
7794
  updateFreeTextVisibility() {
7676
7795
  const effectiveScale = this.exportMode ? Math.max(this.effScale(), LABEL_SCALE) : this.effScale();
7677
- for (const { objectId, node, categoryKey, kind } of this.freeTextById.values()) {
7796
+ for (const { objectId, node, backdrop, categoryKey, kind } of this.freeTextById.values()) {
7678
7797
  const gaDimmed = categoryKey != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaCategoryDimmed(categoryKey);
7679
7798
  const gaOverviewHidden = objectId != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaById.get(objectId)?.sectionId != null && effectiveScale < CACHE_THRESHOLD;
7680
- node.visible(!gaDimmed && !gaOverviewHidden && isBookableLabelLegibleAtScale(node.fontSize(), effectiveScale));
7799
+ const visible = !gaDimmed && !gaOverviewHidden && isBookableLabelLegibleAtScale(node.fontSize(), effectiveScale);
7800
+ node.visible(visible);
7801
+ backdrop?.visible(visible);
7681
7802
  }
7682
7803
  for (const { node, fontSize } of this.iconNodeById.values()) {
7683
7804
  node.visible(isBookableLabelLegibleAtScale(fontSize, effectiveScale));
@@ -8164,6 +8285,11 @@ function groupedTableInventory(doc, modelVersion) {
8164
8285
  // src/picker/PickerController.ts
8165
8286
  var DEFAULT_MAX_SELECTION = 10;
8166
8287
  var MAX_BACKOFF_MS = 15e3;
8288
+ var MAP_THEME_KEYS = ["background", "rowLabelColor", "textColor", "selectionColor"];
8289
+ function sameMapTheme(a, b) {
8290
+ if (!a || !b) return !a && !b;
8291
+ return MAP_THEME_KEYS.every((key) => a[key] === b[key]);
8292
+ }
8167
8293
  function errInfo(err) {
8168
8294
  const e = err ?? {};
8169
8295
  return { status: e.status, conflicts: e.conflicts, reason: e.reason };
@@ -8179,6 +8305,8 @@ var PickerController = class {
8179
8305
  this._doc = null;
8180
8306
  /** Section/zone ids hidden from buyers this event (3.3) — seats vanish, not grey. */
8181
8307
  this.hidden = /* @__PURE__ */ new Set();
8308
+ /** Host overrides folded over the chart's own theme in `visibleDoc()`. */
8309
+ this.mapTheme = null;
8182
8310
  /** Section/zone ids in the `closed` event-state (Phase 2) — seats stay, greyed
8183
8311
  * + not pickable. Kept separate from `hidden` (which strips them). */
8184
8312
  this.closedSections = /* @__PURE__ */ new Set();
@@ -8214,6 +8342,16 @@ var PickerController = class {
8214
8342
  // hold state
8215
8343
  this.hold_ = null;
8216
8344
  this.liveStatuses = /* @__PURE__ */ new Map();
8345
+ /**
8346
+ * What a label absent from `liveStatuses` is.
8347
+ *
8348
+ * `free` while the verbose HTTP map is the source (it names every unit), but a
8349
+ * compact realtime frame names only its exceptions — so on a mostly-sold event
8350
+ * the seats NOT listed are the booked ones. Read through {@link statusOf};
8351
+ * a bare `liveStatuses.get()` silently answers `undefined` for the majority of
8352
+ * a sold-out venue.
8353
+ */
8354
+ this.liveDefault = "free";
8217
8355
  this.currency = "USD";
8218
8356
  this.expiryTimer = null;
8219
8357
  /** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
@@ -8222,13 +8360,66 @@ var PickerController = class {
8222
8360
  this.api = options.transport;
8223
8361
  this.key = options.eventKey;
8224
8362
  this.maxSelection = options.maxSelection ?? DEFAULT_MAX_SELECTION;
8363
+ this.mapTheme = options.mapTheme ?? null;
8225
8364
  }
8226
8365
  get doc() {
8227
8366
  return this._doc;
8228
8367
  }
8229
- /** The chart with hidden sections' seats removed — what buyers actually see. */
8368
+ /**
8369
+ * The chart with hidden sections' seats removed, and the host's map theme
8370
+ * folded over the chart's own — what buyers actually see.
8371
+ *
8372
+ * THE THEME IS MERGED HERE rather than pushed at the renderer separately
8373
+ * because the renderer reads its theme out of the document it is given. One
8374
+ * merge point means every rebuild — a hidden section arriving mid-sale, a
8375
+ * floor switch, a theme change — repaints from the same resolved theme
8376
+ * instead of from whichever of two sources ran last.
8377
+ *
8378
+ * The chart's theme is the BASE and only named keys are overwritten, so a
8379
+ * host that overrides the ground does not thereby erase the organizer's
8380
+ * `seatScale`, `fontFamily`, logo or badge entitlement.
8381
+ */
8230
8382
  visibleDoc() {
8231
- return this._doc ? applyHidden(this._doc, this.hidden) : { objects: [] };
8383
+ if (!this._doc) return { objects: [] };
8384
+ const doc = applyHidden(this._doc, this.hidden);
8385
+ if (!this.mapTheme) return doc;
8386
+ const theme = { ...doc.theme ?? {} };
8387
+ for (const key of MAP_THEME_KEYS) {
8388
+ const value = this.mapTheme[key];
8389
+ if (value) theme[key] = value;
8390
+ }
8391
+ return { ...doc, theme };
8392
+ }
8393
+ /**
8394
+ * Re-ink the drawn map, in place, without losing the buyer's place in it.
8395
+ *
8396
+ * WHY THIS IS A SETTER AND NOT A MOUNT OPTION. On a templated event page the
8397
+ * palette is not known when the map mounts: the page config is a separate
8398
+ * cached read and the map is explicitly not allowed to wait for it. The
8399
+ * alternative — remounting the widget once the palette lands — would destroy
8400
+ * a hold the buyer may already be holding.
8401
+ *
8402
+ * The rebuild is the SAME recipe `render()` uses (setChart → colorblind →
8403
+ * closed sections → statuses), because setChart resets all four; the one
8404
+ * addition is restoring the selection, which `render()` has no need to do.
8405
+ * Statuses are repainted from the map already in memory rather than re-read,
8406
+ * so this costs no round trip and cannot flash a stale seat.
8407
+ *
8408
+ * Returns whether anything actually changed, so a caller may call it freely.
8409
+ */
8410
+ setMapTheme(theme) {
8411
+ if (sameMapTheme(this.mapTheme, theme ?? null)) return false;
8412
+ this.mapTheme = theme ?? null;
8413
+ const r = this.renderer;
8414
+ if (!r) return true;
8415
+ const selected = r.getSelection().map((seat) => seat.id);
8416
+ const floorId = this.getActiveFloorId();
8417
+ r.setChart(this.visibleDoc(), floorId ? { floorId } : void 0);
8418
+ if (this.opts.colorblindSafe) r.setColorblindSafe?.(true);
8419
+ if (this.closedSections.size) r.setClosedSections?.([...this.closedSections]);
8420
+ this.applySeatsMap(Object.fromEntries(this.liveStatuses), this.liveDefault);
8421
+ if (selected.length) r.select?.(selected);
8422
+ return true;
8232
8423
  }
8233
8424
  /** Adopt a new hidden set; rebuild the visible chart only if it differs. */
8234
8425
  syncHidden(ids) {
@@ -8417,12 +8608,14 @@ var PickerController = class {
8417
8608
  }
8418
8609
  this.renderer = renderer;
8419
8610
  let seats = null;
8611
+ let seatsDefault = "free";
8420
8612
  let closedIds = [];
8421
8613
  try {
8422
8614
  const objs = await this.api.objects(this.key);
8423
8615
  this.hidden = new Set(objs.hidden ?? []);
8424
8616
  closedIds = (objs.closed ?? []).filter((x) => typeof x === "string");
8425
8617
  seats = objs.seats;
8618
+ seatsDefault = objs.default ?? "free";
8426
8619
  } catch {
8427
8620
  }
8428
8621
  if (this.closed) {
@@ -8434,7 +8627,7 @@ var PickerController = class {
8434
8627
  if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
8435
8628
  this.closedSections = new Set(closedIds);
8436
8629
  if (closedIds.length) renderer.setClosedSections?.(closedIds);
8437
- if (seats) this.applySeatsMap(seats);
8630
+ if (seats) this.applySeatsMap(seats, seatsDefault);
8438
8631
  this.attachVisibilityListener();
8439
8632
  this.connect();
8440
8633
  return {
@@ -8443,6 +8636,7 @@ var PickerController = class {
8443
8636
  eventName: res.event.name,
8444
8637
  venue: res.event.venue,
8445
8638
  startsAt: res.event.startsAt,
8639
+ timezone: res.event.timezone ?? null,
8446
8640
  currency,
8447
8641
  // 'test' ⇒ sandbox event: hosts render a TEST MODE ribbon (Phase 11).
8448
8642
  mode: res.event.mode ?? "live"
@@ -8751,7 +8945,7 @@ var PickerController = class {
8751
8945
  ...area.displayLabel ? { displayLabel: area.displayLabel } : {},
8752
8946
  ...area.displayType ? { displayType: area.displayType } : {},
8753
8947
  capacity: Math.max(0, Math.floor(area.capacity)),
8754
- available: gaUnitLabels(area).filter((label) => (this.liveStatuses.get(label) ?? "free") === "free").length,
8948
+ available: gaUnitLabels(area).filter((label) => this.statusOf(label) === "free").length,
8755
8949
  categoryKey: area.categoryKey,
8756
8950
  tiers: category?.tiers,
8757
8951
  price: category?.tiers?.[0]?.price ?? category?.price ?? 0,
@@ -8765,7 +8959,7 @@ var PickerController = class {
8765
8959
  if (!doc || !Number.isFinite(qty) || qty < 1) return null;
8766
8960
  const area = gaAreasOf(doc).find((candidate) => candidate.id === areaId);
8767
8961
  if (!area) return null;
8768
- const labels = gaUnitLabels(area).filter((label) => !this.hold_?.labels.includes(label) && (this.liveStatuses.get(label) ?? "free") === "free").slice(0, Math.floor(qty));
8962
+ const labels = gaUnitLabels(area).filter((label) => !this.hold_?.labels.includes(label) && this.statusOf(label) === "free").slice(0, Math.floor(qty));
8769
8963
  if (labels.length !== Math.floor(qty)) return null;
8770
8964
  const selections = /* @__PURE__ */ new Map();
8771
8965
  for (const item of this.hold_?.items ?? []) selections.set(item.label, {
@@ -9460,7 +9654,7 @@ var PickerController = class {
9460
9654
  try {
9461
9655
  const snap = await this.api.objects(this.key);
9462
9656
  if (this.hold_?.holdId !== hold.holdId) return;
9463
- this.applySeatsMap(snap.seats);
9657
+ this.applySeatsMap(snap.seats, snap.default ?? "free");
9464
9658
  } catch {
9465
9659
  if (this.hold_?.holdId === hold.holdId) {
9466
9660
  this.expiryTimer = setTimeout(() => void this.expireActiveHold(), 2e3);
@@ -9468,18 +9662,30 @@ var PickerController = class {
9468
9662
  return;
9469
9663
  }
9470
9664
  if (this.hold_?.holdId !== hold.holdId) return;
9471
- if (hold.labels.some((label) => this.liveStatuses.get(label) === "held")) {
9665
+ if (hold.labels.some((label) => this.statusOf(label) === "held")) {
9472
9666
  this.expiryTimer = setTimeout(() => void this.expireActiveHold(), 1e3);
9473
9667
  return;
9474
9668
  }
9475
9669
  this.clearHold();
9476
9670
  this.opts.onHoldExpired?.();
9477
9671
  }
9478
- applySeatsMap(seats) {
9672
+ /**
9673
+ * Repaint from an authoritative map of unit statuses.
9674
+ *
9675
+ * `defaultStatus` is what every unit NOT named in `seats` is. The verbose HTTP
9676
+ * route names every unit and leaves it at `free`; the compact realtime frame
9677
+ * names only the exceptions and states its own default, which on a mostly-sold
9678
+ * event is `booked` rather than `free`. Applying a compact map while assuming
9679
+ * `free` would paint a sold-out stadium as fully available, so the default is
9680
+ * a parameter rather than a constant.
9681
+ */
9682
+ applySeatsMap(seats, defaultStatus = "free") {
9479
9683
  const r = this.renderer;
9480
9684
  if (!r) return;
9481
9685
  this.liveStatuses = new Map(Object.entries(seats));
9482
- if (this.allIds.length) r.setStatus(this.allIds, "free");
9686
+ this.liveDefault = defaultStatus;
9687
+ const defaultSeatStatus = mapStatus(defaultStatus);
9688
+ if (this.allIds.length) r.setStatus(this.allIds, defaultSeatStatus);
9483
9689
  r.setOwnedHold?.(
9484
9690
  this.idsForLabels(this.hold_?.labels ?? [])
9485
9691
  );
@@ -9487,15 +9693,19 @@ var PickerController = class {
9487
9693
  for (const [label, st] of Object.entries(seats)) {
9488
9694
  byStatus[mapStatus(st)].push(...this.idsForLabel(label));
9489
9695
  }
9490
- ["held", "booked", "not_for_sale"].forEach((st) => {
9491
- if (byStatus[st].length) r.setStatus(byStatus[st], st);
9696
+ ["free", "held", "booked", "not_for_sale"].forEach((st) => {
9697
+ if (st !== defaultSeatStatus && byStatus[st].length) r.setStatus(byStatus[st], st);
9492
9698
  });
9493
9699
  this.clearBookedHoldIfSettled();
9494
9700
  this.opts.onStatusChange?.();
9495
9701
  }
9702
+ /** The live status of one unit, honouring the projection's default. */
9703
+ statusOf(label) {
9704
+ return this.liveStatuses.get(label) ?? this.liveDefault;
9705
+ }
9496
9706
  clearBookedHoldIfSettled() {
9497
9707
  const hold = this.hold_;
9498
- if (hold?.labels.every((label) => this.liveStatuses.get(label) === "booked")) {
9708
+ if (hold?.labels.every((label) => this.statusOf(label) === "booked")) {
9499
9709
  this.clearHold();
9500
9710
  this.resetTableQuantitiesForLabels(hold.labels);
9501
9711
  }
@@ -9503,7 +9713,7 @@ var PickerController = class {
9503
9713
  async resnapshot() {
9504
9714
  try {
9505
9715
  const objs = await this.api.objects(this.key);
9506
- this.applySeatsMap(objs.seats);
9716
+ this.applySeatsMap(objs.seats, objs.default ?? "free");
9507
9717
  } catch {
9508
9718
  }
9509
9719
  }
@@ -9561,6 +9771,7 @@ var PickerController = class {
9561
9771
  realtimeSink() {
9562
9772
  return {
9563
9773
  applyStatuses: (changes) => this.applyStatusChanges(changes),
9774
+ applyProjection: (projection) => this.applySeatsMap(projection.exceptions, projection.default),
9564
9775
  resync: () => this.resnapshot(),
9565
9776
  onSections: (hidden, closed) => {
9566
9777
  const rebuilt = this.syncHidden(hidden);
@@ -10863,6 +11074,7 @@ async function loadLocale(code) {
10863
11074
  ACCESSIBILITY_RING_COLOR,
10864
11075
  ACCESSIBILITY_TYPES,
10865
11076
  CHART_STORAGE_KEY,
11077
+ CURRENCY_CHOICES,
10866
11078
  DEFAULT_CURRENCY,
10867
11079
  LABEL_STYLE_MAX_SIZE,
10868
11080
  LABEL_STYLE_MIN_SIZE,