@seatlayer/core 0.10.1 → 0.10.2

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
@@ -411,6 +411,18 @@ interface ISeatmapRenderer {
411
411
  }): void;
412
412
  /** Bulk status update; re-renders affected seats only. */
413
413
  setStatus(seatIds: string[], status: SeatStatus): void;
414
+ /**
415
+ * SYNCHRONOUS repaint that bypasses requestAnimationFrame. Konva's batchDraw()
416
+ * (used by setStatus and friends) schedules the actual paint on the next rAF
417
+ * tick, which Chrome throttles/pauses on hidden, backgrounded, or occluded
418
+ * tabs — so a seat-status delta updates the scene graph but the pixels never
419
+ * change until the tab is foregrounded again. forceDraw() paints the affected
420
+ * layers immediately (Layer.draw() is synchronous) and flushes any pending
421
+ * cache-debounce, so a caller (visibilitychange catch-up, or an opted-in
422
+ * always-live board) can guarantee the canvas reflects current state
423
+ * regardless of tab visibility. No-op difference in the foreground.
424
+ */
425
+ forceDraw(): void;
414
426
  getStatus(seatId: string): SeatStatus;
415
427
  getSelection(): ExpandedSeat[];
416
428
  clearSelection(): void;
@@ -1109,7 +1121,22 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1109
1121
  /** rAF-coalesced `onViewChange` — at most one host callback per animation frame. */
1110
1122
  private scheduleViewChange;
1111
1123
  private updateLOD;
1124
+ /** Rebuild the seat-layer bitmap synchronously (no paint). Shared by the
1125
+ * debounced cacheSeatLayer() and the synchronous forceDraw() catch-up. */
1126
+ private rebuildSeatCache;
1112
1127
  private cacheSeatLayer;
1128
+ /**
1129
+ * SYNCHRONOUS repaint that bypasses requestAnimationFrame — see the
1130
+ * ISeatmapRenderer.forceDraw() contract. Chrome pauses rAF (and therefore
1131
+ * Konva's batchDraw) on hidden/backgrounded/occluded tabs, so seat-status
1132
+ * deltas applied via setStatus() mutate the scene graph but never reach the
1133
+ * canvas until the tab is foregrounded. This flushes the cache-debounce and
1134
+ * paints every layer setStatus() can touch, immediately.
1135
+ *
1136
+ * Additive: the foreground path never calls this, so foreground behaviour is
1137
+ * byte-identical.
1138
+ */
1139
+ forceDraw(): void;
1113
1140
  private updateLabels;
1114
1141
  private handleResize;
1115
1142
  private startFpsLoop;
@@ -1280,6 +1307,18 @@ interface PickerOptions extends PickerCallbacks {
1280
1307
  flashOnLiveChange?: boolean;
1281
1308
  /** Start in colorblind-safe rendering (Okabe-Ito hues + hollow booked seats). */
1282
1309
  colorblindSafe?: boolean;
1310
+ /**
1311
+ * Keep painting seat-status deltas even while the tab is hidden/backgrounded.
1312
+ *
1313
+ * Default FALSE — the buyer widget stays efficient: it lets rAF stay paused
1314
+ * while hidden and does a single synchronous catch-up repaint on regain (the
1315
+ * visibilitychange handler resnapshots + forceDraw()s). Set TRUE only for an
1316
+ * always-live surface — the future organizer control-room board — where a
1317
+ * backgrounded monitor must keep repainting; then each delta calls
1318
+ * renderer.forceDraw() when the tab is hidden. Purely a paint hint; enforcement
1319
+ * and the WS protocol are identical either way.
1320
+ */
1321
+ keepLiveWhileHidden?: boolean;
1283
1322
  }
