@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.js
CHANGED
|
@@ -635,6 +635,8 @@ var LABEL_SCALE = 1;
|
|
|
635
635
|
var SEAT_TAP_SLOP_PX = 14;
|
|
636
636
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
637
637
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
638
|
+
var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
639
|
+
var PAN_START_SLOP_PX = 8;
|
|
638
640
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
639
641
|
var MAX_LABELS = 700;
|
|
640
642
|
var MARQUEE_RING_CAP = 2500;
|
|
@@ -785,6 +787,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
785
787
|
this.circleById = /* @__PURE__ */ new Map();
|
|
786
788
|
/** Booth block geometry, keyed by booth id (= the unit's rowId). */
|
|
787
789
|
this.boothDims = /* @__PURE__ */ new Map();
|
|
790
|
+
/** Booth label node so status changes can say HELD/SOLD on the full block. */
|
|
791
|
+
this.boothLabelById = /* @__PURE__ */ new Map();
|
|
788
792
|
this.statusById = /* @__PURE__ */ new Map();
|
|
789
793
|
this.catColor = /* @__PURE__ */ new Map();
|
|
790
794
|
this.theme = {};
|
|
@@ -795,7 +799,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
795
799
|
/** Category order from the doc — the stable index into the CB palette. */
|
|
796
800
|
this.catOrder = [];
|
|
797
801
|
this.selection = /* @__PURE__ */ new Set();
|
|
798
|
-
|
|
802
|
+
/** Whole-seat/block selection markers: outline + non-colour check cue. */
|
|
803
|
+
this.selectionMarkers = /* @__PURE__ */ new Map();
|
|
804
|
+
/** Held by this picker instance, not by another buyer. */
|
|
805
|
+
this.ownedHold = /* @__PURE__ */ new Set();
|
|
806
|
+
/** One selected seat being inspected before it is committed to the cart. */
|
|
807
|
+
this.selectionFocusId = null;
|
|
799
808
|
this.focusedId = null;
|
|
800
809
|
/**
|
|
801
810
|
* Accessibility filter: `null` = off; `[]` = dim all non-accessible free seats;
|
|
@@ -864,7 +873,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
864
873
|
this.pointers = /* @__PURE__ */ new Map();
|
|
865
874
|
this.pinch = null;
|
|
866
875
|
this.panLast = null;
|
|
867
|
-
|
|
876
|
+
this.panStart = null;
|
|
877
|
+
this.panStarted = false;
|
|
878
|
+
/** Maximum displacement from gesture start — suppress taps only after a real pan/pinch. */
|
|
868
879
|
this.moved = 0;
|
|
869
880
|
/**
|
|
870
881
|
* Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
|
|
@@ -907,15 +918,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
907
918
|
// pipeline. We rely on bubbling instead.
|
|
908
919
|
this.onPointerDown = (e) => {
|
|
909
920
|
this.cancelGlide();
|
|
910
|
-
|
|
921
|
+
const local = this.toLocal(e);
|
|
922
|
+
this.pointers.set(e.pointerId, local);
|
|
911
923
|
if (this.pointers.size === 1) {
|
|
912
924
|
this.moved = 0;
|
|
913
925
|
this.pinch = null;
|
|
926
|
+
this.panStart = local;
|
|
927
|
+
this.panStarted = false;
|
|
914
928
|
if (this.opts.manageMode && this.opts.marqueeSelect && e.pointerType !== "touch" && e.button === 0 && this.getRung() === "seats") {
|
|
915
|
-
this.beginMarquee(
|
|
929
|
+
this.beginMarquee(local);
|
|
916
930
|
this.panLast = null;
|
|
917
931
|
} else {
|
|
918
|
-
this.panLast =
|
|
932
|
+
this.panLast = local;
|
|
919
933
|
}
|
|
920
934
|
} else if (this.pointers.size === 2) {
|
|
921
935
|
if (this.marquee) this.cancelMarquee();
|
|
@@ -928,15 +942,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
928
942
|
worldMid: { x: (mid.x - this.stage.x()) / s, y: (mid.y - this.stage.y()) / s }
|
|
929
943
|
};
|
|
930
944
|
this.panLast = null;
|
|
945
|
+
this.panStart = null;
|
|
946
|
+
this.panStarted = true;
|
|
947
|
+
this.moved = PAN_START_SLOP_PX + 1;
|
|
931
948
|
}
|
|
932
949
|
};
|
|
933
950
|
this.onPointerMove = (e) => {
|
|
934
951
|
if (!this.pointers.has(e.pointerId)) return;
|
|
935
952
|
e.preventDefault();
|
|
936
953
|
const p = this.toLocal(e);
|
|
937
|
-
const prev = this.pointers.get(e.pointerId);
|
|
938
|
-
this.moved += Math.hypot(p.x - prev.x, p.y - prev.y);
|
|
939
954
|
this.pointers.set(e.pointerId, p);
|
|
955
|
+
if (this.pointers.size === 1 && this.panStart) {
|
|
956
|
+
this.moved = Math.max(this.moved, Math.hypot(p.x - this.panStart.x, p.y - this.panStart.y));
|
|
957
|
+
} else if (this.pointers.size >= 2) {
|
|
958
|
+
this.moved = PAN_START_SLOP_PX + 1;
|
|
959
|
+
}
|
|
940
960
|
if (this.marquee) {
|
|
941
961
|
this.updateMarquee(p);
|
|
942
962
|
return;
|
|
@@ -956,6 +976,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
956
976
|
this.stage.batchDraw();
|
|
957
977
|
this.scheduleViewChange();
|
|
958
978
|
} else if (this.panLast && this.pointers.size === 1) {
|
|
979
|
+
if (!this.panStarted) {
|
|
980
|
+
if (this.moved <= PAN_START_SLOP_PX) return;
|
|
981
|
+
this.panStarted = true;
|
|
982
|
+
}
|
|
959
983
|
this.stage.position({
|
|
960
984
|
x: this.stage.x() + (p.x - this.panLast.x),
|
|
961
985
|
y: this.stage.y() + (p.y - this.panLast.y)
|
|
@@ -976,9 +1000,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
976
1000
|
if (this.pointers.size < 2) this.pinch = null;
|
|
977
1001
|
if (this.pointers.size === 1) {
|
|
978
1002
|
this.panLast = [...this.pointers.values()][0];
|
|
1003
|
+
this.panStart = this.panLast;
|
|
1004
|
+
this.panStarted = true;
|
|
979
1005
|
}
|
|
980
1006
|
if (this.pointers.size === 0) {
|
|
981
1007
|
this.panLast = null;
|
|
1008
|
+
this.panStart = null;
|
|
1009
|
+
this.panStarted = false;
|
|
982
1010
|
this.afterViewChange();
|
|
983
1011
|
}
|
|
984
1012
|
};
|
|
@@ -1060,7 +1088,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1060
1088
|
this.labelGroup.destroyChildren();
|
|
1061
1089
|
this.circleById.clear();
|
|
1062
1090
|
this.boothDims.clear();
|
|
1063
|
-
this.
|
|
1091
|
+
this.boothLabelById.clear();
|
|
1092
|
+
this.selectionMarkers.clear();
|
|
1093
|
+
this.ownedHold.clear();
|
|
1094
|
+
this.selectionFocusId = null;
|
|
1064
1095
|
this.statusById.clear();
|
|
1065
1096
|
this.selection.clear();
|
|
1066
1097
|
this.seatById.clear();
|
|
@@ -1180,6 +1211,48 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1180
1211
|
} else {
|
|
1181
1212
|
this.seatLayer.batchDraw();
|
|
1182
1213
|
}
|
|
1214
|
+
if (this.effScale() > LABEL_SCALE) this.updateLabels();
|
|
1215
|
+
}
|
|
1216
|
+
setOwnedHold(seatIds) {
|
|
1217
|
+
const next = new Set((seatIds ?? []).filter((id) => this.statusById.has(id)));
|
|
1218
|
+
const touched = /* @__PURE__ */ new Set([...this.ownedHold, ...next]);
|
|
1219
|
+
this.ownedHold = next;
|
|
1220
|
+
for (const id of touched) {
|
|
1221
|
+
const shape = this.circleById.get(id);
|
|
1222
|
+
if (shape) this.paintSeat(shape, id);
|
|
1223
|
+
this.syncSelectionMarker(id);
|
|
1224
|
+
}
|
|
1225
|
+
if (!touched.size) return;
|
|
1226
|
+
if (this.cached) {
|
|
1227
|
+
if (this.recacheTimer) clearTimeout(this.recacheTimer);
|
|
1228
|
+
this.recacheTimer = setTimeout(() => this.cacheSeatLayer(), 150);
|
|
1229
|
+
} else {
|
|
1230
|
+
this.seatLayer.batchDraw();
|
|
1231
|
+
}
|
|
1232
|
+
if (this.effScale() > LABEL_SCALE) this.updateLabels();
|
|
1233
|
+
this.overlayLayer.batchDraw();
|
|
1234
|
+
}
|
|
1235
|
+
setSelectionFocus(seatId) {
|
|
1236
|
+
const next = seatId && this.selection.has(seatId) ? seatId : null;
|
|
1237
|
+
if (next === this.selectionFocusId) return;
|
|
1238
|
+
const previous = this.selectionFocusId;
|
|
1239
|
+
this.selectionFocusId = next;
|
|
1240
|
+
for (const seat of this.seats) {
|
|
1241
|
+
const shape = this.circleById.get(seat.id);
|
|
1242
|
+
if (shape) this.paintSeat(shape, seat.id);
|
|
1243
|
+
}
|
|
1244
|
+
if (previous) this.syncSelectionMarker(previous);
|
|
1245
|
+
if (next) this.syncSelectionMarker(next);
|
|
1246
|
+
for (const [id, marker] of this.selectionMarkers) {
|
|
1247
|
+
marker.opacity(!next || id === next ? 1 : 0.2);
|
|
1248
|
+
}
|
|
1249
|
+
if (this.cached) {
|
|
1250
|
+
this.seatLayer.clearCache();
|
|
1251
|
+
this.cacheSeatLayer();
|
|
1252
|
+
} else {
|
|
1253
|
+
this.seatLayer.batchDraw();
|
|
1254
|
+
}
|
|
1255
|
+
this.overlayLayer.batchDraw();
|
|
1183
1256
|
}
|
|
1184
1257
|
getStatus(seatId) {
|
|
1185
1258
|
return this.statusById.get(seatId) ?? "free";
|
|
@@ -1202,6 +1275,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1202
1275
|
setManageInteraction(options) {
|
|
1203
1276
|
if (this.marquee) this.cancelMarquee();
|
|
1204
1277
|
this.panLast = null;
|
|
1278
|
+
this.panStart = null;
|
|
1279
|
+
this.panStarted = false;
|
|
1205
1280
|
this.opts.manageMode = options.manageMode;
|
|
1206
1281
|
this.opts.marqueeSelect = options.marqueeSelect;
|
|
1207
1282
|
this.opts.selectableStatuses = [...options.selectableStatuses];
|
|
@@ -1798,6 +1873,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1798
1873
|
t2.offsetX(t2.width() / 2);
|
|
1799
1874
|
t2.offsetY(t2.height() / 2);
|
|
1800
1875
|
this.hasBoothText = true;
|
|
1876
|
+
this.boothLabelById.set(seat.id, t2);
|
|
1801
1877
|
target.add(t2);
|
|
1802
1878
|
}
|
|
1803
1879
|
/** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
|
|
@@ -1812,6 +1888,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1812
1888
|
const status = this.statusById.get(id) ?? "free";
|
|
1813
1889
|
const selected = this.selection.has(id);
|
|
1814
1890
|
const base = this.seatBaseColor(seat.categoryKey);
|
|
1891
|
+
const boothLabel = this.boothLabelById.get(id);
|
|
1815
1892
|
c.dash([]);
|
|
1816
1893
|
c.strokeWidth(0);
|
|
1817
1894
|
c.stroke("");
|
|
@@ -1823,7 +1900,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1823
1900
|
);
|
|
1824
1901
|
break;
|
|
1825
1902
|
case "held":
|
|
1826
|
-
|
|
1903
|
+
if (this.ownedHold.has(id)) {
|
|
1904
|
+
c.fill(lighten(base, this.effSelection === DEF_SELECTION ? 0.24 : 0.1));
|
|
1905
|
+
c.stroke(this.effSelection);
|
|
1906
|
+
c.strokeWidth(3);
|
|
1907
|
+
} else {
|
|
1908
|
+
c.fill(HELD_FILL);
|
|
1909
|
+
}
|
|
1827
1910
|
break;
|
|
1828
1911
|
case "booked":
|
|
1829
1912
|
if (this.colorblind) {
|
|
@@ -1843,6 +1926,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1843
1926
|
c.dash([2, 2]);
|
|
1844
1927
|
break;
|
|
1845
1928
|
}
|
|
1929
|
+
if (boothLabel) {
|
|
1930
|
+
boothLabel.text(status === "booked" ? "SOLD" : status === "held" ? "HELD" : seat.label);
|
|
1931
|
+
boothLabel.fontSize(status === "free" ? 10 : Math.min(10, Math.max(6, this.boothDims.get(seat.rowId)?.width ?? 40) / 6));
|
|
1932
|
+
boothLabel.fontStyle(status === "free" ? "600" : "800");
|
|
1933
|
+
boothLabel.fill(status === "free" ? this.theme.seatLabelColor ?? DEF_SEAT_LABEL : "#ffffff");
|
|
1934
|
+
boothLabel.offsetX(boothLabel.width() / 2);
|
|
1935
|
+
boothLabel.offsetY(boothLabel.height() / 2);
|
|
1936
|
+
}
|
|
1846
1937
|
if (this.accessFilter && status === "free" && !selected && !seatMatchesAccess(seat, this.accessFilter)) {
|
|
1847
1938
|
c.opacity(0.25);
|
|
1848
1939
|
}
|
|
@@ -1870,6 +1961,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1870
1961
|
const inFocus = !!sec && (sec.id === this.focusedSectionId || sec.zone === this.focusedSectionId);
|
|
1871
1962
|
if (!inFocus) c.opacity(FOCUS_DIM_OPACITY);
|
|
1872
1963
|
}
|
|
1964
|
+
if (this.selectionFocusId && id !== this.selectionFocusId) c.opacity(Math.min(c.opacity(), 0.16));
|
|
1873
1965
|
}
|
|
1874
1966
|
/** True when a seat sits in a section/zone currently marked `closed`. */
|
|
1875
1967
|
seatInClosedSection(id) {
|
|
@@ -1937,7 +2029,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1937
2029
|
this.drawFocusBackdrop(id);
|
|
1938
2030
|
this.repaintSectionsAndSeats();
|
|
1939
2031
|
this.updateLOD();
|
|
1940
|
-
this.focusRegion(id);
|
|
2032
|
+
this.focusRegion(id, { minScale: SEAT_FOCUS_SCALE });
|
|
1941
2033
|
}
|
|
1942
2034
|
/** Clear an AXS section focus — restore full-bowl brightness + drop the backdrop. */
|
|
1943
2035
|
clearSectionFocus() {
|
|
@@ -2730,30 +2822,101 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2730
2822
|
const c = this.circleById.get(id);
|
|
2731
2823
|
if (on) {
|
|
2732
2824
|
this.selection.add(id);
|
|
2733
|
-
const seat = this.seatById.get(id);
|
|
2734
|
-
const ring = new Circle({
|
|
2735
|
-
x: seat.x,
|
|
2736
|
-
y: seat.y,
|
|
2737
|
-
radius: this.seatR,
|
|
2738
|
-
stroke: this.effSelection,
|
|
2739
|
-
strokeWidth: 3,
|
|
2740
|
-
listening: false,
|
|
2741
|
-
perfectDrawEnabled: false,
|
|
2742
|
-
shadowForStrokeEnabled: false
|
|
2743
|
-
});
|
|
2744
|
-
this.selectionRings.set(id, ring);
|
|
2745
|
-
this.overlayLayer.add(ring);
|
|
2746
2825
|
} else {
|
|
2826
|
+
if (this.selectionFocusId === id) this.setSelectionFocus(null);
|
|
2747
2827
|
this.selection.delete(id);
|
|
2748
|
-
const ring = this.selectionRings.get(id);
|
|
2749
|
-
ring?.destroy();
|
|
2750
|
-
this.selectionRings.delete(id);
|
|
2751
2828
|
}
|
|
2829
|
+
this.syncSelectionMarker(id);
|
|
2752
2830
|
if (c) {
|
|
2753
2831
|
this.paintSeat(c, id);
|
|
2754
2832
|
if (!silent && !this.cached) this.seatLayer.batchDraw();
|
|
2755
2833
|
}
|
|
2756
2834
|
}
|
|
2835
|
+
/** Rebuild one marker after selected/held/candidate state changes. */
|
|
2836
|
+
syncSelectionMarker(id) {
|
|
2837
|
+
this.selectionMarkers.get(id)?.destroy();
|
|
2838
|
+
this.selectionMarkers.delete(id);
|
|
2839
|
+
if (!this.selection.has(id) && !this.ownedHold.has(id)) return;
|
|
2840
|
+
const seat = this.seatById.get(id);
|
|
2841
|
+
if (!seat) return;
|
|
2842
|
+
const candidate = this.selectionFocusId === id;
|
|
2843
|
+
const dims = this.boothDims.get(seat.rowId);
|
|
2844
|
+
const marker = new Group({
|
|
2845
|
+
x: seat.x,
|
|
2846
|
+
y: seat.y,
|
|
2847
|
+
rotation: dims?.rotation ?? 0,
|
|
2848
|
+
listening: false,
|
|
2849
|
+
perfectDrawEnabled: false,
|
|
2850
|
+
opacity: this.selectionFocusId && !candidate ? 0.2 : 1
|
|
2851
|
+
});
|
|
2852
|
+
const common = {
|
|
2853
|
+
stroke: this.effSelection,
|
|
2854
|
+
listening: false,
|
|
2855
|
+
perfectDrawEnabled: false,
|
|
2856
|
+
shadowForStrokeEnabled: false
|
|
2857
|
+
};
|
|
2858
|
+
if (dims) {
|
|
2859
|
+
marker.add(new Rect({
|
|
2860
|
+
...common,
|
|
2861
|
+
width: dims.width,
|
|
2862
|
+
height: dims.height,
|
|
2863
|
+
offsetX: dims.width / 2,
|
|
2864
|
+
offsetY: dims.height / 2,
|
|
2865
|
+
cornerRadius: 4,
|
|
2866
|
+
strokeWidth: candidate ? 4 : 3
|
|
2867
|
+
}));
|
|
2868
|
+
if (candidate) {
|
|
2869
|
+
marker.add(new Rect({
|
|
2870
|
+
...common,
|
|
2871
|
+
width: dims.width + 10,
|
|
2872
|
+
height: dims.height + 10,
|
|
2873
|
+
offsetX: (dims.width + 10) / 2,
|
|
2874
|
+
offsetY: (dims.height + 10) / 2,
|
|
2875
|
+
cornerRadius: 7,
|
|
2876
|
+
strokeWidth: 2,
|
|
2877
|
+
opacity: 0.55
|
|
2878
|
+
}));
|
|
2879
|
+
} else {
|
|
2880
|
+
const badgeX = Math.max(0, dims.width / 2 - 14);
|
|
2881
|
+
const badgeY = -Math.max(0, dims.height / 2 - 14);
|
|
2882
|
+
marker.add(new Circle({ x: badgeX, y: badgeY, radius: 10, fill: this.effSelection, listening: false }));
|
|
2883
|
+
marker.add(new Line({
|
|
2884
|
+
x: badgeX,
|
|
2885
|
+
y: badgeY,
|
|
2886
|
+
points: [-4.5, 0, -1, 3.5, 5.5, -4.5],
|
|
2887
|
+
stroke: isLightColor(this.effSelection) ? "#0b1220" : "#ffffff",
|
|
2888
|
+
strokeWidth: 2.4,
|
|
2889
|
+
lineCap: "round",
|
|
2890
|
+
lineJoin: "round",
|
|
2891
|
+
listening: false
|
|
2892
|
+
}));
|
|
2893
|
+
}
|
|
2894
|
+
} else {
|
|
2895
|
+
marker.add(new Circle({ ...common, radius: this.seatR + (candidate ? 4.5 : 2.5), strokeWidth: candidate ? 3.5 : 2.5 }));
|
|
2896
|
+
if (candidate) {
|
|
2897
|
+
marker.add(new Circle({
|
|
2898
|
+
...common,
|
|
2899
|
+
radius: this.seatR + 8,
|
|
2900
|
+
strokeWidth: 2,
|
|
2901
|
+
opacity: 0.55
|
|
2902
|
+
}));
|
|
2903
|
+
} else {
|
|
2904
|
+
marker.add(new Line({
|
|
2905
|
+
points: [-this.seatR * 0.52, 0, -this.seatR * 0.12, this.seatR * 0.4, this.seatR * 0.6, -this.seatR * 0.46],
|
|
2906
|
+
stroke: "#ffffff",
|
|
2907
|
+
strokeWidth: Math.max(2.6, this.seatR * 0.34),
|
|
2908
|
+
lineCap: "round",
|
|
2909
|
+
lineJoin: "round",
|
|
2910
|
+
shadowColor: "#0b1220",
|
|
2911
|
+
shadowBlur: 1.5,
|
|
2912
|
+
shadowOpacity: 0.55,
|
|
2913
|
+
listening: false
|
|
2914
|
+
}));
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
this.selectionMarkers.set(id, marker);
|
|
2918
|
+
this.overlayLayer.add(marker);
|
|
2919
|
+
}
|
|
2757
2920
|
// ---- interaction ----------------------------------------------------------
|
|
2758
2921
|
/**
|
|
2759
2922
|
* The stage scale `focusRegion(id)` would settle at — i.e. the zoom that
|
|
@@ -2771,7 +2934,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2771
2934
|
const { min, max } = this.zoomBounds();
|
|
2772
2935
|
const margin = 1.12;
|
|
2773
2936
|
if (b.width <= 0 || b.height <= 0) return this.stage.scaleX();
|
|
2774
|
-
|
|
2937
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
2938
|
+
return clamp(Math.max(frameScale, SEAT_FOCUS_SCALE), min, max);
|
|
2775
2939
|
}
|
|
2776
2940
|
/**
|
|
2777
2941
|
* Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
|
|
@@ -2829,7 +2993,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2829
2993
|
}
|
|
2830
2994
|
wireInteraction() {
|
|
2831
2995
|
this.seatLayer.on("click tap", (e) => {
|
|
2832
|
-
if (this.moved >
|
|
2996
|
+
if (this.moved > PAN_START_SLOP_PX) return;
|
|
2833
2997
|
const id = seatIdOf(e.target);
|
|
2834
2998
|
if (!id) return;
|
|
2835
2999
|
this.handleSeatTap(id);
|
|
@@ -2859,7 +3023,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2859
3023
|
this.zoomAbout(this.stage.scaleX() * clamp(factor, 0.5, 2), pointer);
|
|
2860
3024
|
});
|
|
2861
3025
|
this.stage.on("click tap", (e) => {
|
|
2862
|
-
if (this.moved >
|
|
3026
|
+
if (this.moved > PAN_START_SLOP_PX) return;
|
|
2863
3027
|
const pointer = this.stage.getPointerPosition();
|
|
2864
3028
|
if (!pointer) return;
|
|
2865
3029
|
if (!this.cached) {
|
|
@@ -2943,12 +3107,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2943
3107
|
this.stage.batchDraw();
|
|
2944
3108
|
}
|
|
2945
3109
|
/** Zoom + pan so world-rect `b` fills the viewport (with a small margin). */
|
|
2946
|
-
zoomToBounds(b) {
|
|
3110
|
+
zoomToBounds(b, minScale) {
|
|
2947
3111
|
const w = this.stage.width();
|
|
2948
3112
|
const h = this.stage.height();
|
|
2949
3113
|
const { min, max } = this.zoomBounds();
|
|
2950
3114
|
const margin = 1.12;
|
|
2951
|
-
const
|
|
3115
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3116
|
+
const scale = clamp(Math.max(frameScale, minScale ?? min), min, max);
|
|
2952
3117
|
this.stage.scale({ x: scale, y: scale });
|
|
2953
3118
|
this.stage.position({
|
|
2954
3119
|
x: w / 2 - (b.x + b.width / 2) * scale,
|
|
@@ -2972,14 +3137,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2972
3137
|
if (!b) return;
|
|
2973
3138
|
this.cancelGlide();
|
|
2974
3139
|
if (opts?.animate === false || this.reducedMotion) {
|
|
2975
|
-
this.zoomToBounds(b);
|
|
3140
|
+
this.zoomToBounds(b, opts?.minScale);
|
|
2976
3141
|
return;
|
|
2977
3142
|
}
|
|
2978
3143
|
const w = this.stage.width();
|
|
2979
3144
|
const h = this.stage.height();
|
|
2980
3145
|
const { min, max } = this.zoomBounds();
|
|
2981
3146
|
const margin = 1.12;
|
|
2982
|
-
const
|
|
3147
|
+
const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
|
|
3148
|
+
const toScale = clamp(Math.max(frameScale, opts?.minScale ?? min), min, max);
|
|
2983
3149
|
const toX = w / 2 - (b.x + b.width / 2) * toScale;
|
|
2984
3150
|
const toY = h / 2 - (b.y + b.height / 2) * toScale;
|
|
2985
3151
|
const fromScale = this.stage.scaleX();
|
|
@@ -3034,7 +3200,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3034
3200
|
this.zoomToFit();
|
|
3035
3201
|
return;
|
|
3036
3202
|
}
|
|
3037
|
-
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(
|
|
3203
|
+
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(SEAT_FOCUS_SCALE, CACHE_THRESHOLD * 1.3);
|
|
3038
3204
|
const w = this.stage.width();
|
|
3039
3205
|
const h = this.stage.height();
|
|
3040
3206
|
const cx = this.bounds.x + this.bounds.width / 2;
|
|
@@ -3120,19 +3286,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3120
3286
|
for (const seat of this.seats) {
|
|
3121
3287
|
if (seat.kind === "booth") continue;
|
|
3122
3288
|
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
3289
|
+
const status = this.statusById.get(seat.id) ?? "free";
|
|
3290
|
+
const statusCue = status === "booked" ? "\xD7" : status === "held" && !this.ownedHold.has(seat.id) ? "H" : null;
|
|
3123
3291
|
const t2 = new Text({
|
|
3124
3292
|
x: seat.x,
|
|
3125
3293
|
y: seat.y,
|
|
3126
|
-
text: seat.label,
|
|
3127
|
-
fontSize: 7,
|
|
3128
|
-
fontStyle: "600",
|
|
3294
|
+
text: statusCue ?? seat.label,
|
|
3295
|
+
fontSize: statusCue ? status === "booked" ? 13 : 8 : 7,
|
|
3296
|
+
fontStyle: statusCue ? "800" : "600",
|
|
3129
3297
|
fontFamily: this.labelFont(),
|
|
3130
|
-
fill: this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
3298
|
+
fill: statusCue ? "#ffffff" : this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
3131
3299
|
listening: false,
|
|
3132
3300
|
perfectDrawEnabled: false
|
|
3133
3301
|
});
|
|
3134
3302
|
const maxW = this.seatR * 2 - 3;
|
|
3135
|
-
if (t2.width() > maxW) t2.fontSize(Math.max(4,
|
|
3303
|
+
if (t2.width() > maxW) t2.fontSize(Math.max(4, t2.fontSize() * maxW / t2.width()));
|
|
3136
3304
|
if (t2.fontSize() < 4.2) {
|
|
3137
3305
|
t2.destroy();
|
|
3138
3306
|
continue;
|
|
@@ -3234,6 +3402,8 @@ var PickerController = class {
|
|
|
3234
3402
|
this.seatTiers = /* @__PURE__ */ new Map();
|
|
3235
3403
|
/** id → seat, for the section-summary breakdown (renderer members are ids). */
|
|
3236
3404
|
this.seatById = /* @__PURE__ */ new Map();
|
|
3405
|
+
/** id → buyer-facing spatial metadata used by every tooltip/confirm surface. */
|
|
3406
|
+
this.seatContext = /* @__PURE__ */ new Map();
|
|
3237
3407
|
this.allIds = [];
|
|
3238
3408
|
// realtime socket
|
|
3239
3409
|
this.ws = null;
|
|
@@ -3312,12 +3482,28 @@ var PickerController = class {
|
|
|
3312
3482
|
this.labelToId = /* @__PURE__ */ new Map();
|
|
3313
3483
|
this.labelToSeat = /* @__PURE__ */ new Map();
|
|
3314
3484
|
this.seatById = /* @__PURE__ */ new Map();
|
|
3485
|
+
this.seatContext = /* @__PURE__ */ new Map();
|
|
3315
3486
|
this.allIds = [];
|
|
3487
|
+
const chartObjects = new Map(allObjects(res.doc).map((object) => [object.id, object]));
|
|
3488
|
+
const membership = computeSections(res.doc);
|
|
3489
|
+
const sectionLabels = new Map(
|
|
3490
|
+
[...membership.sections, ...membership.ungrouped ? [membership.ungrouped] : []].map((section) => [section.id, section.label])
|
|
3491
|
+
);
|
|
3316
3492
|
for (const s of expandChart(res.doc)) {
|
|
3317
3493
|
this.labelToId.set(s.label, s.id);
|
|
3318
3494
|
this.labelToSeat.set(s.label, s);
|
|
3319
3495
|
this.seatById.set(s.id, s);
|
|
3320
3496
|
this.allIds.push(s.id);
|
|
3497
|
+
const source = chartObjects.get(s.rowId);
|
|
3498
|
+
const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
|
|
3499
|
+
const rowLabel = s.kind === "booth" ? void 0 : sourceLabel;
|
|
3500
|
+
const labelParts = s.label.split("-");
|
|
3501
|
+
const seatNumber = rowLabel && s.label.startsWith(`${rowLabel}-`) ? s.label.slice(rowLabel.length + 1) : s.kind === "booth" ? s.label : labelParts[labelParts.length - 1] ?? s.label;
|
|
3502
|
+
this.seatContext.set(s.id, {
|
|
3503
|
+
sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
|
|
3504
|
+
rowLabel,
|
|
3505
|
+
seatNumber
|
|
3506
|
+
});
|
|
3321
3507
|
}
|
|
3322
3508
|
const currency = res.event.currency ?? this.opts.currency;
|
|
3323
3509
|
this.currency = currency ?? "USD";
|
|
@@ -3387,6 +3573,11 @@ var PickerController = class {
|
|
|
3387
3573
|
if (!this.renderer) return [];
|
|
3388
3574
|
return this.renderer.getSelection().map((s) => this.toSeat(s));
|
|
3389
3575
|
}
|
|
3576
|
+
/** Enriched metadata for a seat confirmation card or tooltip. */
|
|
3577
|
+
seatDetails(seatId) {
|
|
3578
|
+
const seat = this.seatById.get(seatId);
|
|
3579
|
+
return seat ? this.describeSeat(seat) : null;
|
|
3580
|
+
}
|
|
3390
3581
|
clearSelection() {
|
|
3391
3582
|
this.renderer?.clearSelection();
|
|
3392
3583
|
this.emitSelectionChange();
|
|
@@ -3426,6 +3617,19 @@ var PickerController = class {
|
|
|
3426
3617
|
throw err;
|
|
3427
3618
|
}
|
|
3428
3619
|
}
|
|
3620
|
+
/** Restore an active server hold without creating or extending inventory. */
|
|
3621
|
+
async resumeHold(holdId) {
|
|
3622
|
+
if (this.closed || !holdId || !this.api.resume) return null;
|
|
3623
|
+
const result = await this.api.resume(this.key, holdId);
|
|
3624
|
+
if (this.closed) return null;
|
|
3625
|
+
const labels = [...new Set(result.items.map((item) => item.label))];
|
|
3626
|
+
if (!labels.length) return null;
|
|
3627
|
+
this.setHold(
|
|
3628
|
+
{ holdId: result.holdId, labels, expiresAt: result.expiresAt, items: result.items },
|
|
3629
|
+
"restored"
|
|
3630
|
+
);
|
|
3631
|
+
return this.hold_;
|
|
3632
|
+
}
|
|
3429
3633
|
/**
|
|
3430
3634
|
* P4 "need more time?": extend the OPEN hold's server-side expiry and re-arm
|
|
3431
3635
|
* the client expiry timer to match (via setHold), so the controller doesn't
|
|
@@ -3515,7 +3719,7 @@ var PickerController = class {
|
|
|
3515
3719
|
async bestAvailable(qty, categoryKey) {
|
|
3516
3720
|
const r = this.renderer;
|
|
3517
3721
|
if (!r) return null;
|
|
3518
|
-
if (this.hold_
|
|
3722
|
+
if (this.hold_ && !await this.release()) return null;
|
|
3519
3723
|
try {
|
|
3520
3724
|
const result = await this.api.bestAvailable(this.key, qty, categoryKey);
|
|
3521
3725
|
r.clearSelection();
|
|
@@ -3600,15 +3804,25 @@ var PickerController = class {
|
|
|
3600
3804
|
/** Release the whole open hold (if any), repaint those seats free. */
|
|
3601
3805
|
async release() {
|
|
3602
3806
|
const hold = this.hold_;
|
|
3603
|
-
if (!hold) return;
|
|
3604
|
-
this.clearHold();
|
|
3605
|
-
const ids = hold.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
3606
|
-
if (ids.length) this.renderer?.setStatus(ids, "free");
|
|
3807
|
+
if (!hold) return true;
|
|
3607
3808
|
try {
|
|
3608
|
-
await this.api.release(this.key, hold.labels, hold.holdId);
|
|
3809
|
+
const result = await this.api.release(this.key, hold.labels, hold.holdId);
|
|
3810
|
+
if (!this.releaseConfirmed(result, hold.labels)) {
|
|
3811
|
+
await this.resnapshot();
|
|
3812
|
+
return false;
|
|
3813
|
+
}
|
|
3609
3814
|
} catch (err) {
|
|
3610
3815
|
this.emitError(err);
|
|
3816
|
+
return false;
|
|
3817
|
+
}
|
|
3818
|
+
if (this.hold_?.holdId !== hold.holdId) return true;
|
|
3819
|
+
this.clearHold();
|
|
3820
|
+
const ids = hold.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
3821
|
+
if (ids.length) {
|
|
3822
|
+
this.renderer?.deselect(ids);
|
|
3823
|
+
this.renderer?.setStatus(ids, "free");
|
|
3611
3824
|
}
|
|
3825
|
+
return true;
|
|
3612
3826
|
}
|
|
3613
3827
|
/**
|
|
3614
3828
|
* Release just some labels from the open hold, keeping the rest held (used when
|
|
@@ -3616,19 +3830,35 @@ var PickerController = class {
|
|
|
3616
3830
|
*/
|
|
3617
3831
|
async releaseLabels(labels) {
|
|
3618
3832
|
const hold = this.hold_;
|
|
3619
|
-
if (!hold) return;
|
|
3833
|
+
if (!hold) return true;
|
|
3620
3834
|
const drop = labels.filter((l) => hold.labels.includes(l));
|
|
3621
|
-
if (!drop.length) return;
|
|
3622
|
-
const remaining = hold.labels.filter((l) => !drop.includes(l));
|
|
3623
|
-
if (remaining.length) this.setHold({ ...hold, labels: remaining });
|
|
3624
|
-
else this.clearHold();
|
|
3625
|
-
const ids = drop.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
3626
|
-
if (ids.length) this.renderer?.setStatus(ids, "free");
|
|
3835
|
+
if (!drop.length) return true;
|
|
3627
3836
|
try {
|
|
3628
|
-
await this.api.release(this.key, drop, hold.holdId);
|
|
3837
|
+
const result = await this.api.release(this.key, drop, hold.holdId);
|
|
3838
|
+
if (!this.releaseConfirmed(result, drop)) {
|
|
3839
|
+
await this.resnapshot();
|
|
3840
|
+
return false;
|
|
3841
|
+
}
|
|
3629
3842
|
} catch (err) {
|
|
3630
3843
|
this.emitError(err);
|
|
3844
|
+
return false;
|
|
3631
3845
|
}
|
|
3846
|
+
if (this.hold_?.holdId !== hold.holdId) return true;
|
|
3847
|
+
const remaining = hold.labels.filter((l) => !drop.includes(l));
|
|
3848
|
+
const remainingItems = hold.items?.filter((item) => !drop.includes(item.label));
|
|
3849
|
+
if (remaining.length) this.setHold({ ...hold, labels: remaining, items: remainingItems });
|
|
3850
|
+
else this.clearHold();
|
|
3851
|
+
const ids = drop.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
3852
|
+
if (ids.length) {
|
|
3853
|
+
this.renderer?.deselect(ids);
|
|
3854
|
+
this.renderer?.setStatus(ids, "free");
|
|
3855
|
+
}
|
|
3856
|
+
return true;
|
|
3857
|
+
}
|
|
3858
|
+
/** New transports return the exact labels released; tolerate older adapters. */
|
|
3859
|
+
releaseConfirmed(result, requested) {
|
|
3860
|
+
const released = result?.released;
|
|
3861
|
+
return !Array.isArray(released) || requested.every((label) => released.includes(label));
|
|
3632
3862
|
}
|
|
3633
3863
|
// ---- renderer proxies (so consumers don't reach through) ------------------
|
|
3634
3864
|
setStatus(ids, status) {
|
|
@@ -3652,6 +3882,9 @@ var PickerController = class {
|
|
|
3652
3882
|
worldToScreen(p) {
|
|
3653
3883
|
return this.renderer?.worldToScreen(p) ?? { x: 0, y: 0 };
|
|
3654
3884
|
}
|
|
3885
|
+
setSelectionFocus(seatId) {
|
|
3886
|
+
this.renderer?.setSelectionFocus?.(seatId);
|
|
3887
|
+
}
|
|
3655
3888
|
setAccessibilityFilter(types) {
|
|
3656
3889
|
this.renderer?.setAccessibilityFilter?.(types);
|
|
3657
3890
|
}
|
|
@@ -3839,7 +4072,8 @@ var PickerController = class {
|
|
|
3839
4072
|
categoryLabel: cat?.label ?? s.categoryKey,
|
|
3840
4073
|
categoryColor: cat?.color ?? "#6e7bff",
|
|
3841
4074
|
status: this.renderer?.getStatus(s.id) ?? "free",
|
|
3842
|
-
currency: this.currency
|
|
4075
|
+
currency: this.currency,
|
|
4076
|
+
...this.seatContext.get(s.id)
|
|
3843
4077
|
};
|
|
3844
4078
|
}
|
|
3845
4079
|
priceFor(categoryKey) {
|
|
@@ -3930,16 +4164,21 @@ var PickerController = class {
|
|
|
3930
4164
|
this.emitSelectionChange();
|
|
3931
4165
|
}
|
|
3932
4166
|
/** Set the open hold + (re)arm the server-authoritative expiry timer. */
|
|
3933
|
-
setHold(hold) {
|
|
4167
|
+
setHold(hold, source = "created") {
|
|
3934
4168
|
const full = { ...hold, seats: this.seatsForLabels(hold.labels) };
|
|
3935
4169
|
this.hold_ = full;
|
|
4170
|
+
this.renderer?.setOwnedHold?.(
|
|
4171
|
+
full.labels.map((label) => this.labelToId.get(label)).filter((id) => !!id)
|
|
4172
|
+
);
|
|
3936
4173
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
3937
4174
|
const ms = Math.max(0, full.expiresAt - Date.now());
|
|
3938
4175
|
this.expiryTimer = setTimeout(() => void this.expireActiveHold(), ms);
|
|
3939
|
-
this.opts.
|
|
4176
|
+
if (source === "restored") this.opts.onHoldRestored?.(full);
|
|
4177
|
+
else this.opts.onHold?.(full);
|
|
3940
4178
|
}
|
|
3941
4179
|
clearHold() {
|
|
3942
4180
|
this.hold_ = null;
|
|
4181
|
+
this.renderer?.setOwnedHold?.(null);
|
|
3943
4182
|
if (this.expiryTimer) {
|
|
3944
4183
|
clearTimeout(this.expiryTimer);
|
|
3945
4184
|
this.expiryTimer = null;
|
|
@@ -3971,6 +4210,9 @@ var PickerController = class {
|
|
|
3971
4210
|
if (!r) return;
|
|
3972
4211
|
this.liveStatuses = new Map(Object.entries(seats));
|
|
3973
4212
|
if (this.allIds.length) r.setStatus(this.allIds, "free");
|
|
4213
|
+
r.setOwnedHold?.(
|
|
4214
|
+
(this.hold_?.labels ?? []).map((label) => this.labelToId.get(label)).filter((id) => !!id)
|
|
4215
|
+
);
|
|
3974
4216
|
const byStatus = { free: [], held: [], booked: [], not_for_sale: [] };
|
|
3975
4217
|
for (const [label, st] of Object.entries(seats)) {
|
|
3976
4218
|
const id = this.labelToId.get(label);
|