@seatlayer/core 0.27.0 → 0.28.1

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/index.cjs CHANGED
@@ -1313,7 +1313,30 @@ function isBookableLabelLegibleAtScale(fontSize, effectiveScale) {
1313
1313
  return fontSize * effectiveScale >= MIN_VISIBLE_BOOKABLE_LABEL_PX;
1314
1314
  }
1315
1315
  var ACCESS_GLYPH_VIEWBOX = 24;
1316
- 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";
1316
+ var ACCESS_TYPE_GLYPH_PATHS = {
1317
+ // ISO-style wheelchair: seated figure (head + L-shaped body) over a ring wheel.
1318
+ wheelchair: "M7.8 4.5a2.2 2.2 0 1 0 4.4 0a2.2 2.2 0 1 0 -4.4 0zM8 7L10.5 7L10.5 15L8 15ZM8 12.5L18 12.5L18 15L8 15ZM16 15L18 15L18 18L16 18ZM5.5 16a5.5 5.5 0 1 0 11 0a5.5 5.5 0 1 0 -11 0zM8 16a3 3 0 1 1 6 0a3 3 0 1 1 -6 0z",
1319
+ // Companion: two overlapping head-and-shoulders figures (seat + a guest).
1320
+ companion: "M5.5 6.5a3 3 0 1 0 6 0a3 3 0 1 0 -6 0zM4.5 18.5L6 11.5L11 11.5L12.5 18.5ZM12.5 7.5a3 3 0 1 0 6 0a3 3 0 1 0 -6 0zM11.5 19L13 12.5L18 12.5L19.5 19Z",
1321
+ // Semi-ambulatory: walking figure leaning on a cane at its right side.
1322
+ "semi-ambulatory": "M8.3 3.8a2.2 2.2 0 1 0 4.4 0a2.2 2.2 0 1 0 -4.4 0zM9 6.2L12 6.6L11 13.5L8.6 13L9 6.2ZM8.6 13L11 13L9.5 21L7 21ZM10.4 13L12.6 13L13.6 21L11.2 21ZM11.2 7.2L14 8L14.6 9.6L11.9 8.9ZM14.4 6.8L15.9 6.8L16.4 21L14.8 21Z",
1323
+ // Hearing: a bold ear silhouette (assistive listening).
1324
+ hearing: "M15.5 3.5C9 2.2 5.5 6 5.5 11.5C5.5 16 8 19.5 10.5 21C13.2 22.6 16.4 20.8 15.4 18C14.8 16.4 12.9 16.1 13 14C13.1 11.9 15.8 11.4 15.8 8.5C15.8 5.4 13 4 15.5 3.5Z",
1325
+ // CART captions: a rounded caption bubble with two bold caption bars cut out.
1326
+ cart: "M5.5 6L18.5 6a2.5 2.5 0 0 1 2.5 2.5L21 15.5a2.5 2.5 0 0 1 -2.5 2.5L11 18L7 20.5L7.5 18L5.5 18a2.5 2.5 0 0 1 -2.5 -2.5L3 8.5a2.5 2.5 0 0 1 2.5 -2.5zM6.5 11L17.5 11L17.5 12.6L6.5 12.6zM6.5 14L17.5 14L17.5 15.6L6.5 15.6z",
1327
+ // Sign language: a raised open hand (palm with four fingers and a thumb).
1328
+ "sign-language": "M7 11L17 11L17 18a2 2 0 0 1 -2 2L9 20a2 2 0 0 1 -2 -2zM7.6 5L9.4 5L9.4 12L7.6 12zM9.9 3.5L11.7 3.5L11.7 12L9.9 12zM12.3 3.5L14.1 3.5L14.1 12L12.3 12zM14.6 5L16.4 5L16.4 12L14.6 12zM7.2 12.5L5 10.2L6.4 8.9L8.6 11.2Z",
1329
+ // Plus-size: a broad-shouldered wide figure.
1330
+ "plus-size": "M9.4 4.3a2.6 2.6 0 1 0 5.2 0a2.6 2.6 0 1 0 -5.2 0zM5 20L4 13C4 10.5 7 9.5 12 9.5C17 9.5 20 10.5 20 13L19 20Z",
1331
+ // Lift-up armrest: a chair side-profile with an up arrow over the armrest.
1332
+ "lift-armrest": "M5 6L8 6L8 19L5 19zM5 16L18 16L18 19L5 19zM8 12L17 12L17 14.5L8 14.5zM14 7L16 7L16 12L14 12zM12 7L18 7L15 3Z"
1333
+ };
1334
+ var ACCESS_GLYPH_PATH = ACCESS_TYPE_GLYPH_PATHS.wheelchair;
1335
+ function accessGlyphPath(accessibility) {
1336
+ if (!accessibility?.length) return null;
1337
+ const primary = accessibility.includes("wheelchair") ? "wheelchair" : accessibility[0];
1338
+ return ACCESS_TYPE_GLYPH_PATHS[primary] ?? null;
1339
+ }
1317
1340
  function bookableMarkerLabel(publicLabel) {
1318
1341
  return /-(\d{1,5})$/.exec(publicLabel)?.[1] ?? publicLabel;
1319
1342
  }