1284
1323
  declare class PickerController {
1285
1324
  private readonly opts;
@@ -1305,6 +1344,10 @@ declare class PickerController {
1305
1344
  private reconnectTimer;
1306
1345
  private attempt;
1307
1346
  private closed;
1347
+ /** Bound visibilitychange listener (buyer catch-up on tab regain). Kept on the
1348
+ * instance so destroy() can detach it; null when not attached (guards double
1349
+ * mounts / non-DOM environments). */
1350
+ private onVisibilityChange;
1308
1351
  private hold_;
1309
1352
  private liveStatuses;
1310
1353
  private currency;
@@ -1498,6 +1541,19 @@ declare class PickerController {
1498
1541
  private applySeatsMap;
1499
1542
  private clearBookedHoldIfSettled;
1500
1543
  private resnapshot;
1544
+ /**
1545
+ * Buyer catch-up on tab regain. While a tab is hidden Chrome pauses rAF, so
1546
+ * seat-status deltas that arrived over the WS mutated the scene graph but the
1547
+ * canvas colors never repainted. When the tab becomes visible again we (1)
1548
+ * resnapshot to pull authoritative state for anything the socket may have
1549
+ * missed while backgrounded, then (2) forceDraw() a synchronous repaint so the
1550
+ * seat colors are correct immediately rather than on the next incidental draw.
1551
+ *
1552
+ * Attached once per mount; guarded against double-attach and non-DOM
1553
+ * environments; detached in destroy() (no leaks).
1554
+ */
1555
+ private attachVisibilityListener;
1556
+ private detachVisibilityListener;
1501
1557
  private connect;
1502
1558
  private scheduleReconnect;
1503
1559
  }
package/dist/index.d.ts CHANGED
@@ -411,6 +411,18 @@ interface ISeatmapRenderer {
411
411
  }): void;
412
412
  /** Bulk status update; re-renders affected seats only. */
413
413
  setStatus(seatIds: string[], status: SeatStatus): void;
414
+ /**
415
+ * SYNCHRONOUS repaint that bypasses requestAnimationFrame. Konva's batchDraw()
416
+ * (used by setStatus and friends) schedules the actual paint on the next rAF
417
+ * tick, which Chrome throttles/pauses on hidden, backgrounded, or occluded
418
+ * tabs — so a seat-status delta updates the scene graph but the pixels never
419
+ * change until the tab is foregrounded again. forceDraw() paints the affected
420
+ * layers immediately (Layer.draw() is synchronous) and flushes any pending
421
+ * cache-debounce, so a caller (visibilitychange catch-up, or an opted-in
422
+ * always-live board) can guarantee the canvas reflects current state
423
+ * regardless of tab visibility. No-op difference in the foreground.
424
+ */
425
+ forceDraw(): void;
414
426
  getStatus(seatId: string): SeatStatus;
415
427
  getSelection(): ExpandedSeat[];
416
428
  clearSelection(): void;
@@ -1109,7 +1121,22 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1109
1121
  /** rAF-coalesced `onViewChange` — at most one host callback per animation frame. */
1110
1122
  private scheduleViewChange;
1111
1123
  private updateLOD;
1124
+ /** Rebuild the seat-layer bitmap synchronously (no paint). Shared by the
1125
+ * debounced cacheSeatLayer() and the synchronous forceDraw() catch-up. */
1126
+ private rebuildSeatCache;
1112
1127
  private cacheSeatLayer;
1128
+ /**
1129
+ * SYNCHRONOUS repaint that bypasses requestAnimationFrame — see the
1130
+ * ISeatmapRenderer.forceDraw() contract. Chrome pauses rAF (and therefore
1131
+ * Konva's batchDraw) on hidden/backgrounded/occluded tabs, so seat-status
1132
+ * deltas applied via setStatus() mutate the scene graph but never reach the
1133
+ * canvas until the tab is foregrounded. This flushes the cache-debounce and
1134
+ * paints every layer setStatus() can touch, immediately.
1135
+ *
1136
+ * Additive: the foreground path never calls this, so foreground behaviour is
1137
+ * byte-identical.
1138
+ */
1139
+ forceDraw(): void;
1113
1140
  private updateLabels;
1114
1141
  private handleResize;
1115
1142
  private startFpsLoop;
@@ -1280,6 +1307,18 @@ interface PickerOptions extends PickerCallbacks {
1280
1307
  flashOnLiveChange?: boolean;
1281
1308
  /** Start in colorblind-safe rendering (Okabe-Ito hues + hollow booked seats). */
1282
1309
  colorblindSafe?: boolean;
1310
+ /**
1311
+ * Keep painting seat-status deltas even while the tab is hidden/backgrounded.
1312
+ *
1313
+ * Default FALSE — the buyer widget stays efficient: it lets rAF stay paused
1314
+ * while hidden and does a single synchronous catch-up repaint on regain (the
1315
+ * visibilitychange handler resnapshots + forceDraw()s). Set TRUE only for an
1316
+ * always-live surface — the future organizer control-room board — where a
1317
+ * backgrounded monitor must keep repainting; then each delta calls
1318
+ * renderer.forceDraw() when the tab is hidden. Purely a paint hint; enforcement
1319
+ * and the WS protocol are identical either way.
1320
+ */
1321
+ keepLiveWhileHidden?: boolean;
1283
1322
  }
1284
1323
  declare class PickerController {
1285
1324
  private readonly opts;
@@ -1305,6 +1344,10 @@ declare class PickerController {
1305
1344
  private reconnectTimer;
1306
1345
  private attempt;
1307
1346
  private closed;
1347
+ /** Bound visibilitychange listener (buyer catch-up on tab regain). Kept on the
1348
+ * instance so destroy() can detach it; null when not attached (guards double
1349
+ * mounts / non-DOM environments). */
1350
+ private onVisibilityChange;
1308
1351
  private hold_;
1309
1352
  private liveStatuses;
1310
1353
  private currency;
@@ -1498,6 +1541,19 @@ declare class PickerController {
1498
1541
  private applySeatsMap;
1499
1542
  private clearBookedHoldIfSettled;
1500
1543
  private resnapshot;
1544
+ /**
1545
+ * Buyer catch-up on tab regain. While a tab is hidden Chrome pauses rAF, so
1546
+ * seat-status deltas that arrived over the WS mutated the scene graph but the
1547
+ * canvas colors never repainted. When the tab becomes visible again we (1)
1548
+ * resnapshot to pull authoritative state for anything the socket may have
1549
+ * missed while backgrounded, then (2) forceDraw() a synchronous repaint so the
1550
+ * seat colors are correct immediately rather than on the next incidental draw.
1551
+ *
1552
+ * Attached once per mount; guarded against double-attach and non-DOM
1553
+ * environments; detached in destroy() (no leaks).
1554
+ */
1555
+ private attachVisibilityListener;
1556
+ private detachVisibilityListener;
1501
1557
  private connect;
1502
1558
  private scheduleReconnect;
1503
1559
  }
package/dist/index.js CHANGED
@@ -2849,14 +2849,40 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2849
2849
  this.seatLayer.batchDraw();
2850
2850
  }
2851
2851
  }
2852
- cacheSeatLayer() {
2852
+ /** Rebuild the seat-layer bitmap synchronously (no paint). Shared by the
2853
+ * debounced cacheSeatLayer() and the synchronous forceDraw() catch-up. */
2854
+ rebuildSeatCache() {
2853
2855
  const pr = clamp(this.stage.scaleX() * this.dpr, 0.15, 2);
2854
2856
  this.seatLayer.clearCache();
2855
2857
  this.seatLayer.cache({ pixelRatio: pr });
2856
2858
  this.seatLayer.listening(false);
2857
2859
  this.cached = true;
2860
+ }
2861
+ cacheSeatLayer() {
2862
+ this.rebuildSeatCache();
2858
2863
  this.seatLayer.batchDraw();
2859
2864
  }
2865
+ /**
2866
+ * SYNCHRONOUS repaint that bypasses requestAnimationFrame — see the
2867
+ * ISeatmapRenderer.forceDraw() contract. Chrome pauses rAF (and therefore
2868
+ * Konva's batchDraw) on hidden/backgrounded/occluded tabs, so seat-status
2869
+ * deltas applied via setStatus() mutate the scene graph but never reach the
2870
+ * canvas until the tab is foregrounded. This flushes the cache-debounce and
2871
+ * paints every layer setStatus() can touch, immediately.
2872
+ *
2873
+ * Additive: the foreground path never calls this, so foreground behaviour is
2874
+ * byte-identical.
2875
+ */
2876
+ forceDraw() {
2877
+ if (this.recacheTimer) {
2878
+ clearTimeout(this.recacheTimer);
2879
+ this.recacheTimer = null;
2880
+ this.rebuildSeatCache();
2881
+ }
2882
+ this.bgLayer.draw();
2883
+ this.seatLayer.draw();
2884
+ this.overlayLayer.draw();
2885
+ }
2860
2886
  updateLabels() {
2861
2887
  const show = this.effScale() > LABEL_SCALE;
2862
2888
  this.labelGroup.destroyChildren();
@@ -2992,6 +3018,10 @@ var PickerController = class {
2992
3018
  this.reconnectTimer = null;
2993
3019
  this.attempt = 0;
2994
3020
  this.closed = false;
3021
+ /** Bound visibilitychange listener (buyer catch-up on tab regain). Kept on the
3022
+ * instance so destroy() can detach it; null when not attached (guards double
3023
+ * mounts / non-DOM environments). */
3024
+ this.onVisibilityChange = null;
2995
3025
  // hold state
2996
3026
  this.hold_ = null;
2997
3027
  this.liveStatuses = /* @__PURE__ */ new Map();
@@ -3117,6 +3147,7 @@ var PickerController = class {
3117
3147
  this.closedSections = new Set(closedIds);
3118
3148
  if (closedIds.length) renderer.setClosedSections?.(closedIds);
3119
3149
  if (seats) this.applySeatsMap(seats);
3150
+ this.attachVisibilityListener();
3120
3151
  this.connect();
3121
3152
  return {
3122
3153
  doc: res.doc,
@@ -3543,6 +3574,7 @@ var PickerController = class {
3543
3574
  }
3544
3575
  destroy() {
3545
3576
  this.closed = true;
3577
+ this.detachVisibilityListener();
3546
3578
  if (this.reconnectTimer) {
3547
3579
  clearTimeout(this.reconnectTimer);
3548
3580
  this.reconnectTimer = null;
@@ -3739,6 +3771,37 @@ var PickerController = class {
3739
3771
  }
3740
3772
  }
3741
3773
  // ---- realtime socket ------------------------------------------------------
3774
+ /**
3775
+ * Buyer catch-up on tab regain. While a tab is hidden Chrome pauses rAF, so
3776
+ * seat-status deltas that arrived over the WS mutated the scene graph but the
3777
+ * canvas colors never repainted. When the tab becomes visible again we (1)
3778
+ * resnapshot to pull authoritative state for anything the socket may have
3779
+ * missed while backgrounded, then (2) forceDraw() a synchronous repaint so the
3780
+ * seat colors are correct immediately rather than on the next incidental draw.
3781
+ *
3782
+ * Attached once per mount; guarded against double-attach and non-DOM
3783
+ * environments; detached in destroy() (no leaks).
3784
+ */
3785
+ attachVisibilityListener() {
3786
+ if (this.onVisibilityChange) return;
3787
+ if (typeof document === "undefined" || typeof document.addEventListener !== "function") return;
3788
+ const handler = () => {
3789
+ if (this.closed) return;
3790
+ if (document.visibilityState !== "visible") return;
3791
+ void this.resnapshot().then(() => {
3792
+ if (!this.closed) this.renderer?.forceDraw();
3793
+ });
3794
+ };
3795
+ this.onVisibilityChange = handler;
3796
+ document.addEventListener("visibilitychange", handler);
3797
+ }
3798
+ detachVisibilityListener() {
3799
+ if (!this.onVisibilityChange) return;
3800
+ if (typeof document !== "undefined" && typeof document.removeEventListener === "function") {
3801
+ document.removeEventListener("visibilitychange", this.onVisibilityChange);
3802
+ }
3803
+ this.onVisibilityChange = null;
3804
+ }
3742
3805
  connect() {
3743
3806
  if (this.closed) return;
3744
3807
  let ws;
@@ -3784,6 +3847,9 @@ var PickerController = class {
3784
3847
  }
3785
3848
  r.setStatus([id], next);
3786
3849
  }
3850
+ if (this.opts.keepLiveWhileHidden && typeof document !== "undefined" && document.visibilityState === "hidden") {
3851
+ r.forceDraw();
3852
+ }
3787
3853
  this.clearBookedHoldIfSettled();
3788
3854
  this.opts.onStatusChange?.();
3789
3855
  }