@seatlayer/core 0.10.2 → 0.12.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 +222 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +222 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -956,6 +956,7 @@ var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
|
956
956
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
957
957
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
958
958
|
var MAX_LABELS = 700;
|
|
959
|
+
var MARQUEE_RING_CAP = 2500;
|
|
959
960
|
var ISO_ANGLE_DEG = -11.5;
|
|
960
961
|
var ISO_SQUASH = 0.58;
|
|
961
962
|
var LIFT_PER_STEP = 58;
|
|
@@ -1132,6 +1133,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1132
1133
|
this.catPrice = /* @__PURE__ */ new Map();
|
|
1133
1134
|
/** Section/zone ids to render dimmed (organizer manager: held-back inventory). */
|
|
1134
1135
|
this.dimmedSections = /* @__PURE__ */ new Set();
|
|
1136
|
+
/** Organizer control-room velocity overlay; absent on buyer surfaces. */
|
|
1137
|
+
this.sectionHeat = /* @__PURE__ */ new Map();
|
|
1135
1138
|
/** Phase 2: section/zone ids in the event-level `closed` state — flat grey
|
|
1136
1139
|
* block, seats greyed + not pickable, but the section stays rendered. */
|
|
1137
1140
|
this.closedSections = /* @__PURE__ */ new Set();
|
|
@@ -1182,8 +1185,28 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1182
1185
|
this.panLast = null;
|
|
1183
1186
|
/** Cumulative gesture movement in px — clicks are suppressed after a real pan/pinch. */
|
|
1184
1187
|
this.moved = 0;
|
|
1188
|
+
/**
|
|
1189
|
+
* Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
|
|
1190
|
+
* points (overlayLayer rides the stage transform); `rect` is the on-canvas
|
|
1191
|
+
* selection band. Null except during an active manage-mode drag.
|
|
1192
|
+
*/
|
|
1193
|
+
this.marquee = null;
|
|
1194
|
+
this.marqueeCur = null;
|
|
1185
1195
|
// ---- keyboard navigation (accessibility) ----------------------------------
|
|
1186
1196
|
this.onKeyDown = (e) => {
|
|
1197
|
+
if (this.opts.manageMode) {
|
|
1198
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === "a" || e.key === "A")) {
|
|
1199
|
+
e.preventDefault();
|
|
1200
|
+
this.opts.onMarquee?.(this.selectAllSelectable());
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (e.key === "Escape" && this.selection.size) {
|
|
1204
|
+
e.preventDefault();
|
|
1205
|
+
this.clearSelection();
|
|
1206
|
+
this.opts.onMarquee?.([]);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1187
1210
|
const dir = e.key === "ArrowLeft" ? { x: -1, y: 0 } : e.key === "ArrowRight" ? { x: 1, y: 0 } : e.key === "ArrowUp" ? { x: 0, y: -1 } : e.key === "ArrowDown" ? { x: 0, y: 1 } : null;
|
|
1188
1211
|
if (dir) {
|
|
1189
1212
|
e.preventDefault();
|
|
@@ -1206,9 +1229,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1206
1229
|
this.pointers.set(e.pointerId, this.toLocal(e));
|
|
1207
1230
|
if (this.pointers.size === 1) {
|
|
1208
1231
|
this.moved = 0;
|
|
1209
|
-
this.panLast = this.toLocal(e);
|
|
1210
1232
|
this.pinch = null;
|
|
1233
|
+
if (this.opts.manageMode && this.opts.marqueeSelect && e.pointerType !== "touch" && e.button === 0 && this.getRung() === "seats") {
|
|
1234
|
+
this.beginMarquee(this.toLocal(e));
|
|
1235
|
+
this.panLast = null;
|
|
1236
|
+
} else {
|
|
1237
|
+
this.panLast = this.toLocal(e);
|
|
1238
|
+
}
|
|
1211
1239
|
} else if (this.pointers.size === 2) {
|
|
1240
|
+
if (this.marquee) this.cancelMarquee();
|
|
1212
1241
|
const [a, b] = [...this.pointers.values()];
|
|
1213
1242
|
const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
1214
1243
|
const s = this.stage.scaleX();
|
|
@@ -1227,6 +1256,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1227
1256
|
const prev = this.pointers.get(e.pointerId);
|
|
1228
1257
|
this.moved += Math.hypot(p.x - prev.x, p.y - prev.y);
|
|
1229
1258
|
this.pointers.set(e.pointerId, p);
|
|
1259
|
+
if (this.marquee) {
|
|
1260
|
+
this.updateMarquee(p);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1230
1263
|
if (this.pinch && this.pointers.size >= 2) {
|
|
1231
1264
|
const [a, b] = [...this.pointers.values()];
|
|
1232
1265
|
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
@@ -1252,6 +1285,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1252
1285
|
}
|
|
1253
1286
|
};
|
|
1254
1287
|
this.onPointerEnd = (e) => {
|
|
1288
|
+
if (this.marquee) {
|
|
1289
|
+
this.finishMarquee();
|
|
1290
|
+
this.pointers.delete(e.pointerId);
|
|
1291
|
+
if (this.pointers.size === 0) this.panLast = null;
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1255
1294
|
this.pointers.delete(e.pointerId);
|
|
1256
1295
|
if (this.pointers.size < 2) this.pinch = null;
|
|
1257
1296
|
if (this.pointers.size === 1) {
|
|
@@ -1477,6 +1516,31 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1477
1516
|
for (const id of ids) this.setSelected(id, false, true);
|
|
1478
1517
|
this.overlayLayer.batchDraw();
|
|
1479
1518
|
}
|
|
1519
|
+
/** Switch organizer interaction in place so the host preserves camera, LOD,
|
|
1520
|
+
* focus and live status state while moving between Monitor and Block. */
|
|
1521
|
+
setManageInteraction(options) {
|
|
1522
|
+
if (this.marquee) this.cancelMarquee();
|
|
1523
|
+
this.panLast = null;
|
|
1524
|
+
this.opts.manageMode = options.manageMode;
|
|
1525
|
+
this.opts.marqueeSelect = options.marqueeSelect;
|
|
1526
|
+
this.opts.selectableStatuses = [...options.selectableStatuses];
|
|
1527
|
+
if (options.maxSelection != null) this.opts.maxSelection = options.maxSelection;
|
|
1528
|
+
const invalid = [...this.selection].filter((id) => !this.isSelectable(id));
|
|
1529
|
+
if (invalid.length) this.deselect(invalid);
|
|
1530
|
+
if (!options.manageMode && this.selection.size) this.clearSelection();
|
|
1531
|
+
this.container.style.cursor = "default";
|
|
1532
|
+
this.overlayLayer.batchDraw();
|
|
1533
|
+
}
|
|
1534
|
+
/** Apply a non-destructive velocity treatment to section outlines. Seat
|
|
1535
|
+
* category/status fills remain untouched so heat never changes seat meaning. */
|
|
1536
|
+
setSectionHeat(scores) {
|
|
1537
|
+
this.sectionHeat.clear();
|
|
1538
|
+
for (const [sectionId, raw] of Object.entries(scores ?? {})) {
|
|
1539
|
+
if (Number.isFinite(raw)) this.sectionHeat.set(sectionId, Math.max(0, Math.min(1, raw)));
|
|
1540
|
+
}
|
|
1541
|
+
for (const section of this.sections) this.refreshSectionHeat(section);
|
|
1542
|
+
this.bgLayer.batchDraw();
|
|
1543
|
+
}
|
|
1480
1544
|
deselect(seatIds) {
|
|
1481
1545
|
let changed = false;
|
|
1482
1546
|
for (const id of seatIds) {
|
|
@@ -1487,6 +1551,144 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1487
1551
|
}
|
|
1488
1552
|
if (changed) this.overlayLayer.batchDraw();
|
|
1489
1553
|
}
|
|
1554
|
+
// ---- manage-mode bulk selection (SDK SeatManager) -------------------------
|
|
1555
|
+
// All option-gated: no-ops (or empty) unless `manageMode` is set, so buyer
|
|
1556
|
+
// surfaces that never pass the flag can't reach any of this.
|
|
1557
|
+
/** Select every selectable seat on the chart (⌘A). Returns the added seats. */
|
|
1558
|
+
selectAllSelectable() {
|
|
1559
|
+
if (!this.opts.manageMode) return [];
|
|
1560
|
+
const ids = [];
|
|
1561
|
+
for (const seat of this.seats) if (this.isSelectable(seat.id)) ids.push(seat.id);
|
|
1562
|
+
return this.selectMany(ids);
|
|
1563
|
+
}
|
|
1564
|
+
/** Select the selectable seats matching these public labels (category/row/
|
|
1565
|
+
* section bulk resolves to labels host-side). Returns the newly added seats. */
|
|
1566
|
+
selectByLabels(labels) {
|
|
1567
|
+
if (!this.opts.manageMode) return [];
|
|
1568
|
+
const want = new Set(labels);
|
|
1569
|
+
const ids = [];
|
|
1570
|
+
for (const seat of this.seats) {
|
|
1571
|
+
if (want.has(seat.label) && this.isSelectable(seat.id)) ids.push(seat.id);
|
|
1572
|
+
}
|
|
1573
|
+
return this.selectMany(ids);
|
|
1574
|
+
}
|
|
1575
|
+
/** Selectable seats in a section OR zone id — pure read (no selection change). */
|
|
1576
|
+
getSelectableInSection(sectionId) {
|
|
1577
|
+
const out = [];
|
|
1578
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1579
|
+
for (const sec of this.sections) {
|
|
1580
|
+
if (sec.id !== sectionId && sec.zone !== sectionId) continue;
|
|
1581
|
+
for (const id of sec.memberIds) {
|
|
1582
|
+
if (seen.has(id)) continue;
|
|
1583
|
+
seen.add(id);
|
|
1584
|
+
if (!this.isSelectable(id)) continue;
|
|
1585
|
+
const s = this.seatById.get(id);
|
|
1586
|
+
if (s) out.push(s);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return out;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Union `ids` into the selection in one batched pass. Beyond MARQUEE_RING_CAP
|
|
1593
|
+
* total selected we skip the per-seat ring nodes (fill paint still marks the
|
|
1594
|
+
* seats) so a whole-arena select doesn't spawn thousands of Konva shapes.
|
|
1595
|
+
* Returns the full current selection.
|
|
1596
|
+
*/
|
|
1597
|
+
selectMany(ids) {
|
|
1598
|
+
const fresh = ids.filter((id) => !this.selection.has(id));
|
|
1599
|
+
if (!fresh.length) return this.getSelection();
|
|
1600
|
+
const drawRings = this.selection.size + fresh.length <= MARQUEE_RING_CAP;
|
|
1601
|
+
for (const id of fresh) {
|
|
1602
|
+
if (drawRings) {
|
|
1603
|
+
this.setSelected(id, true, true);
|
|
1604
|
+
} else {
|
|
1605
|
+
this.selection.add(id);
|
|
1606
|
+
const c = this.circleById.get(id);
|
|
1607
|
+
if (c) this.paintSeat(c, id);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (!this.cached) this.seatLayer.batchDraw();
|
|
1611
|
+
this.overlayLayer.batchDraw();
|
|
1612
|
+
return this.getSelection();
|
|
1613
|
+
}
|
|
1614
|
+
// ---- manage-mode marquee gesture ------------------------------------------
|
|
1615
|
+
beginMarquee(clientPt) {
|
|
1616
|
+
const w = this.screenToWorld(clientPt);
|
|
1617
|
+
const s = this.stage.scaleX() || 1;
|
|
1618
|
+
const rect = new import_Rect.Rect({
|
|
1619
|
+
x: w.x,
|
|
1620
|
+
y: w.y,
|
|
1621
|
+
width: 0,
|
|
1622
|
+
height: 0,
|
|
1623
|
+
stroke: this.effSelection,
|
|
1624
|
+
strokeWidth: 1.5 / s,
|
|
1625
|
+
// world units → ~constant on-screen px under the stage scale
|
|
1626
|
+
dash: [5 / s, 4 / s],
|
|
1627
|
+
fill: "rgba(110,123,255,0.10)",
|
|
1628
|
+
listening: false,
|
|
1629
|
+
perfectDrawEnabled: false,
|
|
1630
|
+
shadowForStrokeEnabled: false
|
|
1631
|
+
});
|
|
1632
|
+
this.overlayLayer.add(rect);
|
|
1633
|
+
this.marquee = { start: w, rect };
|
|
1634
|
+
this.marqueeCur = w;
|
|
1635
|
+
this.overlayLayer.batchDraw();
|
|
1636
|
+
}
|
|
1637
|
+
updateMarquee(clientPt) {
|
|
1638
|
+
if (!this.marquee) return;
|
|
1639
|
+
const w = this.screenToWorld(clientPt);
|
|
1640
|
+
const { start, rect } = this.marquee;
|
|
1641
|
+
rect.setAttrs({
|
|
1642
|
+
x: Math.min(start.x, w.x),
|
|
1643
|
+
y: Math.min(start.y, w.y),
|
|
1644
|
+
width: Math.abs(w.x - start.x),
|
|
1645
|
+
height: Math.abs(w.y - start.y)
|
|
1646
|
+
});
|
|
1647
|
+
this.marqueeCur = w;
|
|
1648
|
+
this.overlayLayer.batchDraw();
|
|
1649
|
+
}
|
|
1650
|
+
cancelMarquee() {
|
|
1651
|
+
if (!this.marquee) return;
|
|
1652
|
+
this.marquee.rect.destroy();
|
|
1653
|
+
this.marquee = null;
|
|
1654
|
+
this.marqueeCur = null;
|
|
1655
|
+
this.overlayLayer.batchDraw();
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Pointer-up: hit-test the marquee world-rect against the in-memory seat
|
|
1659
|
+
* centres (NOT the Konva hit-graph — that's a cached bitmap when zoomed and
|
|
1660
|
+
* far slower), keep only SELECTABLE seats, union them into the selection and
|
|
1661
|
+
* fire `onMarquee`. A near-zero drag reads as a click: clear the selection.
|
|
1662
|
+
*/
|
|
1663
|
+
finishMarquee() {
|
|
1664
|
+
const m = this.marquee;
|
|
1665
|
+
this.marquee = null;
|
|
1666
|
+
if (!m) return;
|
|
1667
|
+
m.rect.destroy();
|
|
1668
|
+
this.overlayLayer.batchDraw();
|
|
1669
|
+
const cur = this.marqueeCur ?? m.start;
|
|
1670
|
+
this.marqueeCur = null;
|
|
1671
|
+
const x0 = Math.min(m.start.x, cur.x);
|
|
1672
|
+
const x1 = Math.max(m.start.x, cur.x);
|
|
1673
|
+
const y0 = Math.min(m.start.y, cur.y);
|
|
1674
|
+
const y1 = Math.max(m.start.y, cur.y);
|
|
1675
|
+
const s = this.stage.scaleX() || 1;
|
|
1676
|
+
if ((x1 - x0) * s < 4 && (y1 - y0) * s < 4) {
|
|
1677
|
+
if (this.selection.size) {
|
|
1678
|
+
this.clearSelection();
|
|
1679
|
+
this.opts.onMarquee?.([]);
|
|
1680
|
+
}
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
const ids = [];
|
|
1684
|
+
for (const seat of this.seats) {
|
|
1685
|
+
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
1686
|
+
if (!this.isSelectable(seat.id)) continue;
|
|
1687
|
+
ids.push(seat.id);
|
|
1688
|
+
}
|
|
1689
|
+
this.selectMany(ids);
|
|
1690
|
+
this.opts.onMarquee?.(this.getSelection());
|
|
1691
|
+
}
|
|
1490
1692
|
flashSeat(seatId, color = "#f43f5e") {
|
|
1491
1693
|
const seat = this.seatById.get(seatId);
|
|
1492
1694
|
if (!seat) return;
|
|
@@ -2535,6 +2737,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2535
2737
|
total: memberIds.length,
|
|
2536
2738
|
free,
|
|
2537
2739
|
baseFill,
|
|
2740
|
+
outlineTint,
|
|
2538
2741
|
outlinePoly,
|
|
2539
2742
|
blockPoly,
|
|
2540
2743
|
rowLines,
|
|
@@ -2547,8 +2750,26 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2547
2750
|
};
|
|
2548
2751
|
for (const id of memberIds) this.seatSection.set(id, sec);
|
|
2549
2752
|
this.refreshSectionFill(sec);
|
|
2753
|
+
this.refreshSectionHeat(sec);
|
|
2550
2754
|
this.sections.push(sec);
|
|
2551
2755
|
}
|
|
2756
|
+
refreshSectionHeat(sec) {
|
|
2757
|
+
const raw = this.sectionHeat.get(sec.id) ?? (sec.zone ? this.sectionHeat.get(sec.zone) : void 0);
|
|
2758
|
+
if (raw == null || raw <= 0) {
|
|
2759
|
+
sec.outlinePoly.stroke(rgba(sec.outlineTint, 0.5));
|
|
2760
|
+
sec.outlinePoly.fill(rgba(sec.outlineTint, 0.08));
|
|
2761
|
+
sec.outlinePoly.strokeWidth(1.75);
|
|
2762
|
+
sec.outlinePoly.shadowOpacity(0);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const color = lerpColor("#f4b740", "#ef4444", raw);
|
|
2766
|
+
sec.outlinePoly.stroke(rgba(color, 0.72 + raw * 0.25));
|
|
2767
|
+
sec.outlinePoly.fill(rgba(color, 0.08 + raw * 0.13));
|
|
2768
|
+
sec.outlinePoly.strokeWidth(2 + raw * 3.5);
|
|
2769
|
+
sec.outlinePoly.shadowColor(color);
|
|
2770
|
+
sec.outlinePoly.shadowBlur(4 + raw * 12);
|
|
2771
|
+
sec.outlinePoly.shadowOpacity(0.25 + raw * 0.45);
|
|
2772
|
+
}
|
|
2552
2773
|
/** Recompute a section's availability-tinted fill + "N LEFT" (cheap; on status change). */
|
|
2553
2774
|
refreshSectionFill(sec) {
|
|
2554
2775
|
sec.blockPoly.fill(this.sectionBlockFill(sec));
|