@seatlayer/core 0.10.0 → 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 +148 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +148 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -1035,6 +1047,32 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1035
1047
|
*/
|
|
1036
1048
|
private resolveSelectionColor;
|
|
1037
1049
|
private setSelected;
|
|
1050
|
+
/**
|
|
1051
|
+
* The stage scale `focusRegion(id)` would settle at — i.e. the zoom that
|
|
1052
|
+
* frames this section in the current viewport. Used to decide whether
|
|
1053
|
+
* redirecting a seat tap to section-focus would actually zoom in FURTHER, or
|
|
1054
|
+
* whether the section already fills the viewport (small container) so the tap
|
|
1055
|
+
* must fall through and pick.
|
|
1056
|
+
*/
|
|
1057
|
+
private sectionFrameScale;
|
|
1058
|
+
/**
|
|
1059
|
+
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
1060
|
+
* then toggle. The gate (§4) redirects small on-screen seats to section-focus
|
|
1061
|
+
* to avoid blind mis-picks at overview scales — but ONLY when focusing would
|
|
1062
|
+
* zoom the seat up to a safe size. If we're already focused on the section, or
|
|
1063
|
+
* the section already fills the viewport (a narrow — OR even a wide — container
|
|
1064
|
+
* frames a big section BELOW LABEL_SCALE), redirecting is a no-op that would
|
|
1065
|
+
* leave the seat permanently unpickable (§5 `picking-gate-unreachable`), so we
|
|
1066
|
+
* fall through and PICK. This is the invariant: seats are never unpickable.
|
|
1067
|
+
*/
|
|
1068
|
+
private handleSeatTap;
|
|
1069
|
+
/**
|
|
1070
|
+
* Nearest SELECTABLE seat whose centre is within `slopPx` (screen space) of a
|
|
1071
|
+
* seat circle edge, or null. Enlarges the effective tap target for small seats
|
|
1072
|
+
* so a near-miss on mobile still lands on the intended seat, without ever
|
|
1073
|
+
* hijacking a clean tap on empty space beyond the slop.
|
|
1074
|
+
*/
|
|
1075
|
+
private nearestSeatToScreen;
|
|
1038
1076
|
private wireInteraction;
|
|
1039
1077
|
/**
|
|
1040
1078
|
* Which deck (floor) a screen tap landed on in the 3D stacked overview. The
|
|
@@ -1083,7 +1121,22 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1083
1121
|
/** rAF-coalesced `onViewChange` — at most one host callback per animation frame. */
|
|
1084
1122
|
private scheduleViewChange;
|
|
1085
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;
|
|
1086
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;
|
|
1087
1140
|
private updateLabels;
|
|
1088
1141
|
private handleResize;
|
|
1089
1142
|
private startFpsLoop;
|
|
@@ -1254,6 +1307,18 @@ interface PickerOptions extends PickerCallbacks {
|
|
|
1254
1307
|
flashOnLiveChange?: boolean;
|
|
1255
1308
|
/** Start in colorblind-safe rendering (Okabe-Ito hues + hollow booked seats). */
|
|
1256
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;
|
|
1257
1322
|
}
|
|
1258
1323
|
declare class PickerController {
|
|
1259
1324
|
private readonly opts;
|
|
@@ -1279,6 +1344,10 @@ declare class PickerController {
|
|
|
1279
1344
|
private reconnectTimer;
|
|
1280
1345
|
private attempt;
|
|
1281
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;
|
|
1282
1351
|
private hold_;
|
|
1283
1352
|
private liveStatuses;
|
|
1284
1353
|
private currency;
|
|
@@ -1472,6 +1541,19 @@ declare class PickerController {
|
|
|
1472
1541
|
private applySeatsMap;
|
|
1473
1542
|
private clearBookedHoldIfSettled;
|
|
1474
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;
|
|
1475
1557
|
private connect;
|
|
1476
1558
|
private scheduleReconnect;
|
|
1477
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;
|
|
@@ -1035,6 +1047,32 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1035
1047
|
*/
|
|
1036
1048
|
private resolveSelectionColor;
|
|
1037
1049
|
private setSelected;
|
|
1050
|
+
/**
|
|
1051
|
+
* The stage scale `focusRegion(id)` would settle at — i.e. the zoom that
|
|
1052
|
+
* frames this section in the current viewport. Used to decide whether
|
|
1053
|
+
* redirecting a seat tap to section-focus would actually zoom in FURTHER, or
|
|
1054
|
+
* whether the section already fills the viewport (small container) so the tap
|
|
1055
|
+
* must fall through and pick.
|
|
1056
|
+
*/
|
|
1057
|
+
private sectionFrameScale;
|
|
1058
|
+
/**
|
|
1059
|
+
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
1060
|
+
* then toggle. The gate (§4) redirects small on-screen seats to section-focus
|
|
1061
|
+
* to avoid blind mis-picks at overview scales — but ONLY when focusing would
|
|
1062
|
+
* zoom the seat up to a safe size. If we're already focused on the section, or
|
|
1063
|
+
* the section already fills the viewport (a narrow — OR even a wide — container
|
|
1064
|
+
* frames a big section BELOW LABEL_SCALE), redirecting is a no-op that would
|
|
1065
|
+
* leave the seat permanently unpickable (§5 `picking-gate-unreachable`), so we
|
|
1066
|
+
* fall through and PICK. This is the invariant: seats are never unpickable.
|
|
1067
|
+
*/
|
|
1068
|
+
private handleSeatTap;
|
|
1069
|
+
/**
|
|
1070
|
+
* Nearest SELECTABLE seat whose centre is within `slopPx` (screen space) of a
|
|
1071
|
+
* seat circle edge, or null. Enlarges the effective tap target for small seats
|
|
1072
|
+
* so a near-miss on mobile still lands on the intended seat, without ever
|
|
1073
|
+
* hijacking a clean tap on empty space beyond the slop.
|
|
1074
|
+
*/
|
|
1075
|
+
private nearestSeatToScreen;
|
|
1038
1076
|
private wireInteraction;
|
|
1039
1077
|
/**
|
|
1040
1078
|
* Which deck (floor) a screen tap landed on in the 3D stacked overview. The
|
|
@@ -1083,7 +1121,22 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1083
1121
|
/** rAF-coalesced `onViewChange` — at most one host callback per animation frame. */
|
|
1084
1122
|
private scheduleViewChange;
|
|
1085
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;
|
|
1086
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;
|
|
1087
1140
|
private updateLabels;
|
|
1088
1141
|
private handleResize;
|
|
1089
1142
|
private startFpsLoop;
|
|
@@ -1254,6 +1307,18 @@ interface PickerOptions extends PickerCallbacks {
|
|
|
1254
1307
|
flashOnLiveChange?: boolean;
|
|
1255
1308
|
/** Start in colorblind-safe rendering (Okabe-Ito hues + hollow booked seats). */
|
|
1256
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;
|
|
1257
1322
|
}
|
|
1258
1323
|
declare class PickerController {
|
|
1259
1324
|
private readonly opts;
|
|
@@ -1279,6 +1344,10 @@ declare class PickerController {
|
|
|
1279
1344
|
private reconnectTimer;
|
|
1280
1345
|
private attempt;
|
|
1281
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;
|
|
1282
1351
|
private hold_;
|
|
1283
1352
|
private liveStatuses;
|
|
1284
1353
|
private currency;
|
|
@@ -1472,6 +1541,19 @@ declare class PickerController {
|
|
|
1472
1541
|
private applySeatsMap;
|
|
1473
1542
|
private clearBookedHoldIfSettled;
|
|
1474
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;
|
|
1475
1557
|
private connect;
|
|
1476
1558
|
private scheduleReconnect;
|
|
1477
1559
|
}
|
package/dist/index.js
CHANGED
|
@@ -632,6 +632,7 @@ var SEAT_RADIUS = 9;
|
|
|
632
632
|
var SEAT_LEGIBLE_SCALE = 0.9;
|
|
633
633
|
var CACHE_THRESHOLD = 0.55 * SEAT_LEGIBLE_SCALE;
|
|
634
634
|
var LABEL_SCALE = 1;
|
|
635
|
+
var SEAT_TAP_SLOP_PX = 14;
|
|
635
636
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
636
637
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
637
638
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
@@ -2533,27 +2534,84 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2533
2534
|
}
|
|
2534
2535
|
}
|
|
2535
2536
|
// ---- interaction ----------------------------------------------------------
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2537
|
+
/**
|
|
2538
|
+
* The stage scale `focusRegion(id)` would settle at — i.e. the zoom that
|
|
2539
|
+
* frames this section in the current viewport. Used to decide whether
|
|
2540
|
+
* redirecting a seat tap to section-focus would actually zoom in FURTHER, or
|
|
2541
|
+
* whether the section already fills the viewport (small container) so the tap
|
|
2542
|
+
* must fall through and pick.
|
|
2543
|
+
*/
|
|
2544
|
+
sectionFrameScale(id) {
|
|
2545
|
+
const sec = this.sections.find((s) => s.id === id);
|
|
2546
|
+
if (!sec) return this.stage.scaleX();
|
|
2547
|
+
const b = polyBounds(sec.outline);
|
|
2548
|
+
const w = this.stage.width();
|
|
2549
|
+
const h = this.stage.height();
|
|
2550
|
+
const { min, max } = this.zoomBounds();
|
|
2551
|
+
const margin = 1.12;
|
|
2552
|
+
if (b.width <= 0 || b.height <= 0) return this.stage.scaleX();
|
|
2553
|
+
return clamp(Math.min(w / (b.width * margin), h / (b.height * margin)), min, max);
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
2557
|
+
* then toggle. The gate (§4) redirects small on-screen seats to section-focus
|
|
2558
|
+
* to avoid blind mis-picks at overview scales — but ONLY when focusing would
|
|
2559
|
+
* zoom the seat up to a safe size. If we're already focused on the section, or
|
|
2560
|
+
* the section already fills the viewport (a narrow — OR even a wide — container
|
|
2561
|
+
* frames a big section BELOW LABEL_SCALE), redirecting is a no-op that would
|
|
2562
|
+
* leave the seat permanently unpickable (§5 `picking-gate-unreachable`), so we
|
|
2563
|
+
* fall through and PICK. This is the invariant: seats are never unpickable.
|
|
2564
|
+
*/
|
|
2565
|
+
handleSeatTap(id) {
|
|
2566
|
+
if (this.stacked && this.opts.onDeckTap) {
|
|
2567
|
+
const floorId = this.objectFloor.get(this.seatById.get(id)?.rowId ?? "");
|
|
2568
|
+
if (floorId) {
|
|
2569
|
+
this.opts.onDeckTap(floorId);
|
|
2570
|
+
return;
|
|
2547
2571
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2572
|
+
}
|
|
2573
|
+
if (this.effScale() < LABEL_SCALE && this.sections.length) {
|
|
2574
|
+
const sec = this.seatSection.get(id);
|
|
2575
|
+
if (sec) {
|
|
2576
|
+
const alreadyFocused = this.focusedSectionId === sec.id;
|
|
2577
|
+
const canZoomInFurther = this.sectionFrameScale(sec.id) > this.stage.scaleX() * 1.02;
|
|
2578
|
+
if (!alreadyFocused && canZoomInFurther) {
|
|
2551
2579
|
if (this.opts.onSectionTap) this.opts.onSectionTap(sec.id);
|
|
2552
2580
|
else this.focusSection(sec.id);
|
|
2553
2581
|
return;
|
|
2554
2582
|
}
|
|
2555
2583
|
}
|
|
2556
|
-
|
|
2584
|
+
}
|
|
2585
|
+
this.toggleSeat(id);
|
|
2586
|
+
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Nearest SELECTABLE seat whose centre is within `slopPx` (screen space) of a
|
|
2589
|
+
* seat circle edge, or null. Enlarges the effective tap target for small seats
|
|
2590
|
+
* so a near-miss on mobile still lands on the intended seat, without ever
|
|
2591
|
+
* hijacking a clean tap on empty space beyond the slop.
|
|
2592
|
+
*/
|
|
2593
|
+
nearestSeatToScreen(screen, slopPx) {
|
|
2594
|
+
const s = this.stage.scaleX() || 1;
|
|
2595
|
+
const reachWorld = this.seatR + slopPx / s;
|
|
2596
|
+
const world = this.screenToWorld(screen);
|
|
2597
|
+
let best = null;
|
|
2598
|
+
let bestD = reachWorld;
|
|
2599
|
+
for (const seat of this.seats) {
|
|
2600
|
+
if (!this.selection.has(seat.id) && !this.isSelectable(seat.id)) continue;
|
|
2601
|
+
const d = Math.hypot(seat.x - world.x, seat.y - world.y);
|
|
2602
|
+
if (d < bestD) {
|
|
2603
|
+
bestD = d;
|
|
2604
|
+
best = seat.id;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
return best;
|
|
2608
|
+
}
|
|
2609
|
+
wireInteraction() {
|
|
2610
|
+
this.seatLayer.on("click tap", (e) => {
|
|
2611
|
+
if (this.moved > 8) return;
|
|
2612
|
+
const id = seatIdOf(e.target);
|
|
2613
|
+
if (!id) return;
|
|
2614
|
+
this.handleSeatTap(id);
|
|
2557
2615
|
});
|
|
2558
2616
|
this.seatLayer.on("mouseover", (e) => {
|
|
2559
2617
|
const id = seatIdOf(e.target);
|
|
@@ -2579,10 +2637,16 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2579
2637
|
const factor = Math.exp(-e.evt.deltaY * 2e-3);
|
|
2580
2638
|
this.zoomAbout(this.stage.scaleX() * clamp(factor, 0.5, 2), pointer);
|
|
2581
2639
|
});
|
|
2582
|
-
this.stage.on("click tap", () => {
|
|
2583
|
-
if (
|
|
2640
|
+
this.stage.on("click tap", (e) => {
|
|
2641
|
+
if (this.moved > 8) return;
|
|
2584
2642
|
const pointer = this.stage.getPointerPosition();
|
|
2585
2643
|
if (!pointer) return;
|
|
2644
|
+
if (!this.cached) {
|
|
2645
|
+
if (seatIdOf(e.target)) return;
|
|
2646
|
+
const near = this.nearestSeatToScreen(pointer, SEAT_TAP_SLOP_PX);
|
|
2647
|
+
if (near) this.handleSeatTap(near);
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2586
2650
|
if (this.stacked && this.opts.onDeckTap) {
|
|
2587
2651
|
const floorId = this.deckFloorAt();
|
|
2588
2652
|
if (floorId) {
|
|
@@ -2785,14 +2849,40 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2785
2849
|
this.seatLayer.batchDraw();
|
|
2786
2850
|
}
|
|
2787
2851
|
}
|
|
2788
|
-
|
|
2852
|
+
/** Rebuild the seat-layer bitmap synchronously (no paint). Shared by the
|
|
2853
|
+
* debounced cacheSeatLayer() and the synchronous forceDraw() catch-up. */
|
|
2854
|
+
rebuildSeatCache() {
|
|
2789
2855
|
const pr = clamp(this.stage.scaleX() * this.dpr, 0.15, 2);
|
|
2790
2856
|
this.seatLayer.clearCache();
|
|
2791
2857
|
this.seatLayer.cache({ pixelRatio: pr });
|
|
2792
2858
|
this.seatLayer.listening(false);
|
|
2793
2859
|
this.cached = true;
|
|
2860
|
+
}
|
|
2861
|
+
cacheSeatLayer() {
|
|
2862
|
+
this.rebuildSeatCache();
|
|
2794
2863
|
this.seatLayer.batchDraw();
|
|
2795
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
|
+
}
|
|
2796
2886
|
updateLabels() {
|
|
2797
2887
|
const show = this.effScale() > LABEL_SCALE;
|
|
2798
2888
|
this.labelGroup.destroyChildren();
|
|
@@ -2928,6 +3018,10 @@ var PickerController = class {
|
|
|
2928
3018
|
this.reconnectTimer = null;
|
|
2929
3019
|
this.attempt = 0;
|
|
2930
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;
|
|
2931
3025
|
// hold state
|
|
2932
3026
|
this.hold_ = null;
|
|
2933
3027
|
this.liveStatuses = /* @__PURE__ */ new Map();
|
|
@@ -3053,6 +3147,7 @@ var PickerController = class {
|
|
|
3053
3147
|
this.closedSections = new Set(closedIds);
|
|
3054
3148
|
if (closedIds.length) renderer.setClosedSections?.(closedIds);
|
|
3055
3149
|
if (seats) this.applySeatsMap(seats);
|
|
3150
|
+
this.attachVisibilityListener();
|
|
3056
3151
|
this.connect();
|
|
3057
3152
|
return {
|
|
3058
3153
|
doc: res.doc,
|
|
@@ -3479,6 +3574,7 @@ var PickerController = class {
|
|
|
3479
3574
|
}
|
|
3480
3575
|
destroy() {
|
|
3481
3576
|
this.closed = true;
|
|
3577
|
+
this.detachVisibilityListener();
|
|
3482
3578
|
if (this.reconnectTimer) {
|
|
3483
3579
|
clearTimeout(this.reconnectTimer);
|
|
3484
3580
|
this.reconnectTimer = null;
|
|
@@ -3675,6 +3771,37 @@ var PickerController = class {
|
|
|
3675
3771
|
}
|
|
3676
3772
|
}
|
|
3677
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
|
+
}
|
|
3678
3805
|
connect() {
|
|
3679
3806
|
if (this.closed) return;
|
|
3680
3807
|
let ws;
|
|
@@ -3720,6 +3847,9 @@ var PickerController = class {
|
|
|
3720
3847
|
}
|
|
3721
3848
|
r.setStatus([id], next);
|
|
3722
3849
|
}
|
|
3850
|
+
if (this.opts.keepLiveWhileHidden && typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
3851
|
+
r.forceDraw();
|
|
3852
|
+
}
|
|
3723
3853
|
this.clearBookedHoldIfSettled();
|
|
3724
3854
|
this.opts.onStatusChange?.();
|
|
3725
3855
|
}
|