@seatlayer/core 0.13.0 → 0.15.0
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 +299 -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 +299 -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,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2162
2245
|
c.dash([2, 2]);
|
|
2163
2246
|
break;
|
|
2164
2247
|
}
|
|
2248
|
+
if (boothLabel) {
|
|
2249
|
+
const owned = status === "held" && this.ownedHold.has(id);
|
|
2250
|
+
boothLabel.text(status === "booked" ? "SOLD" : status === "held" ? owned ? "HELD BY YOU" : "HELD" : seat.label);
|
|
2251
|
+
boothLabel.fontSize(status === "free" ? 10 : Math.min(10, Math.max(6, this.boothDims.get(seat.rowId)?.width ?? 40) / 6));
|
|
2252
|
+
boothLabel.fontStyle(status === "free" ? "600" : "800");
|
|
2253
|
+
boothLabel.fill(status === "free" ? this.theme.seatLabelColor ?? DEF_SEAT_LABEL : "#ffffff");
|
|
2254
|
+
boothLabel.offsetX(boothLabel.width() / 2);
|
|
2255
|
+
boothLabel.offsetY(boothLabel.height() / 2);
|
|
2256
|
+
}
|
|
2165
2257
|
if (this.accessFilter && status === "free" && !selected && !seatMatchesAccess(seat, this.accessFilter)) {
|
|
2166
2258
|
c.opacity(0.25);
|
|
2167
2259
|
}
|
|
@@ -2189,6 +2281,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2189
2281
|
const inFocus = !!sec && (sec.id === this.focusedSectionId || sec.zone === this.focusedSectionId);
|
|
2190
2282
|
if (!inFocus) c.opacity(FOCUS_DIM_OPACITY);
|
|
2191
2283
|
}
|
|
2284
|
+
if (this.selectionFocusId && id !== this.selectionFocusId) c.opacity(Math.min(c.opacity(), 0.16));
|
|
2192
2285
|
}
|
|
2193
2286
|
/** True when a seat sits in a section/zone currently marked `closed`. */
|
|
2194
2287
|
seatInClosedSection(id) {
|
|
@@ -2256,7 +2349,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2256
2349
|
this.drawFocusBackdrop(id);
|
|
2257
2350
|
this.repaintSectionsAndSeats();
|
|
2258
2351
|
this.updateLOD();
|
|
2259
|
-
this.focusRegion(id);
|
|
2352
|
+
this.focusRegion(id, { minScale: SEAT_FOCUS_SCALE });
|
|
2260
2353
|
}
|
|
2261
2354
|
/** Clear an AXS section focus — restore full-bowl brightness + drop the backdrop. */
|
|
2262
2355
|
clearSectionFocus() {
|
|
@@ -3049,30 +3142,101 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3049
3142
|
const c = this.circleById.get(id);
|
|
3050
3143
|
if (on) {
|
|
3051
3144
|
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
3145
|
} else {
|
|
3146
|
+
if (this.selectionFocusId === id) this.setSelectionFocus(null);
|
|
3066
3147
|
this.selection.delete(id);
|
|
3067
|
-
const ring = this.selectionRings.get(id);
|
|
3068
|
-
ring?.destroy();
|
|
3069
|
-
this.selectionRings.delete(id);
|
|
3070
3148
|
}
|
|
3149
|
+
this.syncSelectionMarker(id);
|
|
3071
3150
|
if (c) {
|
|
3072
3151
|
this.paintSeat(c, id);
|
|
3073
3152
|
if (!silent && !this.cached) this.seatLayer.batchDraw();
|
|
3074
3153
|
}
|
|
3075
3154
|
}
|
|
3155
|
+
/** Rebuild one marker after selected/held/candidate state changes. */
|
|
3156
|
+
syncSelectionMarker(id) {
|
|
3157
|
+
this.selectionMarkers.get(id)?.destroy();
|
|
3158
|
+
this.selectionMarkers.delete(id);
|
|
3159
|
+
if (!this.selection.has(id) && !this.ownedHold.has(id)) return;
|
|
3160
|
+
const seat = this.seatById.get(id);
|
|
3161
|
+
if (!seat) return;
|
|
3162
|
+
const candidate = this.selectionFocusId === id;
|
|
3163
|
+
const dims = this.boothDims.get(seat.rowId);
|
|
3164
|
+
const marker = new import_Group.Group({
|
|
3165
|
+
x: seat.x,
|
|
3166
|
+
y: seat.y,
|
|
3167
|
+
rotation: dims?.rotation ?? 0,
|
|
3168
|
+
listening: false,
|
|
3169
|
+
perfectDrawEnabled: false,
|
|
3170
|
+
opacity: this.selectionFocusId && !candidate ? 0.2 : 1
|
|
3171
|
+
});
|
|
3172
|
+
const common = {
|
|
3173
|
+
stroke: this.effSelection,
|
|
3174
|
+
listening: false,
|
|
3175
|
+
perfectDrawEnabled: false,
|
|
3176
|
+
shadowForStrokeEnabled: false
|
|
3177
|
+
};
|
|
3178
|
+
if (dims) {
|
|
3179
|
+
marker.add(new import_Rect.Rect({
|
|
3180
|
+
...common,
|
|
3181
|
+
width: dims.width,
|
|
3182
|
+
height: dims.height,
|
|
3183
|
+
offsetX: dims.width / 2,
|
|
3184
|
+
offsetY: dims.height / 2,
|
|
3185
|
+
cornerRadius: 4,
|
|
3186
|
+
strokeWidth: candidate ? 4 : 3
|
|
3187
|
+
}));
|
|
3188
|
+
if (candidate) {
|
|
3189
|
+
marker.add(new import_Rect.Rect({
|
|
3190
|
+
...common,
|
|
3191
|
+
width: dims.width + 10,
|
|
3192
|
+
height: dims.height + 10,
|
|
3193
|
+
offsetX: (dims.width + 10) / 2,
|
|
3194
|
+
offsetY: (dims.height + 10) / 2,
|
|
3195
|
+
cornerRadius: 7,
|
|
3196
|
+
strokeWidth: 2,
|
|
3197
|
+
opacity: 0.55
|
|
3198
|
+
}));
|
|
3199
|
+
} else {
|
|
3200
|
+
const badgeX = Math.max(0, dims.width / 2 - 14);
|
|
3201
|
+
const badgeY = -Math.max(0, dims.height / 2 - 14);
|
|
3202
|
+
marker.add(new import_Circle.Circle({ x: badgeX, y: badgeY, radius: 10, fill: this.effSelection, listening: false }));
|
|
3203
|
+
marker.add(new import_Line.Line({
|
|
3204
|
+
x: badgeX,
|
|
3205
|
+
y: badgeY,
|
|
3206
|
+
points: [-4.5, 0, -1, 3.5, 5.5, -4.5],
|
|
3207
|
+
stroke: isLightColor(this.effSelection) ? "#0b1220" : "#ffffff",
|
|
3208
|
+
strokeWidth: 2.4,
|
|
3209
|
+
lineCap: "round",
|
|
3210
|
+
lineJoin: "round",
|
|
3211
|
+
listening: false
|
|
3212
|
+
}));
|
|
3213
|
+
}
|
|
3214
|
+
} else {
|
|
3215
|
+
marker.add(new import_Circle.Circle({ ...common, radius: this.seatR + (candidate ? 4.5 : 2.5), strokeWidth: candidate ? 3.5 : 2.5 }));
|
|
3216
|
+
if (candidate) {
|
|
3217
|
+
marker.add(new import_Circle.Circle({
|
|
3218
|
+
...common,
|
|
3219
|
+
radius: this.seatR + 8,
|
|
3220
|
+
strokeWidth: 2,
|
|
3221
|
+
opacity: 0.55
|
|
3222
|
+
}));
|
|
3223
|
+
} else {
|
|
3224
|
+
marker.add(new import_Line.Line({
|
|
3225
|
+
points: [-this.seatR * 0.52, 0, -this.seatR * 0.12, this.seatR * 0.4, this.seatR * 0.6, -this.seatR * 0.46],
|
|
3226
|
+
stroke: "#ffffff",
|
|
3227
|
+
strokeWidth: Math.max(2.6, this.seatR * 0.34),
|
|
3228
|
+
lineCap: "round",
|
|
3229
|
+
lineJoin: "round",
|
|
3230
|
+
shadowColor: "#0b1220",
|
|
3231
|
+
shadowBlur: 1.5,
|
|
3232
|
+
shadowOpacity: 0.55,
|
|
3233
|
+
listening: false
|
|
3234
|
+
}));
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
this.selectionMarkers.set(id, marker);
|
|
3238
|
+
this.overlayLayer.add(marker);
|
|
3239
|
+
}
|
|
3076
3240
|
// ---- interaction ----------------------------------------------------------
|
|
3077
3241
|
/**
|
|
3078
3242
|
* The stage scale `focusRegion(id)` would settle at — i.e. the zoom that
|
|
@@ -3090,7 +3254,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3090
3254
|
const { min, max } = this.zoomBounds();
|
|
3091
3255
|
const margin = 1.12;
|
|
3092
3256
|
if (b.width <= 0 || b.height <= 0) return this.stage.scaleX();
|
|
3093
|
-
|
|
3257
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3258
|
+
return clamp(Math.max(frameScale, SEAT_FOCUS_SCALE), min, max);
|
|
3094
3259
|
}
|
|
3095
3260
|
/**
|
|
3096
3261
|
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
@@ -3148,7 +3313,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3148
3313
|
}
|
|
3149
3314
|
wireInteraction() {
|
|
3150
3315
|
this.seatLayer.on("click tap", (e) => {
|
|
3151
|
-
if (this.moved >
|
|
3316
|
+
if (this.moved > PAN_START_SLOP_PX) return;
|
|
3152
3317
|
const id = seatIdOf(e.target);
|
|
3153
3318
|
if (!id) return;
|
|
3154
3319
|
this.handleSeatTap(id);
|
|
@@ -3178,7 +3343,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3178
3343
|
this.zoomAbout(this.stage.scaleX() * clamp(factor, 0.5, 2), pointer);
|
|
3179
3344
|
});
|
|
3180
3345
|
this.stage.on("click tap", (e) => {
|
|
3181
|
-
if (this.moved >
|
|
3346
|
+
if (this.moved > PAN_START_SLOP_PX) return;
|
|
3182
3347
|
const pointer = this.stage.getPointerPosition();
|
|
3183
3348
|
if (!pointer) return;
|
|
3184
3349
|
if (!this.cached) {
|
|
@@ -3262,12 +3427,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3262
3427
|
this.stage.batchDraw();
|
|
3263
3428
|
}
|
|
3264
3429
|
/** Zoom + pan so world-rect `b` fills the viewport (with a small margin). */
|
|
3265
|
-
zoomToBounds(b) {
|
|
3430
|
+
zoomToBounds(b, minScale) {
|
|
3266
3431
|
const w = this.stage.width();
|
|
3267
3432
|
const h = this.stage.height();
|
|
3268
3433
|
const { min, max } = this.zoomBounds();
|
|
3269
3434
|
const margin = 1.12;
|
|
3270
|
-
const
|
|
3435
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3436
|
+
const scale = clamp(Math.max(frameScale, minScale ?? min), min, max);
|
|
3271
3437
|
this.stage.scale({ x: scale, y: scale });
|
|
3272
3438
|
this.stage.position({
|
|
3273
3439
|
x: w / 2 - (b.x + b.width / 2) * scale,
|
|
@@ -3291,14 +3457,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3291
3457
|
if (!b) return;
|
|
3292
3458
|
this.cancelGlide();
|
|
3293
3459
|
if (opts?.animate === false || this.reducedMotion) {
|
|
3294
|
-
this.zoomToBounds(b);
|
|
3460
|
+
this.zoomToBounds(b, opts?.minScale);
|
|
3295
3461
|
return;
|
|
3296
3462
|
}
|
|
3297
3463
|
const w = this.stage.width();
|
|
3298
3464
|
const h = this.stage.height();
|
|
3299
3465
|
const { min, max } = this.zoomBounds();
|
|
3300
3466
|
const margin = 1.12;
|
|
3301
|
-
const
|
|
3467
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3468
|
+
const toScale = clamp(Math.max(frameScale, opts?.minScale ?? min), min, max);
|
|
3302
3469
|
const toX = w / 2 - (b.x + b.width / 2) * toScale;
|
|
3303
3470
|
const toY = h / 2 - (b.y + b.height / 2) * toScale;
|
|
3304
3471
|
const fromScale = this.stage.scaleX();
|
|
@@ -3353,7 +3520,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3353
3520
|
this.zoomToFit();
|
|
3354
3521
|
return;
|
|
3355
3522
|
}
|
|
3356
|
-
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(
|
|
3523
|
+
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(SEAT_FOCUS_SCALE, CACHE_THRESHOLD * 1.3);
|
|
3357
3524
|
const w = this.stage.width();
|
|
3358
3525
|
const h = this.stage.height();
|
|
3359
3526
|
const cx = this.bounds.x + this.bounds.width / 2;
|
|
@@ -3439,19 +3606,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3439
3606
|
for (const seat of this.seats) {
|
|
3440
3607
|
if (seat.kind === "booth") continue;
|
|
3441
3608
|
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
3609
|
+
const status = this.statusById.get(seat.id) ?? "free";
|
|
3610
|
+
const statusCue = status === "booked" ? "\xD7" : status === "held" && !this.ownedHold.has(seat.id) ? "H" : null;
|
|
3442
3611
|
const t2 = new import_Text.Text({
|
|
3443
3612
|
x: seat.x,
|
|
3444
3613
|
y: seat.y,
|
|
3445
|
-
text: seat.label,
|
|
3446
|
-
fontSize: 7,
|
|
3447
|
-
fontStyle: "600",
|
|
3614
|
+
text: statusCue ?? seat.label,
|
|
3615
|
+
fontSize: statusCue ? status === "booked" ? 13 : 8 : 7,
|
|
3616
|
+
fontStyle: statusCue ? "800" : "600",
|
|
3448
3617
|
fontFamily: this.labelFont(),
|
|
3449
|
-
fill: this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
3618
|
+
fill: statusCue ? "#ffffff" : this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
3450
3619
|
listening: false,
|
|
3451
3620
|
perfectDrawEnabled: false
|
|
3452
3621
|
});
|
|
3453
3622
|
const maxW = this.seatR * 2 - 3;
|
|
3454
|
-
if (t2.width() > maxW) t2.fontSize(Math.max(4,
|
|
3623
|
+
if (t2.width() > maxW) t2.fontSize(Math.max(4, t2.fontSize() * maxW / t2.width()));
|
|
3455
3624
|
if (t2.fontSize() < 4.2) {
|
|
3456
3625
|
t2.destroy();
|
|
3457
3626
|
continue;
|
|
@@ -3553,6 +3722,8 @@ var PickerController = class {
|
|
|
3553
3722
|
this.seatTiers = /* @__PURE__ */ new Map();
|
|
3554
3723
|
/** id → seat, for the section-summary breakdown (renderer members are ids). */
|
|
3555
3724
|
this.seatById = /* @__PURE__ */ new Map();
|
|
3725
|
+
/** id → buyer-facing spatial metadata used by every tooltip/confirm surface. */
|
|
3726
|
+
this.seatContext = /* @__PURE__ */ new Map();
|
|
3556
3727
|
this.allIds = [];
|
|
3557
3728
|
// realtime socket
|
|
3558
3729
|
this.ws = null;
|
|
@@ -3631,12 +3802,28 @@ var PickerController = class {
|
|
|
3631
3802
|
this.labelToId = /* @__PURE__ */ new Map();
|
|
3632
3803
|
this.labelToSeat = /* @__PURE__ */ new Map();
|
|
3633
3804
|
this.seatById = /* @__PURE__ */ new Map();
|
|
3805
|
+
this.seatContext = /* @__PURE__ */ new Map();
|
|
3634
3806
|
this.allIds = [];
|
|
3807
|
+
const chartObjects = new Map(allObjects(res.doc).map((object) => [object.id, object]));
|
|
3808
|
+
const membership = computeSections(res.doc);
|
|
3809
|
+
const sectionLabels = new Map(
|
|
3810
|
+
[...membership.sections, ...membership.ungrouped ? [membership.ungrouped] : []].map((section) => [section.id, section.label])
|
|
3811
|
+
);
|
|
3635
3812
|
for (const s of expandChart(res.doc)) {
|
|
3636
3813
|
this.labelToId.set(s.label, s.id);
|
|
3637
3814
|
this.labelToSeat.set(s.label, s);
|
|
3638
3815
|
this.seatById.set(s.id, s);
|
|
3639
3816
|
this.allIds.push(s.id);
|
|
3817
|
+
const source = chartObjects.get(s.rowId);
|
|
3818
|
+
const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
|
|
3819
|
+
const rowLabel = s.kind === "booth" ? void 0 : sourceLabel;
|
|
3820
|
+
const labelParts = s.label.split("-");
|
|
3821
|
+
const seatNumber = rowLabel && s.label.startsWith(`${rowLabel}-`) ? s.label.slice(rowLabel.length + 1) : s.kind === "booth" ? s.label : labelParts[labelParts.length - 1] ?? s.label;
|
|
3822
|
+
this.seatContext.set(s.id, {
|
|
3823
|
+
sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
|
|
3824
|
+
rowLabel,
|
|
3825
|
+
seatNumber
|
|
3826
|
+
});
|
|
3640
3827
|
}
|
|
3641
3828
|
const currency = res.event.currency ?? this.opts.currency;
|
|
3642
3829
|
this.currency = currency ?? "USD";
|
|
@@ -3706,6 +3893,11 @@ var PickerController = class {
|
|
|
3706
3893
|
if (!this.renderer) return [];
|
|
3707
3894
|
return this.renderer.getSelection().map((s) => this.toSeat(s));
|
|
3708
3895
|
}
|
|
3896
|
+
/** Enriched metadata for a seat confirmation card or tooltip. */
|
|
3897
|
+
seatDetails(seatId) {
|
|
3898
|
+
const seat = this.seatById.get(seatId);
|
|
3899
|
+
return seat ? this.describeSeat(seat) : null;
|
|
3900
|
+
}
|
|
3709
3901
|
clearSelection() {
|
|
3710
3902
|
this.renderer?.clearSelection();
|
|
3711
3903
|
this.emitSelectionChange();
|
|
@@ -3745,6 +3937,19 @@ var PickerController = class {
|
|
|
3745
3937
|
throw err;
|
|
3746
3938
|
}
|
|
3747
3939
|
}
|
|
3940
|
+
/** Restore an active server hold without creating or extending inventory. */
|
|
3941
|
+
async resumeHold(holdId) {
|
|
3942
|
+
if (this.closed || !holdId || !this.api.resume) return null;
|
|
3943
|
+
const result = await this.api.resume(this.key, holdId);
|
|
3944
|
+
if (this.closed) return null;
|
|
3945
|
+
const labels = [...new Set(result.items.map((item) => item.label))];
|
|
3946
|
+
if (!labels.length) return null;
|
|
3947
|
+
this.setHold(
|
|
3948
|
+
{ holdId: result.holdId, labels, expiresAt: result.expiresAt, items: result.items },
|
|
3949
|
+
"restored"
|
|
3950
|
+
);
|
|
3951
|
+
return this.hold_;
|
|
3952
|
+
}
|
|
3748
3953
|
/**
|
|
3749
3954
|
* P4 "need more time?": extend the OPEN hold's server-side expiry and re-arm
|
|
3750
3955
|
* the client expiry timer to match (via setHold), so the controller doesn't
|
|
@@ -3834,7 +4039,7 @@ var PickerController = class {
|
|
|
3834
4039
|
async bestAvailable(qty, categoryKey) {
|
|
3835
4040
|
const r = this.renderer;
|
|
3836
4041
|
if (!r) return null;
|
|
3837
|
-
if (this.hold_
|
|
4042
|
+
if (this.hold_ && !await this.release()) return null;
|
|
3838
4043
|
try {
|
|
3839
4044
|
const result = await this.api.bestAvailable(this.key, qty, categoryKey);
|
|
3840
4045
|
r.clearSelection();
|
|
@@ -3919,15 +4124,25 @@ var PickerController = class {
|
|
|
3919
4124
|
/** Release the whole open hold (if any), repaint those seats free. */
|
|
3920
4125
|
async release() {
|
|
3921
4126
|
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");
|
|
4127
|
+
if (!hold) return true;
|
|
3926
4128
|
try {
|
|
3927
|
-
await this.api.release(this.key, hold.labels, hold.holdId);
|
|
4129
|
+
const result = await this.api.release(this.key, hold.labels, hold.holdId);
|
|
4130
|
+
if (!this.releaseConfirmed(result, hold.labels)) {
|
|
4131
|
+
await this.resnapshot();
|
|
4132
|
+
return false;
|
|
4133
|
+
}
|
|
3928
4134
|
} catch (err) {
|
|
3929
4135
|
this.emitError(err);
|
|
4136
|
+
return false;
|
|
4137
|
+
}
|
|
4138
|
+
if (this.hold_?.holdId !== hold.holdId) return true;
|
|
4139
|
+
this.clearHold();
|
|
4140
|
+
const ids = hold.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
4141
|
+
if (ids.length) {
|
|
4142
|
+
this.renderer?.deselect(ids);
|
|
4143
|
+
this.renderer?.setStatus(ids, "free");
|
|
3930
4144
|
}
|
|
4145
|
+
return true;
|
|
3931
4146
|
}
|
|
3932
4147
|
/**
|
|
3933
4148
|
* Release just some labels from the open hold, keeping the rest held (used when
|
|
@@ -3935,19 +4150,35 @@ var PickerController = class {
|
|
|
3935
4150
|
*/
|
|
3936
4151
|
async releaseLabels(labels) {
|
|
3937
4152
|
const hold = this.hold_;
|
|
3938
|
-
if (!hold) return;
|
|
4153
|
+
if (!hold) return true;
|
|
3939
4154
|
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");
|
|
4155
|
+
if (!drop.length) return true;
|
|
3946
4156
|
try {
|
|
3947
|
-
await this.api.release(this.key, drop, hold.holdId);
|
|
4157
|
+
const result = await this.api.release(this.key, drop, hold.holdId);
|
|
4158
|
+
if (!this.releaseConfirmed(result, drop)) {
|
|
4159
|
+
await this.resnapshot();
|
|
4160
|
+
return false;
|
|
4161
|
+
}
|
|
3948
4162
|
} catch (err) {
|
|
3949
4163
|
this.emitError(err);
|
|
4164
|
+
return false;
|
|
3950
4165
|
}
|
|
4166
|
+
if (this.hold_?.holdId !== hold.holdId) return true;
|
|
4167
|
+
const remaining = hold.labels.filter((l) => !drop.includes(l));
|
|
4168
|
+
const remainingItems = hold.items?.filter((item) => !drop.includes(item.label));
|
|
4169
|
+
if (remaining.length) this.setHold({ ...hold, labels: remaining, items: remainingItems });
|
|
4170
|
+
else this.clearHold();
|
|
4171
|
+
const ids = drop.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
4172
|
+
if (ids.length) {
|
|
4173
|
+
this.renderer?.deselect(ids);
|
|
4174
|
+
this.renderer?.setStatus(ids, "free");
|
|
4175
|
+
}
|
|
4176
|
+
return true;
|
|
4177
|
+
}
|
|
4178
|
+
/** New transports return the exact labels released; tolerate older adapters. */
|
|
4179
|
+
releaseConfirmed(result, requested) {
|
|
4180
|
+
const released = result?.released;
|
|
4181
|
+
return !Array.isArray(released) || requested.every((label) => released.includes(label));
|
|
3951
4182
|
}
|
|
3952
4183
|
// ---- renderer proxies (so consumers don't reach through) ------------------
|
|
3953
4184
|
setStatus(ids, status) {
|
|
@@ -3971,6 +4202,9 @@ var PickerController = class {
|
|
|
3971
4202
|
worldToScreen(p) {
|
|
3972
4203
|
return this.renderer?.worldToScreen(p) ?? { x: 0, y: 0 };
|
|
3973
4204
|
}
|
|
4205
|
+
setSelectionFocus(seatId) {
|
|
4206
|
+
this.renderer?.setSelectionFocus?.(seatId);
|
|
4207
|
+
}
|
|
3974
4208
|
setAccessibilityFilter(types) {
|
|
3975
4209
|
this.renderer?.setAccessibilityFilter?.(types);
|
|
3976
4210
|
}
|
|
@@ -4158,7 +4392,8 @@ var PickerController = class {
|
|
|
4158
4392
|
categoryLabel: cat?.label ?? s.categoryKey,
|
|
4159
4393
|
categoryColor: cat?.color ?? "#6e7bff",
|
|
4160
4394
|
status: this.renderer?.getStatus(s.id) ?? "free",
|
|
4161
|
-
currency: this.currency
|
|
4395
|
+
currency: this.currency,
|
|
4396
|
+
...this.seatContext.get(s.id)
|
|
4162
4397
|
};
|
|
4163
4398
|
}
|
|
4164
4399
|
priceFor(categoryKey) {
|
|
@@ -4249,16 +4484,21 @@ var PickerController = class {
|
|
|
4249
4484
|
this.emitSelectionChange();
|
|
4250
4485
|
}
|
|
4251
4486
|
/** Set the open hold + (re)arm the server-authoritative expiry timer. */
|
|
4252
|
-
setHold(hold) {
|
|
4487
|
+
setHold(hold, source = "created") {
|
|
4253
4488
|
const full = { ...hold, seats: this.seatsForLabels(hold.labels) };
|
|
4254
4489
|
this.hold_ = full;
|
|
4490
|
+
this.renderer?.setOwnedHold?.(
|
|
4491
|
+
full.labels.map((label) => this.labelToId.get(label)).filter((id) => !!id)
|
|
4492
|
+
);
|
|
4255
4493
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
4256
4494
|
const ms = Math.max(0, full.expiresAt - Date.now());
|
|
4257
4495
|
this.expiryTimer = setTimeout(() => void this.expireActiveHold(), ms);
|
|
4258
|
-
this.opts.
|
|
4496
|
+
if (source === "restored") this.opts.onHoldRestored?.(full);
|
|
4497
|
+
else this.opts.onHold?.(full);
|
|
4259
4498
|
}
|
|
4260
4499
|
clearHold() {
|
|
4261
4500
|
this.hold_ = null;
|
|
4501
|
+
this.renderer?.setOwnedHold?.(null);
|
|
4262
4502
|
if (this.expiryTimer) {
|
|
4263
4503
|
clearTimeout(this.expiryTimer);
|
|
4264
4504
|
this.expiryTimer = null;
|
|
@@ -4290,6 +4530,9 @@ var PickerController = class {
|
|
|
4290
4530
|
if (!r) return;
|
|
4291
4531
|
this.liveStatuses = new Map(Object.entries(seats));
|
|
4292
4532
|
if (this.allIds.length) r.setStatus(this.allIds, "free");
|
|
4533
|
+
r.setOwnedHold?.(
|
|
4534
|
+
(this.hold_?.labels ?? []).map((label) => this.labelToId.get(label)).filter((id) => !!id)
|
|
4535
|
+
);
|
|
4293
4536
|
const byStatus = { free: [], held: [], booked: [], not_for_sale: [] };
|
|
4294
4537
|
for (const [label, st] of Object.entries(seats)) {
|
|
4295
4538
|
const id = this.labelToId.get(label);
|