@@ -1354,6 +1377,15 @@ function stateAwareBookableLabelInk(fill, preferred) {
1354
1377
  if (darkContrast === 0 && lightContrast === 0) return preferred;
1355
1378
  return darkContrast >= lightContrast ? DARK_BOOKABLE_LABEL_INK : LIGHT_BOOKABLE_LABEL_INK;
1356
1379
  }
1380
+ var GLYPH_CONTRAST = 3;
1381
+ function accessGlyphInk(fill) {
1382
+ const white = renderedTextContrast("#ffffff", fill);
1383
+ if (white != null && white >= GLYPH_CONTRAST) return "#ffffff";
1384
+ const darkContrast = renderedTextContrast(DARK_BOOKABLE_LABEL_INK, fill) ?? 0;
1385
+ const lightContrast = renderedTextContrast(LIGHT_BOOKABLE_LABEL_INK, fill) ?? 0;
1386
+ if (darkContrast === 0 && lightContrast === 0) return "#ffffff";
1387
+ return darkContrast >= lightContrast ? DARK_BOOKABLE_LABEL_INK : LIGHT_BOOKABLE_LABEL_INK;
1388
+ }
1357
1389
 
1358
1390
  // src/core/renderedQuality.ts
1359
1391
  var RENDERED_QUALITY_REPORT_VERSION = 3;
@@ -1880,6 +1912,12 @@ var VENUE_ICONS = [
1880
1912
  group: "facilities",
1881
1913
  path: "M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14 M16 16l5 5 M9 8.5a2 2 0 1 1 3 1.7c-.8.5-1 .8-1 1.8 M11 15h.01"
1882
1914
  },
