@seatlayer/core 0.1.3 → 0.1.4

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.d.cts CHANGED
@@ -425,6 +425,12 @@ interface ISeatmapRenderer {
425
425
  setCategoryHighlight?(key: string | null): void;
426
426
  /** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */
427
427
  setDimmedSections?(ids: string[] | null): void;
428
+ /**
429
+ * Colorblind-safe mode: category hues switch to an Okabe-Ito palette and
430
+ * booked seats render hollow (a non-color cue), so seat state never relies
431
+ * on hue alone. Off (the default) renders exactly as before.
432
+ */
433
+ setColorblindSafe?(on: boolean): void;
428
434
  /**
429
435
  * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the "3D"
430
436
  * view (affine skew/rotate + elevation lift), hit-testing preserved in screen
@@ -645,6 +651,10 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
645
651
  private statusById;
646
652
  private catColor;
647
653
  private theme;
654
+ /** Colorblind-safe mode (Okabe-Ito hues + hollow booked seats). */
655
+ private colorblind;
656
+ /** Category order from the doc — the stable index into the CB palette. */
657
+ private catOrder;
648
658
  private selection;
649
659
  private selectionRings;
650
660
  private hoverRing;
@@ -797,8 +807,15 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
797
807
  private renderSeats;
798
808
  /** A booth renders as a click-selectable rounded block (dims from the doc). */
799
809
  private renderBoothUnit;
810
+ /** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
811
+ private seatBaseColor;
800
812
  /** Apply fill/stroke/opacity for a seat's current status + selection. */
801
813
  private paintSeat;
814
+ /**
815
+ * Colorblind-safe mode: swap category hues for the Okabe-Ito palette and
816
+ * render booked seats hollow. Off restores the exact default rendering.
817
+ */
818
+ setColorblindSafe(on: boolean): void;
802
819
  /**
803
820
  * Dim the seats of these section/zone ids (organizer manager use) so held-back
804
821
  * inventory is visually distinct on the canvas without hiding it. `null`/empty
@@ -1019,6 +1036,12 @@ interface PickerCallbacks extends RendererCallbacks {
1019
1036
  onSectionFocus?: (summary: SectionSummary | null) => void;
1020
1037
  /** A deck was tapped in the 3D all-floors overview — host enters that floor in 2D. */
1021
1038
  onDeckTap?: (floorId: string) => void;
1039
+ /**
1040
+ * Non-blocking selection advice (localized): currently the orphan-seat hint —
1041
+ * the selection would strand a single free seat between unavailable
1042
+ * neighbors. `null` clears a previously shown hint. Never prevents anything.
1043
+ */
1044
+ onHint?: (message: string | null) => void;
1022
1045
  onError?: (err: unknown) => void;
1023
1046
  }
