@seatlayer/core 0.1.3 → 0.2.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/{de-Y3E6YTGP.js → de-YMAVF5S2.js} +5 -2
- package/dist/de-YMAVF5S2.js.map +1 -0
- package/dist/{es-UM4ZS357.js → es-MLM3IFIY.js} +5 -2
- package/dist/es-MLM3IFIY.js.map +1 -0
- package/dist/{fr-IGRD2M4W.js → fr-N7AAVATM.js} +5 -2
- package/dist/fr-N7AAVATM.js.map +1 -0
- package/dist/index.cjs +255 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -5
- package/dist/index.d.ts +88 -5
- package/dist/index.js +240 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/de-Y3E6YTGP.js.map +0 -1
- package/dist/es-UM4ZS357.js.map +0 -1
- package/dist/fr-IGRD2M4W.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -364,16 +364,35 @@ function chartBounds(doc) {
|
|
|
364
364
|
};
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
// src/core/ga.ts
|
|
368
|
+
var PREFIX = "__sl_ga__";
|
|
369
|
+
var MAX_GA_CAPACITY = 1e5;
|
|
370
|
+
var MAX_EVENT_INVENTORY = 1e5;
|
|
371
|
+
function gaUnitLabel(areaId, index) {
|
|
372
|
+
return `${PREFIX}${encodeURIComponent(areaId)}__${index + 1}`;
|
|
373
|
+
}
|
|
374
|
+
function gaUnitLabels(area) {
|
|
375
|
+
const capacity = Math.min(MAX_GA_CAPACITY, Math.max(0, Math.floor(area.capacity)));
|
|
376
|
+
return Array.from({ length: capacity }, (_, i) => gaUnitLabel(area.id, i));
|
|
377
|
+
}
|
|
378
|
+
function gaAreasOf(doc) {
|
|
379
|
+
return allObjects(doc).filter((o) => o.type === "gaArea");
|
|
380
|
+
}
|
|
381
|
+
function isGaUnitLabel(label) {
|
|
382
|
+
return label.startsWith(PREFIX);
|
|
383
|
+
}
|
|
384
|
+
|
|
367
385
|
// src/core/sections.ts
|
|
368
386
|
var UNGROUPED_ID = "__ungrouped__";
|
|
369
387
|
function objectSeatLabels(o) {
|
|
370
388
|
if (o.type === "row") return expandRow(o).map((s) => s.label);
|
|
371
389
|
if (o.type === "table") return expandTable(o).map((s) => s.label);
|
|
372
390
|
if (o.type === "booth") return expandBooth(o).map((s) => s.label);
|
|
391
|
+
if (o.type === "gaArea") return gaUnitLabels(o);
|
|
373
392
|
return [];
|
|
374
393
|
}
|
|
375
394
|
function isSeatObject(o) {
|
|
376
|
-
return o.type === "row" || o.type === "table" || o.type === "booth";
|
|
395
|
+
return o.type === "row" || o.type === "table" || o.type === "booth" || o.type === "gaArea";
|
|
377
396
|
}
|
|
378
397
|
function computeSections(doc) {
|
|
379
398
|
const objs = allObjects(doc);
|
|
@@ -500,7 +519,10 @@ var en = {
|
|
|
500
519
|
"picker.seatsHeld": "Seats held \u2014 {time}",
|
|
501
520
|
"picker.holdExpired": "Your hold expired \u2014 the seats were released. Pick again.",
|
|
502
521
|
"picker.seatTaken": "Seat {label} was just taken by another buyer.",
|
|
503
|
-
"picker.poweredBy": "Powered by
|
|
522
|
+
"picker.poweredBy": "Powered by SeatLayer",
|
|
523
|
+
"picker.testMode": "TEST MODE",
|
|
524
|
+
"picker.colorblind": "Colorblind-friendly colors",
|
|
525
|
+
"picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
|
|
504
526
|
"picker.seats.one": "{count} seat",
|
|
505
527
|
"picker.seats.other": "{count} seats",
|
|
506
528
|
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
@@ -617,6 +639,7 @@ var ZONE_SUB_PX = 12;
|
|
|
617
639
|
var HELD_FILL = "#6b7280";
|
|
618
640
|
var TAKEN_FILL = "#374151";
|
|
619
641
|
var NFS_STROKE = "#4b5563";
|
|
642
|
+
var CB_PALETTE = ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7"];
|
|
620
643
|
var ACCESS_RING = {
|
|
621
644
|
wheelchair: "#3b82f6",
|
|
622
645
|
companion: "#8b5cf6",
|
|
@@ -719,6 +742,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
719
742
|
this.statusById = /* @__PURE__ */ new Map();
|
|
720
743
|
this.catColor = /* @__PURE__ */ new Map();
|
|
721
744
|
this.theme = {};
|
|
745
|
+
/** Colorblind-safe mode (Okabe-Ito hues + hollow booked seats). */
|
|
746
|
+
this.colorblind = false;
|
|
747
|
+
/** Category order from the doc — the stable index into the CB palette. */
|
|
748
|
+
this.catOrder = [];
|
|
722
749
|
this.selection = /* @__PURE__ */ new Set();
|
|
723
750
|
this.selectionRings = /* @__PURE__ */ new Map();
|
|
724
751
|
this.focusedId = null;
|
|
@@ -969,6 +996,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
969
996
|
this.hoverRing.radius(this.seatR + 2);
|
|
970
997
|
this.catColor.clear();
|
|
971
998
|
this.catPrice.clear();
|
|
999
|
+
this.catOrder = doc.categories.map((c) => c.key);
|
|
972
1000
|
for (const c of doc.categories) {
|
|
973
1001
|
this.catColor.set(c.key, c.color);
|
|
974
1002
|
if (typeof c.price === "number") this.catPrice.set(c.key, c.price);
|
|
@@ -1492,12 +1520,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1492
1520
|
this.hasBoothText = true;
|
|
1493
1521
|
target.add(t2);
|
|
1494
1522
|
}
|
|
1523
|
+
/** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
|
|
1524
|
+
seatBaseColor(categoryKey) {
|
|
1525
|
+
if (!this.colorblind) return this.catColor.get(categoryKey) ?? "#6e7bff";
|
|
1526
|
+
const idx = this.catOrder.indexOf(categoryKey);
|
|
1527
|
+
return CB_PALETTE[(idx >= 0 ? idx : 0) % CB_PALETTE.length];
|
|
1528
|
+
}
|
|
1495
1529
|
/** Apply fill/stroke/opacity for a seat's current status + selection. */
|
|
1496
1530
|
paintSeat(c, id) {
|
|
1497
1531
|
const seat = this.seatById.get(id);
|
|
1498
1532
|
const status = this.statusById.get(id) ?? "free";
|
|
1499
1533
|
const selected = this.selection.has(id);
|
|
1500
|
-
const base = this.
|
|
1534
|
+
const base = this.seatBaseColor(seat.categoryKey);
|
|
1501
1535
|
c.dash([]);
|
|
1502
1536
|
c.strokeWidth(0);
|
|
1503
1537
|
c.stroke("");
|
|
@@ -1510,8 +1544,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1510
1544
|
c.fill(HELD_FILL);
|
|
1511
1545
|
break;
|
|
1512
1546
|
case "booked":
|
|
1513
|
-
|
|
1514
|
-
|
|
1547
|
+
if (this.colorblind) {
|
|
1548
|
+
c.fill("rgba(0,0,0,0)");
|
|
1549
|
+
c.stroke(TAKEN_FILL);
|
|
1550
|
+
c.strokeWidth(1.5);
|
|
1551
|
+
c.opacity(0.9);
|
|
1552
|
+
} else {
|
|
1553
|
+
c.fill(TAKEN_FILL);
|
|
1554
|
+
c.opacity(0.45);
|
|
1555
|
+
}
|
|
1515
1556
|
break;
|
|
1516
1557
|
case "not_for_sale":
|
|
1517
1558
|
c.fill(TAKEN_FILL);
|
|
@@ -1533,6 +1574,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1533
1574
|
}
|
|
1534
1575
|
}
|
|
1535
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Colorblind-safe mode: swap category hues for the Okabe-Ito palette and
|
|
1579
|
+
* render booked seats hollow. Off restores the exact default rendering.
|
|
1580
|
+
*/
|
|
1581
|
+
setColorblindSafe(on) {
|
|
1582
|
+
if (on === this.colorblind) return;
|
|
1583
|
+
this.colorblind = on;
|
|
1584
|
+
for (const seat of this.seats) {
|
|
1585
|
+
const c = this.circleById.get(seat.id);
|
|
1586
|
+
if (c) this.paintSeat(c, seat.id);
|
|
1587
|
+
}
|
|
1588
|
+
if (this.cached) {
|
|
1589
|
+
this.seatLayer.clearCache();
|
|
1590
|
+
this.cacheSeatLayer();
|
|
1591
|
+
} else {
|
|
1592
|
+
this.seatLayer.batchDraw();
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1536
1595
|
/**
|
|
1537
1596
|
* Dim the seats of these section/zone ids (organizer manager use) so held-back
|
|
1538
1597
|
* inventory is visually distinct on the canvas without hiding it. `null`/empty
|
|
@@ -2471,6 +2530,38 @@ function createRenderer(container, opts) {
|
|
|
2471
2530
|
return new SeatmapRenderer(container, opts);
|
|
2472
2531
|
}
|
|
2473
2532
|
|
|
2533
|
+
// src/core/orphans.ts
|
|
2534
|
+
function seatIndex(id) {
|
|
2535
|
+
const n = Number(id.slice(id.lastIndexOf(":") + 1));
|
|
2536
|
+
return Number.isFinite(n) ? n : -1;
|
|
2537
|
+
}
|
|
2538
|
+
function strandedSingles(seats, statusOf, selectedIds) {
|
|
2539
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2540
|
+
for (const s of seats) {
|
|
2541
|
+
if (s.kind === "booth") continue;
|
|
2542
|
+
const list = rows.get(s.rowId);
|
|
2543
|
+
if (list) list.push(s);
|
|
2544
|
+
else rows.set(s.rowId, [s]);
|
|
2545
|
+
}
|
|
2546
|
+
const unavailable = (s) => selectedIds.has(s.id) || statusOf(s.id) !== "free";
|
|
2547
|
+
const out = [];
|
|
2548
|
+
for (const list of rows.values()) {
|
|
2549
|
+
if (list.length < 3) continue;
|
|
2550
|
+
list.sort((a, b) => seatIndex(a.id) - seatIndex(b.id));
|
|
2551
|
+
for (let i = 1; i < list.length - 1; i++) {
|
|
2552
|
+
const seat = list[i];
|
|
2553
|
+
if (unavailable(seat)) continue;
|
|
2554
|
+
const left = list[i - 1];
|
|
2555
|
+
const right = list[i + 1];
|
|
2556
|
+
if (seatIndex(seat.id) - seatIndex(left.id) !== 1 || seatIndex(right.id) - seatIndex(seat.id) !== 1) continue;
|
|
2557
|
+
if (!unavailable(left) || !unavailable(right)) continue;
|
|
2558
|
+
if (!selectedIds.has(left.id) && !selectedIds.has(right.id)) continue;
|
|
2559
|
+
out.push(seat);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
return out;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2474
2565
|
// src/picker/PickerController.ts
|
|
2475
2566
|
var DEFAULT_MAX_SELECTION = 10;
|
|
2476
2567
|
var MAX_BACKOFF_MS = 15e3;
|
|
@@ -2504,7 +2595,11 @@ var PickerController = class {
|
|
|
2504
2595
|
this.closed = false;
|
|
2505
2596
|
// hold state
|
|
2506
2597
|
this.hold_ = null;
|
|
2598
|
+
this.liveStatuses = /* @__PURE__ */ new Map();
|
|
2599
|
+
this.currency = "USD";
|
|
2507
2600
|
this.expiryTimer = null;
|
|
2601
|
+
/** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
|
|
2602
|
+
this.hintShown = false;
|
|
2508
2603
|
this.opts = options;
|
|
2509
2604
|
this.api = options.transport;
|
|
2510
2605
|
this.key = options.eventKey;
|
|
@@ -2561,6 +2656,7 @@ var PickerController = class {
|
|
|
2561
2656
|
this.allIds.push(s.id);
|
|
2562
2657
|
}
|
|
2563
2658
|
const currency = res.event.currency ?? this.opts.currency;
|
|
2659
|
+
this.currency = currency ?? "USD";
|
|
2564
2660
|
const renderer = createRenderer(host, {
|
|
2565
2661
|
maxSelection: this.maxSelection,
|
|
2566
2662
|
confirmSelection: this.opts.confirmSelection,
|
|
@@ -2600,6 +2696,7 @@ var PickerController = class {
|
|
|
2600
2696
|
return null;
|
|
2601
2697
|
}
|
|
2602
2698
|
renderer.setChart(this.visibleDoc());
|
|
2699
|
+
if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
|
|
2603
2700
|
if (seats) this.applySeatsMap(seats);
|
|
2604
2701
|
this.connect();
|
|
2605
2702
|
return {
|
|
@@ -2608,7 +2705,9 @@ var PickerController = class {
|
|
|
2608
2705
|
eventName: res.event.name,
|
|
2609
2706
|
venue: res.event.venue,
|
|
2610
2707
|
startsAt: res.event.startsAt,
|
|
2611
|
-
currency
|
|
2708
|
+
currency,
|
|
2709
|
+
// 'test' ⇒ sandbox event: hosts render a TEST MODE ribbon (Phase 11).
|
|
2710
|
+
mode: res.event.mode ?? "live"
|
|
2612
2711
|
};
|
|
2613
2712
|
}
|
|
2614
2713
|
// ---- selection ------------------------------------------------------------
|
|
@@ -2631,21 +2730,72 @@ var PickerController = class {
|
|
|
2631
2730
|
* re-throws so the caller can choose the banner copy. Returns null only when
|
|
2632
2731
|
* there's nothing to hold.
|
|
2633
2732
|
*/
|
|
2634
|
-
async hold(labelsArg) {
|
|
2733
|
+
async hold(labelsArg, ttlMs) {
|
|
2635
2734
|
const r = this.renderer;
|
|
2636
2735
|
if (!r) return null;
|
|
2637
2736
|
const labels = labelsArg ?? r.getSelection().map((s) => s.label);
|
|
2638
|
-
|
|
2639
|
-
|
|
2737
|
+
const existingGA = (this.hold_?.items ?? []).filter((item) => item.objectType === "ga");
|
|
2738
|
+
const combinedLabels = [.../* @__PURE__ */ new Set([...existingGA.map((item) => item.label), ...labels])];
|
|
2739
|
+
if (!combinedLabels.length) return null;
|
|
2740
|
+
if (this.hold_ && this.holdCovers(combinedLabels)) return this.hold_;
|
|
2640
2741
|
try {
|
|
2641
|
-
const
|
|
2642
|
-
|
|
2742
|
+
const selections = combinedLabels.map((label) => {
|
|
2743
|
+
const ga = existingGA.find((item) => item.label === label);
|
|
2744
|
+
if (ga) return { label, tierId: ga.tierId };
|
|
2745
|
+
const seat = this.labelToSeat.get(label);
|
|
2746
|
+
const resolved = seat ? this.toSeat(seat) : null;
|
|
2747
|
+
return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
|
|
2748
|
+
});
|
|
2749
|
+
const result = await this.api.hold(this.key, selections, ttlMs, this.hold_?.holdId);
|
|
2750
|
+
this.setHold({ holdId: result.holdId, labels: combinedLabels, expiresAt: result.expiresAt, items: result.items });
|
|
2643
2751
|
return this.hold_;
|
|
2644
2752
|
} catch (err) {
|
|
2645
2753
|
this.handle409Conflicts(err);
|
|
2646
2754
|
throw err;
|
|
2647
2755
|
}
|
|
2648
2756
|
}
|
|
2757
|
+
/** Public GA inventory derived from the live synthetic-unit status stream. */
|
|
2758
|
+
getGAAreas() {
|
|
2759
|
+
const doc = this.visibleDoc();
|
|
2760
|
+
if (!doc) return [];
|
|
2761
|
+
return gaAreasOf(doc).map((area) => {
|
|
2762
|
+
const category = doc.categories.find((candidate) => candidate.key === area.categoryKey);
|
|
2763
|
+
return {
|
|
2764
|
+
id: area.id,
|
|
2765
|
+
label: area.label,
|
|
2766
|
+
capacity: Math.max(0, Math.floor(area.capacity)),
|
|
2767
|
+
available: gaUnitLabels(area).filter((label) => (this.liveStatuses.get(label) ?? "free") === "free").length,
|
|
2768
|
+
categoryKey: area.categoryKey,
|
|
2769
|
+
tiers: category?.tiers,
|
|
2770
|
+
price: category?.tiers?.[0]?.price ?? category?.price ?? 0,
|
|
2771
|
+
currency: this.currency
|
|
2772
|
+
};
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
/** Select a quantity from one GA area and hold it atomically. */
|
|
2776
|
+
async holdGA(areaId, qty, options = {}) {
|
|
2777
|
+
const doc = this.visibleDoc();
|
|
2778
|
+
if (!doc || !Number.isFinite(qty) || qty < 1) return null;
|
|
2779
|
+
const area = gaAreasOf(doc).find((candidate) => candidate.id === areaId);
|
|
2780
|
+
if (!area) return null;
|
|
2781
|
+
const labels = gaUnitLabels(area).filter((label) => !this.hold_?.labels.includes(label) && (this.liveStatuses.get(label) ?? "free") === "free").slice(0, Math.floor(qty));
|
|
2782
|
+
if (labels.length !== Math.floor(qty)) return null;
|
|
2783
|
+
const selections = /* @__PURE__ */ new Map();
|
|
2784
|
+
for (const item of this.hold_?.items ?? []) selections.set(item.label, { label: item.label, tierId: item.tierId });
|
|
2785
|
+
if (this.hold_ && !this.hold_?.items?.length) {
|
|
2786
|
+
for (const seat of this.hold_.seats) selections.set(seat.label, { label: seat.label, ...seat.tierId ? { tierId: seat.tierId } : {} });
|
|
2787
|
+
}
|
|
2788
|
+
for (const seat of this.renderer?.getSelection() ?? []) {
|
|
2789
|
+
const resolved = this.labelToSeat.get(seat.label);
|
|
2790
|
+
const chosen = resolved ? this.toSeat(resolved) : null;
|
|
2791
|
+
selections.set(seat.label, { label: seat.label, ...chosen?.tierId ? { tierId: chosen.tierId } : {} });
|
|
2792
|
+
}
|
|
2793
|
+
for (const label of labels) selections.set(label, { label, ...options.tierId ? { tierId: options.tierId } : {} });
|
|
2794
|
+
const combined = [...selections.values()];
|
|
2795
|
+
const result = await this.api.hold(this.key, combined, options.ttlMs, this.hold_?.holdId);
|
|
2796
|
+
this.setHold({ holdId: result.holdId, labels: combined.map((s) => s.label), expiresAt: result.expiresAt, items: result.items });
|
|
2797
|
+
return this.hold_;
|
|
2798
|
+
}
|
|
2649
2799
|
/** Server-picks `qty` best free seats and holds them atomically. Throws on failure. */
|
|
2650
2800
|
async bestAvailable(qty, categoryKey) {
|
|
2651
2801
|
const r = this.renderer;
|
|
@@ -2656,7 +2806,7 @@ var PickerController = class {
|
|
|
2656
2806
|
r.clearSelection();
|
|
2657
2807
|
const ids = result.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
2658
2808
|
if (ids.length) r.setStatus(ids, "held");
|
|
2659
|
-
this.setHold({ holdId: result.holdId, labels: [...result.labels], expiresAt: result.expiresAt });
|
|
2809
|
+
this.setHold({ holdId: result.holdId, labels: [...result.labels], expiresAt: result.expiresAt, items: result.items });
|
|
2660
2810
|
const seats = result.labels.map((l) => this.labelToSeat.get(l)).filter((s) => !!s).map((s) => this.toSeat(s));
|
|
2661
2811
|
this.opts.onSelectionChange?.(seats);
|
|
2662
2812
|
return this.hold_;
|
|
@@ -2685,9 +2835,19 @@ var PickerController = class {
|
|
|
2685
2835
|
if (this.hold_ && this.holdCovers(labels)) {
|
|
2686
2836
|
holdId = this.hold_.holdId;
|
|
2687
2837
|
} else {
|
|
2688
|
-
const
|
|
2838
|
+
const replaceHoldId = this.hold_?.holdId;
|
|
2839
|
+
const h = await this.api.hold(
|
|
2840
|
+
this.key,
|
|
2841
|
+
labels.map((label) => {
|
|
2842
|
+
const seat = this.labelToSeat.get(label);
|
|
2843
|
+
const resolved = seat ? this.toSeat(seat) : null;
|
|
2844
|
+
return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
|
|
2845
|
+
}),
|
|
2846
|
+
void 0,
|
|
2847
|
+
replaceHoldId
|
|
2848
|
+
);
|
|
2689
2849
|
holdId = h.holdId;
|
|
2690
|
-
this.setHold({ holdId, labels: [...labels], expiresAt: h.expiresAt });
|
|
2850
|
+
this.setHold({ holdId, labels: [...labels], expiresAt: h.expiresAt, items: h.items });
|
|
2691
2851
|
}
|
|
2692
2852
|
await this.api.book(this.key, labels, holdId, bookingRef);
|
|
2693
2853
|
} catch (err) {
|
|
@@ -2971,6 +3131,31 @@ var PickerController = class {
|
|
|
2971
3131
|
}
|
|
2972
3132
|
emitSelectionChange() {
|
|
2973
3133
|
this.opts.onSelectionChange?.(this.getSelection());
|
|
3134
|
+
this.emitOrphanHint();
|
|
3135
|
+
}
|
|
3136
|
+
/**
|
|
3137
|
+
* Orphan-seat advice on manual selection: when the buyer's current picks
|
|
3138
|
+
* strand one (or more) single free seats between unavailable same-row
|
|
3139
|
+
* neighbors, surface a localized, non-blocking hint. Cleared (null) as soon
|
|
3140
|
+
* as the selection stops stranding anyone.
|
|
3141
|
+
*/
|
|
3142
|
+
emitOrphanHint() {
|
|
3143
|
+
if (!this.opts.onHint) return;
|
|
3144
|
+
const r = this.renderer;
|
|
3145
|
+
if (!r) return;
|
|
3146
|
+
const selected = new Set(r.getSelection().map((s) => s.id));
|
|
3147
|
+
const stranded = selected.size ? strandedSingles(this.seatById.values(), (id) => r.getStatus(id), selected) : [];
|
|
3148
|
+
if (stranded.length > 0) {
|
|
3149
|
+
this.hintShown = true;
|
|
3150
|
+
this.opts.onHint(t("picker.orphanHint"));
|
|
3151
|
+
} else if (this.hintShown) {
|
|
3152
|
+
this.hintShown = false;
|
|
3153
|
+
this.opts.onHint(null);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
/** Toggle colorblind-safe rendering at runtime (see PickerOptions.colorblindSafe). */
|
|
3157
|
+
setColorblindSafe(on) {
|
|
3158
|
+
this.renderer?.setColorblindSafe?.(on);
|
|
2974
3159
|
}
|
|
2975
3160
|
emitError(err) {
|
|
2976
3161
|
if (this.opts.onError) this.opts.onError(err);
|
|
@@ -2978,7 +3163,13 @@ var PickerController = class {
|
|
|
2978
3163
|
}
|
|
2979
3164
|
holdCovers(labels) {
|
|
2980
3165
|
const h = this.hold_;
|
|
2981
|
-
|
|
3166
|
+
if (!h || h.labels.length !== labels.length || !labels.every((l) => h.labels.includes(l))) return false;
|
|
3167
|
+
const tiers = new Map((h.items ?? []).map((item) => [item.label, item.tierId]));
|
|
3168
|
+
return labels.every((label) => {
|
|
3169
|
+
const seat = this.labelToSeat.get(label);
|
|
3170
|
+
const currentTier = seat ? this.toSeat(seat).tierId ?? null : null;
|
|
3171
|
+
return !tiers.has(label) || tiers.get(label) === currentTier;
|
|
3172
|
+
});
|
|
2982
3173
|
}
|
|
2983
3174
|
handle409Conflicts(err) {
|
|
2984
3175
|
const { status, conflicts } = errInfo(err);
|
|
@@ -2998,7 +3189,7 @@ var PickerController = class {
|
|
|
2998
3189
|
this.hold_ = full;
|
|
2999
3190
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
3000
3191
|
const ms = Math.max(0, full.expiresAt - Date.now());
|
|
3001
|
-
this.expiryTimer = setTimeout(() => this.
|
|
3192
|
+
this.expiryTimer = setTimeout(() => void this.expireActiveHold(), ms);
|
|
3002
3193
|
this.opts.onHold?.(full);
|
|
3003
3194
|
}
|
|
3004
3195
|
clearHold() {
|
|
@@ -3008,18 +3199,31 @@ var PickerController = class {
|
|
|
3008
3199
|
this.expiryTimer = null;
|
|
3009
3200
|
}
|
|
3010
3201
|
}
|
|
3011
|
-
|
|
3202
|
+
async expireActiveHold() {
|
|
3012
3203
|
const hold = this.hold_;
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
const
|
|
3016
|
-
if (
|
|
3204
|
+
if (!hold) return;
|
|
3205
|
+
try {
|
|
3206
|
+
const snap = await this.api.objects(this.key);
|
|
3207
|
+
if (this.hold_?.holdId !== hold.holdId) return;
|
|
3208
|
+
this.applySeatsMap(snap.seats);
|
|
3209
|
+
} catch {
|
|
3210
|
+
if (this.hold_?.holdId === hold.holdId) {
|
|
3211
|
+
this.expiryTimer = setTimeout(() => void this.expireActiveHold(), 2e3);
|
|
3212
|
+
}
|
|
3213
|
+
return;
|
|
3017
3214
|
}
|
|
3215
|
+
if (this.hold_?.holdId !== hold.holdId) return;
|
|
3216
|
+
if (hold.labels.some((label) => this.liveStatuses.get(label) === "held")) {
|
|
3217
|
+
this.expiryTimer = setTimeout(() => void this.expireActiveHold(), 1e3);
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
this.clearHold();
|
|
3018
3221
|
this.opts.onHoldExpired?.();
|
|
3019
3222
|
}
|
|
3020
3223
|
applySeatsMap(seats) {
|
|
3021
3224
|
const r = this.renderer;
|
|
3022
3225
|
if (!r) return;
|
|
3226
|
+
this.liveStatuses = new Map(Object.entries(seats));
|
|
3023
3227
|
if (this.allIds.length) r.setStatus(this.allIds, "free");
|
|
3024
3228
|
const byStatus = { free: [], held: [], booked: [], not_for_sale: [] };
|
|
3025
3229
|
for (const [label, st] of Object.entries(seats)) {
|
|
@@ -3030,6 +3234,10 @@ var PickerController = class {
|
|
|
3030
3234
|
if (byStatus[st].length) r.setStatus(byStatus[st], st);
|
|
3031
3235
|
});
|
|
3032
3236
|
this.opts.onStatusChange?.();
|
|
3237
|
+
this.clearBookedHoldIfSettled();
|
|
3238
|
+
}
|
|
3239
|
+
clearBookedHoldIfSettled() {
|
|
3240
|
+
if (this.hold_?.labels.every((label) => this.liveStatuses.get(label) === "booked")) this.clearHold();
|
|
3033
3241
|
}
|
|
3034
3242
|
async resnapshot() {
|
|
3035
3243
|
try {
|
|
@@ -3072,6 +3280,7 @@ var PickerController = class {
|
|
|
3072
3280
|
this.applySeatsMap(m.seats);
|
|
3073
3281
|
} else if (Array.isArray(m.changes)) {
|
|
3074
3282
|
for (const ch of m.changes) {
|
|
3283
|
+
this.liveStatuses.set(ch.label, ch.status);
|
|
3075
3284
|
const id = this.labelToId.get(ch.label);
|
|
3076
3285
|
if (!id) continue;
|
|
3077
3286
|
const next = mapStatus(ch.status);
|
|
@@ -3080,6 +3289,7 @@ var PickerController = class {
|
|
|
3080
3289
|
}
|
|
3081
3290
|
r.setStatus([id], next);
|
|
3082
3291
|
}
|
|
3292
|
+
this.clearBookedHoldIfSettled();
|
|
3083
3293
|
this.opts.onStatusChange?.();
|
|
3084
3294
|
}
|
|
3085
3295
|
};
|
|
@@ -3107,9 +3317,9 @@ var PickerController = class {
|
|
|
3107
3317
|
|
|
3108
3318
|
// src/i18n/bundles.ts
|
|
3109
3319
|
var LOADERS = {
|
|
3110
|
-
es: () => import("./es-
|
|
3111
|
-
de: () => import("./de-
|
|
3112
|
-
fr: () => import("./fr-
|
|
3320
|
+
es: () => import("./es-MLM3IFIY.js").then((m) => ({ default: m.es })),
|
|
3321
|
+
de: () => import("./de-YMAVF5S2.js").then((m) => ({ default: m.de })),
|
|
3322
|
+
fr: () => import("./fr-N7AAVATM.js").then((m) => ({ default: m.fr }))
|
|
3113
3323
|
};
|
|
3114
3324
|
var loaded = /* @__PURE__ */ new Set(["en"]);
|
|
3115
3325
|
async function loadLocale(code) {
|
|
@@ -3132,6 +3342,8 @@ export {
|
|
|
3132
3342
|
ACCESSIBILITY_TYPES,
|
|
3133
3343
|
CHART_STORAGE_KEY,
|
|
3134
3344
|
DEFAULT_CURRENCY,
|
|
3345
|
+
MAX_EVENT_INVENTORY,
|
|
3346
|
+
MAX_GA_CAPACITY,
|
|
3135
3347
|
PickerController,
|
|
3136
3348
|
SUPPORTED_LOCALES,
|
|
3137
3349
|
SeatmapRenderer,
|
|
@@ -3152,8 +3364,12 @@ export {
|
|
|
3152
3364
|
floorsOf,
|
|
3153
3365
|
formatDate,
|
|
3154
3366
|
formatMoney,
|
|
3367
|
+
gaAreasOf,
|
|
3368
|
+
gaUnitLabel,
|
|
3369
|
+
gaUnitLabels,
|
|
3155
3370
|
getLocale,
|
|
3156
3371
|
hiddenObjectIds,
|
|
3372
|
+
isGaUnitLabel,
|
|
3157
3373
|
isSectionHidden,
|
|
3158
3374
|
layerOf,
|
|
3159
3375
|
loadLocale,
|