@seatlayer/core 0.14.0 → 0.15.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 +298 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -4
- package/dist/index.d.ts +51 -4
- package/dist/index.js +298 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -954,6 +954,8 @@ var LABEL_SCALE = 1;
|
|
|
954
954
|
var SEAT_TAP_SLOP_PX = 14;
|
|
955
955
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
956
956
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
957
|
+
var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
958
|
+
var PAN_START_SLOP_PX = 8;
|
|
957
959
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
958
960
|
var MAX_LABELS = 700;
|
|
959
961
|
var MARQUEE_RING_CAP = 2500;
|
|
@@ -1104,6 +1106,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1104
1106
|
this.circleById = /* @__PURE__ */ new Map();
|
|
1105
1107
|
/** Booth block geometry, keyed by booth id (= the unit's rowId). */
|
|
1106
1108
|
this.boothDims = /* @__PURE__ */ new Map();
|
|
1109
|
+
/** Booth label node so status changes can say HELD/SOLD on the full block. */
|
|
1110
|
+
this.boothLabelById = /* @__PURE__ */ new Map();
|
|
1107
1111
|
this.statusById = /* @__PURE__ */ new Map();
|
|
1108
1112
|
this.catColor = /* @__PURE__ */ new Map();
|
|
1109
1113
|
this.theme = {};
|
|
@@ -1114,7 +1118,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1114
1118
|
/** Category order from the doc — the stable index into the CB palette. */
|
|
1115
1119
|
this.catOrder = [];
|
|
1116
1120
|
this.selection = /* @__PURE__ */ new Set();
|
|
1117
|
-
|
|
1121
|
+
/** Whole-seat/block selection markers: outline + non-colour check cue. */
|
|
1122
|
+
this.selectionMarkers = /* @__PURE__ */ new Map();
|
|
1123
|
+
/** Held by this picker instance, not by another buyer. */
|
|
1124
|
+
this.ownedHold = /* @__PURE__ */ new Set();
|
|
1125
|
+
/** One selected seat being inspected before it is committed to the cart. */
|
|
1126
|
+
this.selectionFocusId = null;
|
|
1118
1127
|
this.focusedId = null;
|
|
1119
1128
|
/**
|
|
1120
1129
|
* Accessibility filter: `null` = off; `[]` = dim all non-accessible free seats;
|
|
@@ -1183,7 +1192,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1183
1192
|
this.pointers = /* @__PURE__ */ new Map();
|
|
1184
1193
|
this.pinch = null;
|
|
1185
1194
|
this.panLast = null;
|
|
1186
|
-
|
|
1195
|
+
this.panStart = null;
|
|
1196
|
+
this.panStarted = false;
|
|
1197
|
+
/** Maximum displacement from gesture start — suppress taps only after a real pan/pinch. */
|
|
1187
1198
|
this.moved = 0;
|
|
1188
1199
|
/**
|
|
1189
1200
|
* Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
|
|
@@ -1226,15 +1237,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1226
1237
|
// pipeline. We rely on bubbling instead.
|
|
1227
1238
|
this.onPointerDown = (e) => {
|
|
1228
1239
|
this.cancelGlide();
|
|
1229
|
-
|
|
1240
|
+
const local = this.toLocal(e);
|
|
1241
|
+
this.pointers.set(e.pointerId, local);
|
|
1230
1242
|
if (this.pointers.size === 1) {
|
|
1231
1243
|
this.moved = 0;
|
|
1232
1244
|
this.pinch = null;
|
|
1245
|
+
this.panStart = local;
|
|
1246
|
+
this.panStarted = false;
|
|
1233
1247
|
if (this.opts.manageMode && this.opts.marqueeSelect && e.pointerType !== "touch" && e.button === 0 && this.getRung() === "seats") {
|
|
1234
|
-
this.beginMarquee(
|
|
1248
|
+
this.beginMarquee(local);
|
|
1235
1249
|
this.panLast = null;
|
|
1236
1250
|
} else {
|
|
1237
|
-
this.panLast =
|
|
1251
|
+
this.panLast = local;
|
|
1238
1252
|
}
|
|
1239
1253
|
} else if (this.pointers.size === 2) {
|
|
1240
1254
|
if (this.marquee) this.cancelMarquee();
|
|
@@ -1247,15 +1261,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1247
1261
|
worldMid: { x: (mid.x - this.stage.x()) / s, y: (mid.y - this.stage.y()) / s }
|
|
1248
1262
|
};
|
|
1249
1263
|
this.panLast = null;
|
|
1264
|
+
this.panStart = null;
|
|
1265
|
+
this.panStarted = true;
|
|
1266
|
+
this.moved = PAN_START_SLOP_PX + 1;
|
|
1250
1267
|
}
|
|
1251
1268
|
};
|
|
1252
1269
|
this.onPointerMove = (e) => {
|
|
1253
1270
|
if (!this.pointers.has(e.pointerId)) return;
|
|
1254
1271
|
e.preventDefault();
|
|
1255
1272
|
const p = this.toLocal(e);
|
|
1256
|
-
const prev = this.pointers.get(e.pointerId);
|
|
1257
|
-
this.moved += Math.hypot(p.x - prev.x, p.y - prev.y);
|
|
1258
1273
|
this.pointers.set(e.pointerId, p);
|
|
1274
|
+
if (this.pointers.size === 1 && this.panStart) {
|
|
1275
|
+
this.moved = Math.max(this.moved, Math.hypot(p.x - this.panStart.x, p.y - this.panStart.y));
|
|
1276
|
+
} else if (this.pointers.size >= 2) {
|
|
1277
|
+
this.moved = PAN_START_SLOP_PX + 1;
|
|
1278
|
+
}
|
|
1259
1279
|
if (this.marquee) {
|
|
1260
1280
|
this.updateMarquee(p);
|
|
1261
1281
|
return;
|
|
@@ -1275,6 +1295,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1275
1295
|
this.stage.batchDraw();
|
|
1276
1296
|
this.scheduleViewChange();
|
|
1277
1297
|
} else if (this.panLast && this.pointers.size === 1) {
|
|
1298
|
+
if (!this.panStarted) {
|
|
1299
|
+
if (this.moved <= PAN_START_SLOP_PX) return;
|
|
1300
|
+
this.panStarted = true;
|
|
1301
|
+
}
|
|
1278
1302
|
this.stage.position({
|
|
1279
1303
|
x: this.stage.x() + (p.x - this.panLast.x),
|
|
1280
1304
|
y: this.stage.y() + (p.y - this.panLast.y)
|
|
@@ -1295,9 +1319,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1295
1319
|
if (this.pointers.size < 2) this.pinch = null;
|
|
1296
1320
|
if (this.pointers.size === 1) {
|
|
1297
1321
|
this.panLast = [...this.pointers.values()][0];
|
|
1322
|
+
this.panStart = this.panLast;
|
|
1323
|
+
this.panStarted = true;
|
|
1298
1324
|
}
|
|
1299
1325
|
if (this.pointers.size === 0) {
|
|
1300
1326
|
this.panLast = null;
|
|
1327
|
+
this.panStart = null;
|
|
1328
|
+
this.panStarted = false;
|
|
1301
1329
|
this.afterViewChange();
|
|
1302
1330
|
}
|
|
1303
1331
|
};
|
|
@@ -1379,7 +1407,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1379
1407
|
this.labelGroup.destroyChildren();
|
|
1380
1408
|
this.circleById.clear();
|
|
1381
1409
|
this.boothDims.clear();
|
|
1382
|
-
this.
|
|
1410
|
+
this.boothLabelById.clear();
|
|
1411
|
+
this.selectionMarkers.clear();
|
|
1412
|
+
this.ownedHold.clear();
|
|
1413
|
+
this.selectionFocusId = null;
|
|
1383
1414
|
this.statusById.clear();
|
|
1384
1415
|
this.selection.clear();
|
|
1385
1416
|
this.seatById.clear();
|
|
@@ -1499,6 +1530,48 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1499
1530
|
} else {
|
|
1500
1531
|
this.seatLayer.batchDraw();
|
|
1501
1532
|
}
|
|
1533
|
+
if (this.effScale() > LABEL_SCALE) this.updateLabels();
|
|
1534
|
+
}
|
|
1535
|
+
setOwnedHold(seatIds) {
|
|
1536
|
+
const next = new Set((seatIds ?? []).filter((id) => this.statusById.has(id)));
|
|
1537
|
+
const touched = /* @__PURE__ */ new Set([...this.ownedHold, ...next]);
|
|
1538
|
+
this.ownedHold = next;
|
|
1539
|
+
for (const id of touched) {
|
|
1540
|
+
const shape = this.circleById.get(id);
|
|
1541
|
+
if (shape) this.paintSeat(shape, id);
|
|
1542
|
+
this.syncSelectionMarker(id);
|
|
1543
|
+
}
|
|
1544
|
+
if (!touched.size) return;
|
|
1545
|
+
if (this.cached) {
|
|
1546
|
+
if (this.recacheTimer) clearTimeout(this.recacheTimer);
|
|
1547
|
+
this.recacheTimer = setTimeout(() => this.cacheSeatLayer(), 150);
|
|
1548
|
+
} else {
|
|
1549
|
+
this.seatLayer.batchDraw();
|
|
1550
|
+
}
|
|
1551
|
+
if (this.effScale() > LABEL_SCALE) this.updateLabels();
|
|
1552
|
+
this.overlayLayer.batchDraw();
|
|
1553
|
+
}
|
|
1554
|
+
setSelectionFocus(seatId) {
|
|
1555
|
+
const next = seatId && this.selection.has(seatId) ? seatId : null;
|
|
1556
|
+
if (next === this.selectionFocusId) return;
|
|
1557
|
+
const previous = this.selectionFocusId;
|
|
1558
|
+
this.selectionFocusId = next;
|
|
1559
|
+
for (const seat of this.seats) {
|
|
1560
|
+
const shape = this.circleById.get(seat.id);
|
|
1561
|
+
if (shape) this.paintSeat(shape, seat.id);
|
|
1562
|
+
}
|
|
1563
|
+
if (previous) this.syncSelectionMarker(previous);
|
|
1564
|
+
if (next) this.syncSelectionMarker(next);
|
|
1565
|
+
for (const [id, marker] of this.selectionMarkers) {
|
|
1566
|
+
marker.opacity(!next || id === next ? 1 : 0.2);
|
|
1567
|
+
}
|
|
1568
|
+
if (this.cached) {
|
|
1569
|
+
this.seatLayer.clearCache();
|
|
1570
|
+
this.cacheSeatLayer();
|
|
1571
|
+
} else {
|
|
1572
|
+
this.seatLayer.batchDraw();
|
|
1573
|
+
}
|
|
1574
|
+
this.overlayLayer.batchDraw();
|
|
1502
1575
|
}
|
|
1503
1576
|
getStatus(seatId) {
|
|
1504
1577
|
return this.statusById.get(seatId) ?? "free";
|
|
@@ -1521,6 +1594,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1521
1594
|
setManageInteraction(options) {
|
|
1522
1595
|
if (this.marquee) this.cancelMarquee();
|
|
1523
1596
|
this.panLast = null;
|
|
1597
|
+
this.panStart = null;
|
|
1598
|
+
this.panStarted = false;
|
|
1524
1599
|
this.opts.manageMode = options.manageMode;
|
|
1525
1600
|
this.opts.marqueeSelect = options.marqueeSelect;
|
|
1526
1601
|
this.opts.selectableStatuses = [...options.selectableStatuses];
|
|
@@ -2117,6 +2192,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2117
2192
|
t2.offsetX(t2.width() / 2);
|
|
2118
2193
|
t2.offsetY(t2.height() / 2);
|
|
2119
2194
|
this.hasBoothText = true;
|
|
2195
|
+
this.boothLabelById.set(seat.id, t2);
|
|
2120
2196
|
target.add(t2);
|
|
2121
2197
|
}
|
|
2122
2198
|
/** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
|
|
@@ -2131,6 +2207,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2131
2207
|
const status = this.statusById.get(id) ?? "free";
|
|
2132
2208
|
const selected = this.selection.has(id);
|
|
2133
2209
|
const base = this.seatBaseColor(seat.categoryKey);
|
|
2210
|
+
const boothLabel = this.boothLabelById.get(id);
|
|
2134
2211
|
c.dash([]);
|
|
2135
2212
|
c.strokeWidth(0);
|
|
2136
2213
|
c.stroke("");
|
|
@@ -2142,7 +2219,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2142
2219
|
);
|
|
2143
2220
|
break;
|
|
2144
2221
|
case "held":
|
|
2145
|
-
|
|
2222
|
+
if (this.ownedHold.has(id)) {
|
|
2223
|
+
c.fill(lighten(base, this.effSelection === DEF_SELECTION ? 0.24 : 0.1));
|
|
2224
|
+
c.stroke(this.effSelection);
|
|
2225
|
+
c.strokeWidth(3);
|
|
2226
|
+
} else {
|
|
2227
|
+
c.fill(HELD_FILL);
|
|
2228
|
+
}
|
|
2146
2229
|
break;
|
|
2147
2230
|
case "booked":
|
|
2148
2231
|
if (this.colorblind) {
|
|
@@ -2162,6 +2245,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2162
2245
|
c.dash([2, 2]);
|
|
2163
2246
|
break;
|
|
2164
2247
|
}
|
|
2248
|
+
if (boothLabel) {
|
|
2249
|
+
boothLabel.text(status === "booked" ? "SOLD" : status === "held" ? "HELD" : seat.label);
|
|
2250
|
+
boothLabel.fontSize(status === "free" ? 10 : Math.min(10, Math.max(6, this.boothDims.get(seat.rowId)?.width ?? 40) / 6));
|
|
2251
|
+
boothLabel.fontStyle(status === "free" ? "600" : "800");
|
|
2252
|
+
boothLabel.fill(status === "free" ? this.theme.seatLabelColor ?? DEF_SEAT_LABEL : "#ffffff");
|
|
2253
|
+
boothLabel.offsetX(boothLabel.width() / 2);
|
|
2254
|
+
boothLabel.offsetY(boothLabel.height() / 2);
|
|
2255
|
+
}
|
|
2165
2256
|
if (this.accessFilter && status === "free" && !selected && !seatMatchesAccess(seat, this.accessFilter)) {
|
|
2166
2257
|
c.opacity(0.25);
|
|
2167
2258
|
}
|
|
@@ -2189,6 +2280,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2189
2280
|
const inFocus = !!sec && (sec.id === this.focusedSectionId || sec.zone === this.focusedSectionId);
|
|
2190
2281
|
if (!inFocus) c.opacity(FOCUS_DIM_OPACITY);
|
|
2191
2282
|
}
|
|
2283
|
+
if (this.selectionFocusId && id !== this.selectionFocusId) c.opacity(Math.min(c.opacity(), 0.16));
|
|
2192
2284
|
}
|
|
2193
2285
|
/** True when a seat sits in a section/zone currently marked `closed`. */
|
|
2194
2286
|
seatInClosedSection(id) {
|
|
@@ -2256,7 +2348,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2256
2348
|
this.drawFocusBackdrop(id);
|
|
2257
2349
|
this.repaintSectionsAndSeats();
|
|
2258
2350
|
this.updateLOD();
|
|
2259
|
-
this.focusRegion(id);
|
|
2351
|
+
this.focusRegion(id, { minScale: SEAT_FOCUS_SCALE });
|
|
2260
2352
|
}
|
|
2261
2353
|
/** Clear an AXS section focus — restore full-bowl brightness + drop the backdrop. */
|
|
2262
2354
|
clearSectionFocus() {
|
|
@@ -3049,30 +3141,101 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3049
3141
|
const c = this.circleById.get(id);
|
|
3050
3142
|
if (on) {
|
|
3051
3143
|
this.selection.add(id);
|
|
3052
|
-
const seat = this.seatById.get(id);
|
|
3053
|
-
const ring = new import_Circle.Circle({
|
|
3054
|
-
x: seat.x,
|
|
3055
|
-
y: seat.y,
|
|
3056
|
-
radius: this.seatR,
|
|
3057
|
-
stroke: this.effSelection,
|
|
3058
|
-
strokeWidth: 3,
|
|
3059
|
-
listening: false,
|
|
3060
|
-
perfectDrawEnabled: false,
|
|
3061
|
-
shadowForStrokeEnabled: false
|
|
3062
|
-
});
|
|
3063
|
-
this.selectionRings.set(id, ring);
|
|
3064
|
-
this.overlayLayer.add(ring);
|
|
3065
3144
|
} else {
|
|
3145
|
+
if (this.selectionFocusId === id) this.setSelectionFocus(null);
|
|
3066
3146
|
this.selection.delete(id);
|
|
3067
|
-
const ring = this.selectionRings.get(id);
|
|
3068
|
-
ring?.destroy();
|
|
3069
|
-
this.selectionRings.delete(id);
|
|
3070
3147
|
}
|
|
3148
|
+
this.syncSelectionMarker(id);
|
|
3071
3149
|
if (c) {
|
|
3072
3150
|
this.paintSeat(c, id);
|
|
3073
3151
|
if (!silent && !this.cached) this.seatLayer.batchDraw();
|
|
3074
3152
|
}
|
|
3075
3153
|
}
|
|
3154
|
+
/** Rebuild one marker after selected/held/candidate state changes. */
|
|
3155
|
+
syncSelectionMarker(id) {
|
|
3156
|
+
this.selectionMarkers.get(id)?.destroy();
|
|
3157
|
+
this.selectionMarkers.delete(id);
|
|
3158
|
+
if (!this.selection.has(id) && !this.ownedHold.has(id)) return;
|
|
3159
|
+
const seat = this.seatById.get(id);
|
|
3160
|
+
if (!seat) return;
|
|
3161
|
+
const candidate = this.selectionFocusId === id;
|
|
3162
|
+
const dims = this.boothDims.get(seat.rowId);
|
|
3163
|
+
const marker = new import_Group.Group({
|
|
3164
|
+
x: seat.x,
|
|
3165
|
+
y: seat.y,
|
|
3166
|
+
rotation: dims?.rotation ?? 0,
|
|
3167
|
+
listening: false,
|
|
3168
|
+
perfectDrawEnabled: false,
|
|
3169
|
+
opacity: this.selectionFocusId && !candidate ? 0.2 : 1
|
|
3170
|
+
});
|
|
3171
|
+
const common = {
|
|
3172
|
+
stroke: this.effSelection,
|
|
3173
|
+
listening: false,
|
|
3174
|
+
perfectDrawEnabled: false,
|
|
3175
|
+
shadowForStrokeEnabled: false
|
|
3176
|
+
};
|
|
3177
|
+
if (dims) {
|
|
3178
|
+
marker.add(new import_Rect.Rect({
|
|
3179
|
+
...common,
|
|
3180
|
+
width: dims.width,
|
|
3181
|
+
height: dims.height,
|
|
3182
|
+
offsetX: dims.width / 2,
|
|
3183
|
+
offsetY: dims.height / 2,
|
|
3184
|
+
cornerRadius: 4,
|
|
3185
|
+
strokeWidth: candidate ? 4 : 3
|
|
3186
|
+
}));
|
|
3187
|
+
if (candidate) {
|
|
3188
|
+
marker.add(new import_Rect.Rect({
|
|
3189
|
+
...common,
|
|
3190
|
+
width: dims.width + 10,
|
|
3191
|
+
height: dims.height + 10,
|
|
3192
|
+
offsetX: (dims.width + 10) / 2,
|
|
3193
|
+
offsetY: (dims.height + 10) / 2,
|
|
3194
|
+
cornerRadius: 7,
|
|
3195
|
+
strokeWidth: 2,
|
|
3196
|
+
opacity: 0.55
|
|
3197
|
+
}));
|
|
3198
|
+
} else {
|
|
3199
|
+
const badgeX = Math.max(0, dims.width / 2 - 14);
|
|
3200
|
+
const badgeY = -Math.max(0, dims.height / 2 - 14);
|
|
3201
|
+
marker.add(new import_Circle.Circle({ x: badgeX, y: badgeY, radius: 10, fill: this.effSelection, listening: false }));
|
|
3202
|
+
marker.add(new import_Line.Line({
|
|
3203
|
+
x: badgeX,
|
|
3204
|
+
y: badgeY,
|
|
3205
|
+
points: [-4.5, 0, -1, 3.5, 5.5, -4.5],
|
|
3206
|
+
stroke: isLightColor(this.effSelection) ? "#0b1220" : "#ffffff",
|
|
3207
|
+
strokeWidth: 2.4,
|
|
3208
|
+
lineCap: "round",
|
|
3209
|
+
lineJoin: "round",
|
|
3210
|
+
listening: false
|
|
3211
|
+
}));
|
|
3212
|
+
}
|
|
3213
|
+
} else {
|
|
3214
|
+
marker.add(new import_Circle.Circle({ ...common, radius: this.seatR + (candidate ? 4.5 : 2.5), strokeWidth: candidate ? 3.5 : 2.5 }));
|
|
3215
|
+
if (candidate) {
|
|
3216
|
+
marker.add(new import_Circle.Circle({
|
|
3217
|
+
...common,
|
|
3218
|
+
radius: this.seatR + 8,
|
|
3219
|
+
strokeWidth: 2,
|
|
3220
|
+
opacity: 0.55
|
|
3221
|
+
}));
|
|
3222
|
+
} else {
|
|
3223
|
+
marker.add(new import_Line.Line({
|
|
3224
|
+
points: [-this.seatR * 0.52, 0, -this.seatR * 0.12, this.seatR * 0.4, this.seatR * 0.6, -this.seatR * 0.46],
|
|
3225
|
+
stroke: "#ffffff",
|
|
3226
|
+
strokeWidth: Math.max(2.6, this.seatR * 0.34),
|
|
3227
|
+
lineCap: "round",
|
|
3228
|
+
lineJoin: "round",
|
|
3229
|
+
shadowColor: "#0b1220",
|
|
3230
|
+
shadowBlur: 1.5,
|
|
3231
|
+
shadowOpacity: 0.55,
|
|
3232
|
+
listening: false
|
|
3233
|
+
}));
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
this.selectionMarkers.set(id, marker);
|
|
3237
|
+
this.overlayLayer.add(marker);
|
|
3238
|
+
}
|
|
3076
3239
|
// ---- interaction ----------------------------------------------------------
|
|
3077
3240
|
/**
|
|
3078
3241
|
* The stage scale `focusRegion(id)` would settle at — i.e. the zoom that
|
|
@@ -3090,7 +3253,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3090
3253
|
const { min, max } = this.zoomBounds();
|
|
3091
3254
|
const margin = 1.12;
|
|
3092
3255
|
if (b.width <= 0 || b.height <= 0) return this.stage.scaleX();
|
|
3093
|
-
|
|
3256
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3257
|
+
return clamp(Math.max(frameScale, SEAT_FOCUS_SCALE), min, max);
|
|
3094
3258
|
}
|
|
3095
3259
|
/**
|
|
3096
3260
|
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
@@ -3148,7 +3312,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3148
3312
|
}
|
|
3149
3313
|
wireInteraction() {
|
|
3150
3314
|
this.seatLayer.on("click tap", (e) => {
|
|
3151
|
-
if (this.moved >
|
|
3315
|
+
if (this.moved > PAN_START_SLOP_PX) return;
|
|
3152
3316
|
const id = seatIdOf(e.target);
|
|
3153
3317
|
if (!id) return;
|
|
3154
3318
|
this.handleSeatTap(id);
|
|
@@ -3178,7 +3342,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3178
3342
|
this.zoomAbout(this.stage.scaleX() * clamp(factor, 0.5, 2), pointer);
|
|
3179
3343
|
});
|
|
3180
3344
|
this.stage.on("click tap", (e) => {
|
|
3181
|
-
if (this.moved >
|
|
3345
|
+
if (this.moved > PAN_START_SLOP_PX) return;
|
|
3182
3346
|
const pointer = this.stage.getPointerPosition();
|
|
3183
3347
|
if (!pointer) return;
|
|
3184
3348
|
if (!this.cached) {
|
|
@@ -3262,12 +3426,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3262
3426
|
this.stage.batchDraw();
|
|
3263
3427
|
}
|
|
3264
3428
|
/** Zoom + pan so world-rect `b` fills the viewport (with a small margin). */
|
|
3265
|
-
zoomToBounds(b) {
|
|
3429
|
+
zoomToBounds(b, minScale) {
|
|
3266
3430
|
const w = this.stage.width();
|
|
3267
3431
|
const h = this.stage.height();
|
|
3268
3432
|
const { min, max } = this.zoomBounds();
|
|
3269
3433
|
const margin = 1.12;
|
|
3270
|
-
const
|
|
3434
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3435
|
+
const scale = clamp(Math.max(frameScale, minScale ?? min), min, max);
|
|
3271
3436
|
this.stage.scale({ x: scale, y: scale });
|
|
3272
3437
|
this.stage.position({
|
|
3273
3438
|
x: w / 2 - (b.x + b.width / 2) * scale,
|
|
@@ -3291,14 +3456,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3291
3456
|
if (!b) return;
|
|
3292
3457
|
this.cancelGlide();
|
|
3293
3458
|
if (opts?.animate === false || this.reducedMotion) {
|
|
3294
|
-
this.zoomToBounds(b);
|
|
3459
|
+
this.zoomToBounds(b, opts?.minScale);
|
|
3295
3460
|
return;
|
|
3296
3461
|
}
|
|
3297
3462
|
const w = this.stage.width();
|
|
3298
3463
|
const h = this.stage.height();
|
|
3299
3464
|
const { min, max } = this.zoomBounds();
|
|
3300
3465
|
const margin = 1.12;
|
|
3301
|
-
const
|
|
3466
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3467
|
+
const toScale = clamp(Math.max(frameScale, opts?.minScale ?? min), min, max);
|
|
3302
3468
|
const toX = w / 2 - (b.x + b.width / 2) * toScale;
|
|
3303
3469
|
const toY = h / 2 - (b.y + b.height / 2) * toScale;
|
|
3304
3470
|
const fromScale = this.stage.scaleX();
|
|
@@ -3353,7 +3519,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3353
3519
|
this.zoomToFit();
|
|
3354
3520
|
return;
|
|
3355
3521
|
}
|
|
3356
|
-
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(
|
|
3522
|
+
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(SEAT_FOCUS_SCALE, CACHE_THRESHOLD * 1.3);
|
|
3357
3523
|
const w = this.stage.width();
|
|
3358
3524
|
const h = this.stage.height();
|
|
3359
3525
|
const cx = this.bounds.x + this.bounds.width / 2;
|
|
@@ -3439,19 +3605,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3439
3605
|
for (const seat of this.seats) {
|
|
3440
3606
|
if (seat.kind === "booth") continue;
|
|
3441
3607
|
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
3608
|
+
const status = this.statusById.get(seat.id) ?? "free";
|
|
3609
|
+
const statusCue = status === "booked" ? "\xD7" : status === "held" && !this.ownedHold.has(seat.id) ? "H" : null;
|
|
3442
3610
|
const t2 = new import_Text.Text({
|
|
3443
3611
|
x: seat.x,
|
|
3444
3612
|
y: seat.y,
|
|
3445
|
-
text: seat.label,
|
|
3446
|
-
fontSize: 7,
|
|
3447
|
-
fontStyle: "600",
|
|
3613
|
+
text: statusCue ?? seat.label,
|
|
3614
|
+
fontSize: statusCue ? status === "booked" ? 13 : 8 : 7,
|
|
3615
|
+
fontStyle: statusCue ? "800" : "600",
|
|
3448
3616
|
fontFamily: this.labelFont(),
|
|
3449
|
-
fill: this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
3617
|
+
fill: statusCue ? "#ffffff" : this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
3450
3618
|
listening: false,
|
|
3451
3619
|
perfectDrawEnabled: false
|
|
3452
3620
|
});
|
|
3453
3621
|
const maxW = this.seatR * 2 - 3;
|
|
3454
|
-
if (t2.width() > maxW) t2.fontSize(Math.max(4,
|
|
3622
|
+
if (t2.width() > maxW) t2.fontSize(Math.max(4, t2.fontSize() * maxW / t2.width()));
|
|
3455
3623
|
if (t2.fontSize() < 4.2) {
|
|
3456
3624
|
t2.destroy();
|
|
3457
3625
|
continue;
|
|
@@ -3553,6 +3721,8 @@ var PickerController = class {
|
|
|
3553
3721
|
this.seatTiers = /* @__PURE__ */ new Map();
|
|
3554
3722
|
/** id → seat, for the section-summary breakdown (renderer members are ids). */
|
|
3555
3723
|
this.seatById = /* @__PURE__ */ new Map();
|
|
3724
|
+
/** id → buyer-facing spatial metadata used by every tooltip/confirm surface. */
|
|
3725
|
+
this.seatContext = /* @__PURE__ */ new Map();
|
|
3556
3726
|
this.allIds = [];
|
|
3557
3727
|
// realtime socket
|
|
3558
3728
|
this.ws = null;
|
|
@@ -3631,12 +3801,28 @@ var PickerController = class {
|
|
|
3631
3801
|
this.labelToId = /* @__PURE__ */ new Map();
|
|
3632
3802
|
this.labelToSeat = /* @__PURE__ */ new Map();
|
|
3633
3803
|
this.seatById = /* @__PURE__ */ new Map();
|
|
3804
|
+
this.seatContext = /* @__PURE__ */ new Map();
|
|
3634
3805
|
this.allIds = [];
|
|
3806
|
+
const chartObjects = new Map(allObjects(res.doc).map((object) => [object.id, object]));
|
|
3807
|
+
const membership = computeSections(res.doc);
|
|
3808
|
+
const sectionLabels = new Map(
|
|
3809
|
+
[...membership.sections, ...membership.ungrouped ? [membership.ungrouped] : []].map((section) => [section.id, section.label])
|
|
3810
|
+
);
|
|
3635
3811
|
for (const s of expandChart(res.doc)) {
|
|
3636
3812
|
this.labelToId.set(s.label, s.id);
|
|
3637
3813
|
this.labelToSeat.set(s.label, s);
|
|
3638
3814
|
this.seatById.set(s.id, s);
|
|
3639
3815
|
this.allIds.push(s.id);
|
|
3816
|
+
const source = chartObjects.get(s.rowId);
|
|
3817
|
+
const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
|
|
3818
|
+
const rowLabel = s.kind === "booth" ? void 0 : sourceLabel;
|
|
3819
|
+
const labelParts = s.label.split("-");
|
|
3820
|
+
const seatNumber = rowLabel && s.label.startsWith(`${rowLabel}-`) ? s.label.slice(rowLabel.length + 1) : s.kind === "booth" ? s.label : labelParts[labelParts.length - 1] ?? s.label;
|
|
3821
|
+
this.seatContext.set(s.id, {
|
|
3822
|
+
sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
|
|
3823
|
+
rowLabel,
|
|
3824
|
+
seatNumber
|
|
3825
|
+
});
|
|
3640
3826
|
}
|
|
3641
3827
|
const currency = res.event.currency ?? this.opts.currency;
|
|
3642
3828
|
this.currency = currency ?? "USD";
|
|
@@ -3706,6 +3892,11 @@ var PickerController = class {
|
|
|
3706
3892
|
if (!this.renderer) return [];
|
|
3707
3893
|
return this.renderer.getSelection().map((s) => this.toSeat(s));
|
|
3708
3894
|
}
|
|
3895
|
+
/** Enriched metadata for a seat confirmation card or tooltip. */
|
|
3896
|
+
seatDetails(seatId) {
|
|
3897
|
+
const seat = this.seatById.get(seatId);
|
|
3898
|
+
return seat ? this.describeSeat(seat) : null;
|
|
3899
|
+
}
|
|
3709
3900
|
clearSelection() {
|
|
3710
3901
|
this.renderer?.clearSelection();
|
|
3711
3902
|
this.emitSelectionChange();
|
|
@@ -3745,6 +3936,19 @@ var PickerController = class {
|
|
|
3745
3936
|
throw err;
|
|
3746
3937
|
}
|
|
3747
3938
|
}
|
|
3939
|
+
/** Restore an active server hold without creating or extending inventory. */
|
|
3940
|
+
async resumeHold(holdId) {
|
|
3941
|
+
if (this.closed || !holdId || !this.api.resume) return null;
|
|
3942
|
+
const result = await this.api.resume(this.key, holdId);
|
|
3943
|
+
if (this.closed) return null;
|
|
3944
|
+
const labels = [...new Set(result.items.map((item) => item.label))];
|
|
3945
|
+
if (!labels.length) return null;
|
|
3946
|
+
this.setHold(
|
|
3947
|
+
{ holdId: result.holdId, labels, expiresAt: result.expiresAt, items: result.items },
|
|
3948
|
+
"restored"
|
|
3949
|
+
);
|
|
3950
|
+
return this.hold_;
|
|
3951
|
+
}
|
|
3748
3952
|
/**
|
|
3749
3953
|
* P4 "need more time?": extend the OPEN hold's server-side expiry and re-arm
|
|
3750
3954
|
* the client expiry timer to match (via setHold), so the controller doesn't
|
|
@@ -3834,7 +4038,7 @@ var PickerController = class {
|
|
|
3834
4038
|
async bestAvailable(qty, categoryKey) {
|
|
3835
4039
|
const r = this.renderer;
|
|
3836
4040
|
if (!r) return null;
|
|
3837
|
-
if (this.hold_
|
|
4041
|
+
if (this.hold_ && !await this.release()) return null;
|
|
3838
4042
|
try {
|
|
3839
4043
|
const result = await this.api.bestAvailable(this.key, qty, categoryKey);
|
|
3840
4044
|
r.clearSelection();
|
|
@@ -3919,15 +4123,25 @@ var PickerController = class {
|
|
|
3919
4123
|
/** Release the whole open hold (if any), repaint those seats free. */
|
|
3920
4124
|
async release() {
|
|
3921
4125
|
const hold = this.hold_;
|
|
3922
|
-
if (!hold) return;
|
|
3923
|
-
this.clearHold();
|
|
3924
|
-
const ids = hold.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
3925
|
-
if (ids.length) this.renderer?.setStatus(ids, "free");
|
|
4126
|
+
if (!hold) return true;
|
|
3926
4127
|
try {
|
|
3927
|
-
await this.api.release(this.key, hold.labels, hold.holdId);
|
|
4128
|
+
const result = await this.api.release(this.key, hold.labels, hold.holdId);
|
|
4129
|
+
if (!this.releaseConfirmed(result, hold.labels)) {
|
|
4130
|
+
await this.resnapshot();
|
|
4131
|
+
return false;
|
|
4132
|
+
}
|
|
3928
4133
|
} catch (err) {
|
|
3929
4134
|
this.emitError(err);
|
|
4135
|
+
return false;
|
|
4136
|
+
}
|
|
4137
|
+
if (this.hold_?.holdId !== hold.holdId) return true;
|
|
4138
|
+
this.clearHold();
|
|
4139
|
+
const ids = hold.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
4140
|
+
if (ids.length) {
|
|
4141
|
+
this.renderer?.deselect(ids);
|
|
4142
|
+
this.renderer?.setStatus(ids, "free");
|
|
3930
4143
|
}
|
|
4144
|
+
return true;
|
|
3931
4145
|
}
|
|
3932
4146
|
/**
|
|
3933
4147
|
* Release just some labels from the open hold, keeping the rest held (used when
|
|
@@ -3935,19 +4149,35 @@ var PickerController = class {
|
|
|
3935
4149
|
*/
|
|
3936
4150
|
async releaseLabels(labels) {
|
|
3937
4151
|
const hold = this.hold_;
|
|
3938
|
-
if (!hold) return;
|
|
4152
|
+
if (!hold) return true;
|
|
3939
4153
|
const drop = labels.filter((l) => hold.labels.includes(l));
|
|
3940
|
-
if (!drop.length) return;
|
|
3941
|
-
const remaining = hold.labels.filter((l) => !drop.includes(l));
|
|
3942
|
-
if (remaining.length) this.setHold({ ...hold, labels: remaining });
|
|
3943
|
-
else this.clearHold();
|
|
3944
|
-
const ids = drop.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
3945
|
-
if (ids.length) this.renderer?.setStatus(ids, "free");
|
|
4154
|
+
if (!drop.length) return true;
|
|
3946
4155
|
try {
|
|
3947
|
-
await this.api.release(this.key, drop, hold.holdId);
|
|
4156
|
+
const result = await this.api.release(this.key, drop, hold.holdId);
|
|
4157
|
+
if (!this.releaseConfirmed(result, drop)) {
|
|
4158
|
+
await this.resnapshot();
|
|
4159
|
+
return false;
|
|
4160
|
+
}
|
|
3948
4161
|
} catch (err) {
|
|
3949
4162
|
this.emitError(err);
|
|
4163
|
+
return false;
|
|
3950
4164
|
}
|
|
4165
|
+
if (this.hold_?.holdId !== hold.holdId) return true;
|
|
4166
|
+
const remaining = hold.labels.filter((l) => !drop.includes(l));
|
|
4167
|
+
const remainingItems = hold.items?.filter((item) => !drop.includes(item.label));
|
|
4168
|
+
if (remaining.length) this.setHold({ ...hold, labels: remaining, items: remainingItems });
|
|
4169
|
+
else this.clearHold();
|
|
4170
|
+
const ids = drop.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
4171
|
+
if (ids.length) {
|
|
4172
|
+
this.renderer?.deselect(ids);
|
|
4173
|
+
this.renderer?.setStatus(ids, "free");
|
|
4174
|
+
}
|
|
4175
|
+
return true;
|
|
4176
|
+
}
|
|
4177
|
+
/** New transports return the exact labels released; tolerate older adapters. */
|
|
4178
|
+
releaseConfirmed(result, requested) {
|
|
4179
|
+
const released = result?.released;
|
|
4180
|
+
return !Array.isArray(released) || requested.every((label) => released.includes(label));
|
|
3951
4181
|
}
|
|
3952
4182
|
// ---- renderer proxies (so consumers don't reach through) ------------------
|
|
3953
4183
|
setStatus(ids, status) {
|
|
@@ -3971,6 +4201,9 @@ var PickerController = class {
|
|
|
3971
4201
|
worldToScreen(p) {
|
|
3972
4202
|
return this.renderer?.worldToScreen(p) ?? { x: 0, y: 0 };
|
|
3973
4203
|
}
|
|
4204
|
+
setSelectionFocus(seatId) {
|
|
4205
|
+
this.renderer?.setSelectionFocus?.(seatId);
|
|
4206
|
+
}
|
|
3974
4207
|
setAccessibilityFilter(types) {
|
|
3975
4208
|
this.renderer?.setAccessibilityFilter?.(types);
|
|
3976
4209
|
}
|
|
@@ -4158,7 +4391,8 @@ var PickerController = class {
|
|
|
4158
4391
|
categoryLabel: cat?.label ?? s.categoryKey,
|
|
4159
4392
|
categoryColor: cat?.color ?? "#6e7bff",
|
|
4160
4393
|
status: this.renderer?.getStatus(s.id) ?? "free",
|
|
4161
|
-
currency: this.currency
|
|
4394
|
+
currency: this.currency,
|
|
4395
|
+
...this.seatContext.get(s.id)
|
|
4162
4396
|
};
|
|
4163
4397
|
}
|
|
4164
4398
|
priceFor(categoryKey) {
|
|
@@ -4249,16 +4483,21 @@ var PickerController = class {
|
|
|
4249
4483
|
this.emitSelectionChange();
|
|
4250
4484
|
}
|
|
4251
4485
|
/** Set the open hold + (re)arm the server-authoritative expiry timer. */
|
|
4252
|
-
setHold(hold) {
|
|
4486
|
+
setHold(hold, source = "created") {
|
|
4253
4487
|
const full = { ...hold, seats: this.seatsForLabels(hold.labels) };
|
|
4254
4488
|
this.hold_ = full;
|
|
4489
|
+
this.renderer?.setOwnedHold?.(
|
|
4490
|
+
full.labels.map((label) => this.labelToId.get(label)).filter((id) => !!id)
|
|
4491
|
+
);
|
|
4255
4492
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
4256
4493
|
const ms = Math.max(0, full.expiresAt - Date.now());
|
|
4257
4494
|
this.expiryTimer = setTimeout(() => void this.expireActiveHold(), ms);
|
|
4258
|
-
this.opts.
|
|
4495
|
+
if (source === "restored") this.opts.onHoldRestored?.(full);
|
|
4496
|
+
else this.opts.onHold?.(full);
|
|
4259
4497
|
}
|
|
4260
4498
|
clearHold() {
|
|
4261
4499
|
this.hold_ = null;
|
|
4500
|
+
this.renderer?.setOwnedHold?.(null);
|
|
4262
4501
|
if (this.expiryTimer) {
|
|
4263
4502
|
clearTimeout(this.expiryTimer);
|
|
4264
4503
|
this.expiryTimer = null;
|
|
@@ -4290,6 +4529,9 @@ var PickerController = class {
|
|
|
4290
4529
|
if (!r) return;
|
|
4291
4530
|
this.liveStatuses = new Map(Object.entries(seats));
|
|
4292
4531
|
if (this.allIds.length) r.setStatus(this.allIds, "free");
|
|
4532
|
+
r.setOwnedHold?.(
|
|
4533
|
+
(this.hold_?.labels ?? []).map((label) => this.labelToId.get(label)).filter((id) => !!id)
|
|
4534
|
+
);
|
|
4293
4535
|
const byStatus = { free: [], held: [], booked: [], not_for_sale: [] };
|
|
4294
4536
|
for (const [label, st] of Object.entries(seats)) {
|
|
4295
4537
|
const id = this.labelToId.get(label);
|