@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.cjs CHANGED
@@ -3168,14 +3168,40 @@ var _SeatmapRenderer = class _SeatmapRenderer {
3168
3168
  this.seatLayer.batchDraw();
3169
3169
  }
3170
3170
  }
3171
- cacheSeatLayer() {
3171
+ /** Rebuild the seat-layer bitmap synchronously (no paint). Shared by the
3172
+ * debounced cacheSeatLayer() and the synchronous forceDraw() catch-up. */
3173
+ rebuildSeatCache() {
3172
3174
  const pr = clamp(this.stage.scaleX() * this.dpr, 0.15, 2);
3173
3175
  this.seatLayer.clearCache();
3174
3176
  this.seatLayer.cache({ pixelRatio: pr });
3175
3177
  this.seatLayer.listening(false);
3176
3178
  this.cached = true;
3179
+ }
3180
+ cacheSeatLayer() {
3181
+ this.rebuildSeatCache();
3177
3182
  this.seatLayer.batchDraw();
3178
3183
  }
3184
+ /**
3185
+ * SYNCHRONOUS repaint that bypasses requestAnimationFrame — see the
3186
+ * ISeatmapRenderer.forceDraw() contract. Chrome pauses rAF (and therefore
3187
+ * Konva's batchDraw) on hidden/backgrounded/occluded tabs, so seat-status
3188
+ * deltas applied via setStatus() mutate the scene graph but never reach the
3189
+ * canvas until the tab is foregrounded. This flushes the cache-debounce and
3190
+ * paints every layer setStatus() can touch, immediately.
3191
+ *
3192
+ * Additive: the foreground path never calls this, so foreground behaviour is
3193
+ * byte-identical.
3194
+ */
3195
+ forceDraw() {
3196
+ if (this.recacheTimer) {
3197
+ clearTimeout(this.recacheTimer);
3198
+ this.recacheTimer = null;
3199
+ this.rebuildSeatCache();
3200
+ }
3201
+ this.bgLayer.draw();
3202
+ this.seatLayer.draw();
3203
+ this.overlayLayer.draw();
3204
+ }
3179
3205
  updateLabels() {
3180
3206
  const show = this.effScale() > LABEL_SCALE;
3181
3207
  this.labelGroup.destroyChildren();
@@ -3311,6 +3337,10 @@ var PickerController = class {
3311
3337
  this.reconnectTimer = null;
3312
3338
  this.attempt = 0;
3313
3339
  this.closed = false;
3340
+ /** Bound visibilitychange listener (buyer catch-up on tab regain). Kept on the
3341
+ * instance so destroy() can detach it; null when not attached (guards double
3342
+ * mounts / non-DOM environments). */
3343
+ this.onVisibilityChange = null;
3314
3344
  // hold state
3315
3345
  this.hold_ = null;
3316
3346
  this.liveStatuses = /* @__PURE__ */ new Map();
@@ -3436,6 +3466,7 @@ var PickerController = class {
3436
3466
  this.closedSections = new Set(closedIds);
3437
3467
  if (closedIds.length) renderer.setClosedSections?.(closedIds);
3438
3468
  if (seats) this.applySeatsMap(seats);
3469
+ this.attachVisibilityListener();
3439
3470
  this.connect();
3440
3471
  return {
3441
3472
  doc: res.doc,
@@ -3862,6 +3893,7 @@ var PickerController = class {
3862
3893
  }
3863
3894
  destroy() {
3864
3895
  this.closed = true;
3896
+ this.detachVisibilityListener();
3865
3897
  if (this.reconnectTimer) {
3866
3898
  clearTimeout(this.reconnectTimer);
3867
3899
  this.reconnectTimer = null;
@@ -4058,6 +4090,37 @@ var PickerController = class {
4058
4090
  }
4059
4091
  }
4060
4092
  // ---- realtime socket ------------------------------------------------------
4093
+ /**
4094
+ * Buyer catch-up on tab regain. While a tab is hidden Chrome pauses rAF, so
4095
+ * seat-status deltas that arrived over the WS mutated the scene graph but the
4096
+ * canvas colors never repainted. When the tab becomes visible again we (1)
4097
+ * resnapshot to pull authoritative state for anything the socket may have
4098
+ * missed while backgrounded, then (2) forceDraw() a synchronous repaint so the
4099
+ * seat colors are correct immediately rather than on the next incidental draw.
4100
+ *
4101
+ * Attached once per mount; guarded against double-attach and non-DOM
4102
+ * environments; detached in destroy() (no leaks).
4103
+ */
4104
+ attachVisibilityListener() {
4105
+ if (this.onVisibilityChange) return;
4106
+ if (typeof document === "undefined" || typeof document.addEventListener !== "function") return;
4107
+ const handler = () => {
4108
+ if (this.closed) return;
4109
+ if (document.visibilityState !== "visible") return;
4110
+ void this.resnapshot().then(() => {
4111
+ if (!this.closed) this.renderer?.forceDraw();
4112
+ });
4113
+ };
4114
+ this.onVisibilityChange = handler;
4115
+ document.addEventListener("visibilitychange", handler);
4116
+ }
4117
+ detachVisibilityListener() {
4118
+ if (!this.onVisibilityChange) return;
4119
+ if (typeof document !== "undefined" && typeof document.removeEventListener === "function") {
4120
+ document.removeEventListener("visibilitychange", this.onVisibilityChange);
4121
+ }
4122
+ this.onVisibilityChange = null;
4123
+ }
4061
4124
  connect() {
4062
4125
  if (this.closed) return;
4063
4126
  let ws;
@@ -4103,6 +4166,9 @@ var PickerController = class {
4103
4166
  }
4104
4167
  r.setStatus([id], next);
4105
4168
  }
4169
+ if (this.opts.keepLiveWhileHidden && typeof document !== "undefined" && document.visibilityState === "hidden") {
4170
+ r.forceDraw();
4171
+ }
4106
4172
  this.clearBookedHoldIfSettled();
4107
4173
  this.opts.onStatusChange?.();
4108
4174
  }