1024
1047
  interface PickerOptions extends PickerCallbacks {
@@ -1031,6 +1054,8 @@ interface PickerOptions extends PickerCallbacks {
1031
1054
  currency?: string;
1032
1055
  /** Flash a pulse when a seat we didn't touch goes free→taken (live-activity cue). */
1033
1056
  flashOnLiveChange?: boolean;
1057
+ /** Start in colorblind-safe rendering (Okabe-Ito hues + hollow booked seats). */
1058
+ colorblindSafe?: boolean;
1034
1059
  }
1035
1060
  declare class PickerController {
1036
1061
  private readonly opts;
@@ -1162,6 +1187,17 @@ declare class PickerController {
1162
1187
  /** PickerSeats (with chosen tier) for a set of held labels. */
1163
1188
  private seatsForLabels;
1164
1189
  private emitSelectionChange;
1190
+ /** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
1191
+ private hintShown;
1192
+ /**
1193
+ * Orphan-seat advice on manual selection: when the buyer's current picks
1194
+ * strand one (or more) single free seats between unavailable same-row
1195
+ * neighbors, surface a localized, non-blocking hint. Cleared (null) as soon
1196
+ * as the selection stops stranding anyone.
1197
+ */
1198
+ private emitOrphanHint;
1199
+ /** Toggle colorblind-safe rendering at runtime (see PickerOptions.colorblindSafe). */
1200
+ setColorblindSafe(on: boolean): void;
1165
1201
  private emitError;
1166
1202
  private holdCovers;
1167
1203
  private handle409Conflicts;
package/dist/index.d.ts CHANGED
@@ -425,6 +425,12 @@ interface ISeatmapRenderer {
425
425
  setCategoryHighlight?(key: string | null): void;
426
426
  /** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */
427
427
  setDimmedSections?(ids: string[] | null): void;
428
+ /**
429
+ * Colorblind-safe mode: category hues switch to an Okabe-Ito palette and
430
+ * booked seats render hollow (a non-color cue), so seat state never relies
431
+ * on hue alone. Off (the default) renders exactly as before.
432
+ */
433
+ setColorblindSafe?(on: boolean): void;
428
434
  /**
429
435
  * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the "3D"
430
436
  * view (affine skew/rotate + elevation lift), hit-testing preserved in screen
@@ -645,6 +651,10 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
645
651
  private statusById;
646
652
  private catColor;
647
653
  private theme;
654
+ /** Colorblind-safe mode (Okabe-Ito hues + hollow booked seats). */
655
+ private colorblind;
656
+ /** Category order from the doc — the stable index into the CB palette. */
657
+ private catOrder;
648
658
  private selection;
649
659
  private selectionRings;
650
660
  private hoverRing;
@@ -797,8 +807,15 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
797
807
  private renderSeats;
798
808
  /** A booth renders as a click-selectable rounded block (dims from the doc). */
799
809
  private renderBoothUnit;
810
+ /** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
811
+ private seatBaseColor;
800
812
  /** Apply fill/stroke/opacity for a seat's current status + selection. */
801
813
  private paintSeat;
814
+ /**
815
+ * Colorblind-safe mode: swap category hues for the Okabe-Ito palette and
816
+ * render booked seats hollow. Off restores the exact default rendering.
817
+ */
818
+ setColorblindSafe(on: boolean): void;
802
819
  /**
803
820
  * Dim the seats of these section/zone ids (organizer manager use) so held-back
804
821
  * inventory is visually distinct on the canvas without hiding it. `null`/empty
@@ -1019,6 +1036,12 @@ interface PickerCallbacks extends RendererCallbacks {
1019
1036
  onSectionFocus?: (summary: SectionSummary | null) => void;
1020
1037
  /** A deck was tapped in the 3D all-floors overview — host enters that floor in 2D. */
1021
1038
  onDeckTap?: (floorId: string) => void;
1039
+ /**
1040
+ * Non-blocking selection advice (localized): currently the orphan-seat hint —
1041
+ * the selection would strand a single free seat between unavailable
1042
+ * neighbors. `null` clears a previously shown hint. Never prevents anything.
1043
+ */
1044
+ onHint?: (message: string | null) => void;
1022
1045
  onError?: (err: unknown) => void;
1023
1046
  }
1024
1047
  interface PickerOptions extends PickerCallbacks {
@@ -1031,6 +1054,8 @@ interface PickerOptions extends PickerCallbacks {
1031
1054
  currency?: string;
1032
1055
  /** Flash a pulse when a seat we didn't touch goes free→taken (live-activity cue). */
1033
1056
  flashOnLiveChange?: boolean;
1057
+ /** Start in colorblind-safe rendering (Okabe-Ito hues + hollow booked seats). */
1058
+ colorblindSafe?: boolean;
1034
1059
  }
1035
1060
  declare class PickerController {
1036
1061
  private readonly opts;
@@ -1162,6 +1187,17 @@ declare class PickerController {
1162
1187
  /** PickerSeats (with chosen tier) for a set of held labels. */
1163
1188
  private seatsForLabels;
1164
1189
  private emitSelectionChange;
1190
+ /** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
1191
+ private hintShown;
1192
+ /**
1193
+ * Orphan-seat advice on manual selection: when the buyer's current picks
1194
+ * strand one (or more) single free seats between unavailable same-row
1195
+ * neighbors, surface a localized, non-blocking hint. Cleared (null) as soon
1196
+ * as the selection stops stranding anyone.
1197
+ */
1198
+ private emitOrphanHint;
1199
+ /** Toggle colorblind-safe rendering at runtime (see PickerOptions.colorblindSafe). */
1200
+ setColorblindSafe(on: boolean): void;
1165
1201
  private emitError;
1166
1202
  private holdCovers;
1167
1203
  private handle409Conflicts;
package/dist/index.js CHANGED
@@ -500,7 +500,9 @@ var en = {
500
500
  "picker.seatsHeld": "Seats held \u2014 {time}",
501
501
  "picker.holdExpired": "Your hold expired \u2014 the seats were released. Pick again.",
502
502
  "picker.seatTaken": "Seat {label} was just taken by another buyer.",
503
- "picker.poweredBy": "Powered by SeatMap",
503
+ "picker.poweredBy": "Powered by SeatLayer",
504
+ "picker.colorblind": "Colorblind-friendly colors",
505
+ "picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
504
506
  "picker.seats.one": "{count} seat",
505
507
  "picker.seats.other": "{count} seats",
506
508
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -617,6 +619,7 @@ var ZONE_SUB_PX = 12;
617
619
  var HELD_FILL = "#6b7280";
618
620
  var TAKEN_FILL = "#374151";
619
621
  var NFS_STROKE = "#4b5563";
622
+ var CB_PALETTE = ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7"];
620
623
  var ACCESS_RING = {
621
624
  wheelchair: "#3b82f6",
622
625
  companion: "#8b5cf6",
@@ -719,6 +722,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
719
722
  this.statusById = /* @__PURE__ */ new Map();
720
723
  this.catColor = /* @__PURE__ */ new Map();
721
724
  this.theme = {};
725
+ /** Colorblind-safe mode (Okabe-Ito hues + hollow booked seats). */
726
+ this.colorblind = false;
727
+ /** Category order from the doc — the stable index into the CB palette. */
728
+ this.catOrder = [];
722
729
  this.selection = /* @__PURE__ */ new Set();
723
730
  this.selectionRings = /* @__PURE__ */ new Map();
724
731
  this.focusedId = null;
@@ -969,6 +976,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
969
976
  this.hoverRing.radius(this.seatR + 2);
970
977
  this.catColor.clear();
971
978
  this.catPrice.clear();
979
+ this.catOrder = doc.categories.map((c) => c.key);
972
980
  for (const c of doc.categories) {
973
981
  this.catColor.set(c.key, c.color);
974
982
  if (typeof c.price === "number") this.catPrice.set(c.key, c.price);
@@ -1492,12 +1500,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1492
1500
  this.hasBoothText = true;
1493
1501
  target.add(t2);
1494
1502
  }
1503
+ /** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
1504
+ seatBaseColor(categoryKey) {
1505
+ if (!this.colorblind) return this.catColor.get(categoryKey) ?? "#6e7bff";
1506
+ const idx = this.catOrder.indexOf(categoryKey);
1507
+ return CB_PALETTE[(idx >= 0 ? idx : 0) % CB_PALETTE.length];
1508
+ }
1495
1509
  /** Apply fill/stroke/opacity for a seat's current status + selection. */
1496
1510
  paintSeat(c, id) {
1497
1511
  const seat = this.seatById.get(id);
1498
1512
  const status = this.statusById.get(id) ?? "free";
1499
1513
  const selected = this.selection.has(id);
1500
- const base = this.catColor.get(seat.categoryKey) ?? "#6e7bff";
1514
+ const base = this.seatBaseColor(seat.categoryKey);
1501
1515
  c.dash([]);
1502
1516
  c.strokeWidth(0);
1503
1517
  c.stroke("");
@@ -1510,8 +1524,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1510
1524
  c.fill(HELD_FILL);
1511
1525
  break;
1512
1526
  case "booked":
1513
- c.fill(TAKEN_FILL);
1514
- c.opacity(0.45);
1527
+ if (this.colorblind) {
1528
+ c.fill("rgba(0,0,0,0)");
1529
+ c.stroke(TAKEN_FILL);
1530
+ c.strokeWidth(1.5);
1531
+ c.opacity(0.9);
1532
+ } else {
1533
+ c.fill(TAKEN_FILL);
1534
+ c.opacity(0.45);
1535
+ }
1515
1536
  break;
1516
1537
  case "not_for_sale":
1517
1538
  c.fill(TAKEN_FILL);
@@ -1533,6 +1554,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1533
1554
  }
1534
1555
  }
1535
1556
  }
1557
+ /**
1558
+ * Colorblind-safe mode: swap category hues for the Okabe-Ito palette and
1559
+ * render booked seats hollow. Off restores the exact default rendering.
1560
+ */
1561
+ setColorblindSafe(on) {
1562
+ if (on === this.colorblind) return;
1563
+ this.colorblind = on;
1564
+ for (const seat of this.seats) {
1565
+ const c = this.circleById.get(seat.id);
1566
+ if (c) this.paintSeat(c, seat.id);
1567
+ }
1568
+ if (this.cached) {
1569
+ this.seatLayer.clearCache();
1570
+ this.cacheSeatLayer();
1571
+ } else {
1572
+ this.seatLayer.batchDraw();
1573
+ }
1574
+ }
1536
1575
  /**
1537
1576
  * Dim the seats of these section/zone ids (organizer manager use) so held-back
1538
1577
  * inventory is visually distinct on the canvas without hiding it. `null`/empty
@@ -2471,6 +2510,38 @@ function createRenderer(container, opts) {
2471
2510
  return new SeatmapRenderer(container, opts);
2472
2511
  }
2473
2512
 
2513
+ // src/core/orphans.ts
2514
+ function seatIndex(id) {
2515
+ const n = Number(id.slice(id.lastIndexOf(":") + 1));
2516
+ return Number.isFinite(n) ? n : -1;
2517
+ }
2518
+ function strandedSingles(seats, statusOf, selectedIds) {
2519
+ const rows = /* @__PURE__ */ new Map();
2520
+ for (const s of seats) {
2521
+ if (s.kind === "booth") continue;
2522
+ const list = rows.get(s.rowId);
2523
+ if (list) list.push(s);
2524
+ else rows.set(s.rowId, [s]);
2525
+ }
2526
+ const unavailable = (s) => selectedIds.has(s.id) || statusOf(s.id) !== "free";
2527
+ const out = [];
2528
+ for (const list of rows.values()) {
2529
+ if (list.length < 3) continue;
2530
+ list.sort((a, b) => seatIndex(a.id) - seatIndex(b.id));
2531
+ for (let i = 1; i < list.length - 1; i++) {
2532
+ const seat = list[i];
2533
+ if (unavailable(seat)) continue;
2534
+ const left = list[i - 1];
2535
+ const right = list[i + 1];
2536
+ if (seatIndex(seat.id) - seatIndex(left.id) !== 1 || seatIndex(right.id) - seatIndex(seat.id) !== 1) continue;
2537
+ if (!unavailable(left) || !unavailable(right)) continue;
2538
+ if (!selectedIds.has(left.id) && !selectedIds.has(right.id)) continue;
2539
+ out.push(seat);
2540
+ }
2541
+ }
2542
+ return out;
2543
+ }
2544
+
2474
2545
  // src/picker/PickerController.ts
2475
2546
  var DEFAULT_MAX_SELECTION = 10;
2476
2547
  var MAX_BACKOFF_MS = 15e3;
@@ -2505,6 +2576,8 @@ var PickerController = class {
2505
2576
  // hold state
2506
2577
  this.hold_ = null;
2507
2578
  this.expiryTimer = null;
2579
+ /** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
2580
+ this.hintShown = false;
2508
2581
  this.opts = options;
2509
2582
  this.api = options.transport;
2510
2583
  this.key = options.eventKey;
@@ -2600,6 +2673,7 @@ var PickerController = class {
2600
2673
  return null;
2601
2674
  }
2602
2675
  renderer.setChart(this.visibleDoc());
2676
+ if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
2603
2677
  if (seats) this.applySeatsMap(seats);
2604
2678
  this.connect();
2605
2679
  return {
@@ -2971,6 +3045,31 @@ var PickerController = class {
2971
3045
  }
2972
3046
  emitSelectionChange() {
2973
3047
  this.opts.onSelectionChange?.(this.getSelection());
3048
+ this.emitOrphanHint();
3049
+ }
3050
+ /**
3051
+ * Orphan-seat advice on manual selection: when the buyer's current picks
3052
+ * strand one (or more) single free seats between unavailable same-row
3053
+ * neighbors, surface a localized, non-blocking hint. Cleared (null) as soon
3054
+ * as the selection stops stranding anyone.
3055
+ */
3056
+ emitOrphanHint() {
3057
+ if (!this.opts.onHint) return;
3058
+ const r = this.renderer;
3059
+ if (!r) return;
3060
+ const selected = new Set(r.getSelection().map((s) => s.id));
3061
+ const stranded = selected.size ? strandedSingles(this.seatById.values(), (id) => r.getStatus(id), selected) : [];
3062
+ if (stranded.length > 0) {
3063
+ this.hintShown = true;
3064
+ this.opts.onHint(t("picker.orphanHint"));
3065
+ } else if (this.hintShown) {
3066
+ this.hintShown = false;
3067
+ this.opts.onHint(null);
3068
+ }
3069
+ }
3070
+ /** Toggle colorblind-safe rendering at runtime (see PickerOptions.colorblindSafe). */
3071
+ setColorblindSafe(on) {
3072
+ this.renderer?.setColorblindSafe?.(on);
2974
3073
  }
2975
3074
  emitError(err) {
2976
3075
  if (this.opts.onError) this.opts.onError(err);
@@ -3107,9 +3206,9 @@ var PickerController = class {
3107
3206
 
3108
3207
  // src/i18n/bundles.ts
3109
3208
  var LOADERS = {
3110
- es: () => import("./es-UM4ZS357.js").then((m) => ({ default: m.es })),
3111
- de: () => import("./de-Y3E6YTGP.js").then((m) => ({ default: m.de })),
3112
- fr: () => import("./fr-IGRD2M4W.js").then((m) => ({ default: m.fr }))
3209
+ es: () => import("./es-YNYMMP37.js").then((m) => ({ default: m.es })),
3210
+ de: () => import("./de-A4KJQ374.js").then((m) => ({ default: m.de })),
3211
+ fr: () => import("./fr-DQ4KWKKF.js").then((m) => ({ default: m.fr }))
3113
3212
  };
3114
3213
  var loaded = /* @__PURE__ */ new Set(["en"]);
3115
3214
  async function loadLocale(code) {