1915
+ {
1916
+ key: "restrooms",
1917
+ label: "Restrooms",
1918
+ group: "facilities",
1919
+ path: "M6 3h12a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z M12 8a2 2 0 1 0 0 4 2 2 0 0 0 0-4 M9 18v-3.5a3 3 0 0 1 6 0V18"
1920
+ },
1883
1921
  {
1884
1922
  key: "charging",
1885
1923
  label: "Charging point",
@@ -1929,6 +1967,18 @@ var VENUE_ICONS = [
1929
1967
  group: "food-drink",
1930
1968
  path: "M6 8h12l-1 12H7L6 8Z M9 8V6a3 3 0 0 1 6 0v2"
1931
1969
  },
1970
+ {
1971
+ key: "screen",
1972
+ label: "Screen",
1973
+ group: "facilities",
1974
+ path: "M3 4h18v12H3V4Z M9 20h6 M12 16v4 M10 8l4 2-4 2V8Z"
1975
+ },
1976
+ {
1977
+ key: "sound-booth",
1978
+ label: "Sound booth",
1979
+ group: "facilities",
1980
+ path: "M6 4v16 M6 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4 M12 4v16 M12 8a2 2 0 1 0 0 4 2 2 0 0 0 0-4 M18 4v16 M18 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4"
1981
+ },
1932
1982
  // ---- Getting around ----------------------------------------------------
1933
1983
  {
1934
1984
  key: "entrance",
@@ -4587,7 +4637,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
4587
4637
  this.accessRingById.set(seat.id, ring);
4588
4638
  target.add(ring);
4589
4639
  }
4590
- if (seat.accessibility?.includes("wheelchair")) {
4640
+ if (accessGlyphPath(seat.accessibility ?? [])) {
4591
4641
  const glyph = this.buildAccessGlyph(seat, typeof c.fill() === "string" ? c.fill() : "");
4592
4642
  this.accessGlyphById.set(seat.id, glyph);
4593
4643
  target.add(glyph);
@@ -4661,12 +4711,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
4661
4711
  return new import_Path.Path({
4662
4712
  x: seat.x,
4663
4713
  y: seat.y,
4664
- data: ACCESS_GLYPH_PATH,
4714
+ // Non-null: buildAccessGlyph is only called for seats the gate confirmed
4715
+ // resolve to a primary-accommodation glyph.
4716
+ data: accessGlyphPath(seat.accessibility ?? []) ?? "",
4665
4717
  offsetX: ACCESS_GLYPH_VIEWBOX / 2,
4666
4718
  offsetY: ACCESS_GLYPH_VIEWBOX / 2,
4667
4719
  scaleX: k,
4668
4720
  scaleY: k,
4669
- fill: stateAwareBookableLabelInk(seatFill, "#ffffff"),
4721
+ fill: accessGlyphInk(seatFill),
4670
4722
  listening: false,
4671
4723
  visible: false,
4672
4724
  perfectDrawEnabled: false,
@@ -6794,7 +6846,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6794
6846
  if (shouldCache && !this.cached) {
6795
6847
  this.cacheSeatLayer();
6796
6848
  } else if (!shouldCache && (this.cached || !this.seatLayer.listening())) {
6797
- if (this.cached) this.seatLayer.clearCache();
6849
+ if (this.cached) this.releaseSeatLayerBitmap();
6798
6850
  this.seatLayer.listening(true);
6799
6851
  this.cached = false;
6800
6852
  this.seatLayer.batchDraw();
@@ -6814,6 +6866,28 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6814
6866
  this.rebuildSeatCache();
6815
6867
  this.seatLayer.batchDraw();
6816
6868
  }
6869
+ /**
6870
+ * Drop the seat-layer overview bitmap while PRESERVING every descendant's
6871
+ * cached local transform + clientRect.
6872
+ *
6873
+ * Konva's public `Layer.clearCache()` releases the bitmap and then runs a deep
6874
+ * `_clearSelfAndDescendantCache()` that clears those caches on all 14k seat
6875
+ * nodes. Recomputing them on the single frame the melt first reveals a section's
6876
+ * live seats is the ~480ms first-drill stall (OV-80). A camera glide only marks
6877
+ * each seat's ABSOLUTE_TRANSFORM dirty (recomputed lazily at ordinary pan cost)
6878
+ * and never moves a seat's LOCAL transform, so the local caches stay valid —
6879
+ * keeping them lets the first melt paint at pan cost, matching the ~17ms of
6880
+ * every subsequent glide on the same section. Any edit that actually moves a
6881
+ * seat invalidates its own transform cache through the attr setter, so only
6882
+ * still-valid caches survive here.
6883
+ */
6884
+ releaseSeatLayerBitmap() {
6885
+ const layer = this.seatLayer;
6886
+ const entry = layer._cache.get("canvas");
6887
+ if (!entry) return;
6888
+ import_Core.Konva.Util.releaseCanvas(entry.scene._canvas, entry.filter._canvas, entry.hit._canvas);
6889
+ layer._cache.delete("canvas");
6890
+ }
6817
6891
  /**
6818
6892
  * SYNCHRONOUS repaint that bypasses requestAnimationFrame — see the
6819
6893
  * ISeatmapRenderer.forceDraw() contract. Chrome pauses rAF (and therefore
@@ -6832,7 +6906,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6832
6906
  if (this.effScale() < CACHE_THRESHOLD) {
6833
6907
  this.rebuildSeatCache();
6834
6908
  } else if (this.cached) {
6835
- this.seatLayer.clearCache();
6909
+ this.releaseSeatLayerBitmap();
6836
6910
  this.seatLayer.listening(true);
6837
6911
  this.cached = false;
6838
6912
  }
@@ -7353,6 +7427,36 @@ var PickerController = class {
7353
7427
  const group = this.groupedTableBySeatId.get(seatIdOrLabel) ?? this.groupedTablesByLabel.get(seatIdOrLabel);
7354
7428
  return group ? this.toTableSeat(group) : null;
7355
7429
  }
7430
+ /**
7431
+ * Buyer-facing name + type word for a checkout line item, resolved from the
7432
+ * SAME author overrides the tray/tooltip already render. `label` stays the
7433
+ * booking identity; a host builds its own order summary from `displayLabel`/
7434
+ * `displayType` so the buyer sees the designer's copy, not the internal label.
7435
+ * Absent fields = no override (host falls back to `label`).
7436
+ */
7437
+ lineItemDisplay(item) {
7438
+ if (item.objectType === "ga") {
7439
+ const doc = this.visibleDoc();
7440
+ const area = gaAreasOf(doc).find((candidate) => candidate.id === item.objectId);
7441
+ return {
7442
+ ...area?.displayLabel ? { displayLabel: area.displayLabel } : {},
7443
+ ...area?.displayType ? { displayType: area.displayType } : {}
7444
+ };
7445
+ }
7446
+ if (item.objectType === "table") {
7447
+ const group = this.groupedTablesByLabel.get(item.label);
7448
+ return {
7449
+ ...group?.displayLabel && group.displayLabel !== group.label ? { displayLabel: group.displayLabel } : {},
7450
+ ...group?.displayType ? { displayType: group.displayType } : {}
7451
+ };
7452
+ }
7453
+ const seat = this.labelToSeat.get(item.label);
7454
+ const context = seat ? this.seatContext.get(seat.id) : void 0;
7455
+ return {
7456
+ ...seat?.displayLabel ? { displayLabel: seat.displayLabel } : {},
7457
+ ...context?.displayType ? { displayType: context.displayType } : {}
7458
+ };
7459
+ }
7356
7460
  /** Fetch chart, build label maps, mount the renderer, seed statuses, go live. */
7357
7461
  async render(host) {
7358
7462
  if (this.renderer) return null;