@seatlayer/core 0.22.0 → 0.23.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-KPTY7UOT.js → de-DRWMZ2FV.js} +4 -2
- package/dist/{de-KPTY7UOT.js.map → de-DRWMZ2FV.js.map} +1 -1
- package/dist/{es-ALPR5FBB.js → es-N73CIVTC.js} +4 -2
- package/dist/{es-ALPR5FBB.js.map → es-N73CIVTC.js.map} +1 -1
- package/dist/{fr-HVQLQD2N.js → fr-SOY2OB4P.js} +4 -2
- package/dist/{fr-HVQLQD2N.js.map → fr-SOY2OB4P.js.map} +1 -1
- package/dist/index.cjs +187 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +181 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2067,8 +2067,28 @@ declare class PickerController {
|
|
|
2067
2067
|
tierId?: string | null;
|
|
2068
2068
|
ttlMs?: number;
|
|
2069
2069
|
}): Promise<HoldInfo | null>;
|
|
2070
|
-
/**
|
|
2071
|
-
|
|
2070
|
+
/** True when any seat on the visible chart carries the `premium` commercial
|
|
2071
|
+
* flag — gates the buyer widget's "★ Best seats" premium quick-pick (chip is
|
|
2072
|
+
* present-only, exactly like the accessibility filter chips). */
|
|
2073
|
+
hasPremiumSeats(): boolean;
|
|
2074
|
+
/**
|
|
2075
|
+
* Client-side premium pre-pass: find the best contiguous block of `qty`
|
|
2076
|
+
* PREMIUM-flagged free seats (orphan-avoiding, closest to the focal point)
|
|
2077
|
+
* using the local seat geometry + live availability, mirroring the server's
|
|
2078
|
+
* held-back exclusion via the visible (hidden-stripped) chart and current
|
|
2079
|
+
* seat status. Returns a FULL premium block of `qty`, or null when none
|
|
2080
|
+
* exists (the caller then falls back to the normal server pick).
|
|
2081
|
+
*/
|
|
2082
|
+
private pickPremiumBlock;
|
|
2083
|
+
/**
|
|
2084
|
+
* Server-picks `qty` best free seats and holds them atomically. Throws on
|
|
2085
|
+
* failure. With `preferPremium`, first tries to hold the best PREMIUM block
|
|
2086
|
+
* locally; when none matches the requested quantity it falls back to the
|
|
2087
|
+
* normal server pick (the widget surfaces a subtle note on that fallback).
|
|
2088
|
+
*/
|
|
2089
|
+
bestAvailable(qty: number, categoryKey?: string, opts?: {
|
|
2090
|
+
preferPremium?: boolean;
|
|
2091
|
+
}): Promise<HoldInfo | null>;
|
|
2072
2092
|
/**
|
|
2073
2093
|
* Complete a booking. Requires a transport that supports book() (the SDK
|
|
2074
2094
|
* deliberately does not). `bookingRef` MUST be a real reference from the caller.
|
package/dist/index.d.ts
CHANGED
|
@@ -2067,8 +2067,28 @@ declare class PickerController {
|
|
|
2067
2067
|
tierId?: string | null;
|
|
2068
2068
|
ttlMs?: number;
|
|
2069
2069
|
}): Promise<HoldInfo | null>;
|
|
2070
|
-
/**
|
|
2071
|
-
|
|
2070
|
+
/** True when any seat on the visible chart carries the `premium` commercial
|
|
2071
|
+
* flag — gates the buyer widget's "★ Best seats" premium quick-pick (chip is
|
|
2072
|
+
* present-only, exactly like the accessibility filter chips). */
|
|
2073
|
+
hasPremiumSeats(): boolean;
|
|
2074
|
+
/**
|
|
2075
|
+
* Client-side premium pre-pass: find the best contiguous block of `qty`
|
|
2076
|
+
* PREMIUM-flagged free seats (orphan-avoiding, closest to the focal point)
|
|
2077
|
+
* using the local seat geometry + live availability, mirroring the server's
|
|
2078
|
+
* held-back exclusion via the visible (hidden-stripped) chart and current
|
|
2079
|
+
* seat status. Returns a FULL premium block of `qty`, or null when none
|
|
2080
|
+
* exists (the caller then falls back to the normal server pick).
|
|
2081
|
+
*/
|
|
2082
|
+
private pickPremiumBlock;
|
|
2083
|
+
/**
|
|
2084
|
+
* Server-picks `qty` best free seats and holds them atomically. Throws on
|
|
2085
|
+
* failure. With `preferPremium`, first tries to hold the best PREMIUM block
|
|
2086
|
+
* locally; when none matches the requested quantity it falls back to the
|
|
2087
|
+
* normal server pick (the widget surfaces a subtle note on that fallback).
|
|
2088
|
+
*/
|
|
2089
|
+
bestAvailable(qty: number, categoryKey?: string, opts?: {
|
|
2090
|
+
preferPremium?: boolean;
|
|
2091
|
+
}): Promise<HoldInfo | null>;
|
|
2072
2092
|
/**
|
|
2073
2093
|
* Complete a booking. Requires a transport that supports book() (the SDK
|
|
2074
2094
|
* deliberately does not). `bookingRef` MUST be a real reference from the caller.
|
package/dist/index.js
CHANGED
|
@@ -323,8 +323,8 @@ function polygonLabelPoint(outer, holes) {
|
|
|
323
323
|
const xs = outer.map((point) => point.x);
|
|
324
324
|
const ys = outer.map((point) => point.y);
|
|
325
325
|
const bounds = { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) };
|
|
326
|
-
const
|
|
327
|
-
if (pointInPolygonWithHoles(
|
|
326
|
+
const centroid2 = polygonCentroid(outer);
|
|
327
|
+
if (pointInPolygonWithHoles(centroid2, outer, holes)) return centroid2;
|
|
328
328
|
let best = outer[0];
|
|
329
329
|
let bestScore = -Infinity;
|
|
330
330
|
const rings = [outer, ...holes ?? []];
|
|
@@ -810,7 +810,9 @@ var en = {
|
|
|
810
810
|
"picker.restrictedView": "Restricted view",
|
|
811
811
|
"picker.obstructedView": "Obstructed view",
|
|
812
812
|
"picker.premiumSeat": "Premium seat",
|
|
813
|
-
"picker.hideLimitedView": "Hide limited-view seats"
|
|
813
|
+
"picker.hideLimitedView": "Hide limited-view seats",
|
|
814
|
+
"picker.bestSeatsPremium": "Best seats",
|
|
815
|
+
"picker.premiumFallbackNote": "No premium block of {count} \u2014 showing best overall"
|
|
814
816
|
};
|
|
815
817
|
|
|
816
818
|
// src/i18n/index.ts
|
|
@@ -3016,7 +3018,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3016
3018
|
* Membership and category mix are still precomputed for the detailed state.
|
|
3017
3019
|
*/
|
|
3018
3020
|
renderSection(obj) {
|
|
3019
|
-
const
|
|
3021
|
+
const centroid2 = polygonLabelPoint(obj.outline, obj.holes);
|
|
3020
3022
|
const palette = overviewPalette(this.canvasBackground);
|
|
3021
3023
|
const memberIds = [];
|
|
3022
3024
|
const catCounts = /* @__PURE__ */ new Map();
|
|
@@ -3078,8 +3080,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3078
3080
|
const preferredInk = labelStyle?.color ?? palette.sectionInk;
|
|
3079
3081
|
const labelScale = (labelStyle?.size ?? 18) / 18;
|
|
3080
3082
|
const nameLabel = new Text({
|
|
3081
|
-
x: obj.labelPresentation?.position?.x ??
|
|
3082
|
-
y: obj.labelPresentation?.position?.y ??
|
|
3083
|
+
x: obj.labelPresentation?.position?.x ?? centroid2.x,
|
|
3084
|
+
y: obj.labelPresentation?.position?.y ?? centroid2.y,
|
|
3083
3085
|
text: obj.displayLabel ?? obj.label,
|
|
3084
3086
|
rotation: obj.labelPresentation?.rotation ?? 0,
|
|
3085
3087
|
visible: obj.labelPresentation?.visible !== false,
|
|
@@ -3094,8 +3096,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3094
3096
|
nameLabel.offsetY(nameLabel.height() / 2);
|
|
3095
3097
|
bgTarget.add(nameLabel);
|
|
3096
3098
|
const subLabel = new Text({
|
|
3097
|
-
x:
|
|
3098
|
-
y:
|
|
3099
|
+
x: centroid2.x,
|
|
3100
|
+
y: centroid2.y,
|
|
3099
3101
|
text: t("map.seatsLeft", { count: free }),
|
|
3100
3102
|
fontSize: 12,
|
|
3101
3103
|
fontStyle: "700",
|
|
@@ -3114,8 +3116,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3114
3116
|
outline: obj.outline,
|
|
3115
3117
|
...obj.outlinePath ? { outlinePath: obj.outlinePath } : {},
|
|
3116
3118
|
holes: obj.holes ?? [],
|
|
3117
|
-
centroid,
|
|
3118
|
-
labelAnchors: polygonLabelCandidates(obj.outline, obj.holes ?? [],
|
|
3119
|
+
centroid: centroid2,
|
|
3120
|
+
labelAnchors: polygonLabelCandidates(obj.outline, obj.holes ?? [], centroid2),
|
|
3119
3121
|
zone: obj.zone,
|
|
3120
3122
|
memberIds,
|
|
3121
3123
|
total: memberIds.length,
|
|
@@ -4475,6 +4477,103 @@ function strandedSingles(seats, statusOf, selectedIds) {
|
|
|
4475
4477
|
return out;
|
|
4476
4478
|
}
|
|
4477
4479
|
|
|
4480
|
+
// src/core/bestAvailable.ts
|
|
4481
|
+
function seatIndex2(seat, fallback) {
|
|
4482
|
+
const i = seat.id.lastIndexOf(":");
|
|
4483
|
+
if (i < 0) return fallback;
|
|
4484
|
+
const n = Number(seat.id.slice(i + 1));
|
|
4485
|
+
return Number.isFinite(n) ? n : fallback;
|
|
4486
|
+
}
|
|
4487
|
+
function dist2(a, b) {
|
|
4488
|
+
const dx = a.x - b.x;
|
|
4489
|
+
const dy = a.y - b.y;
|
|
4490
|
+
return dx * dx + dy * dy;
|
|
4491
|
+
}
|
|
4492
|
+
function centroid(seats) {
|
|
4493
|
+
let x = 0;
|
|
4494
|
+
let y = 0;
|
|
4495
|
+
for (const s of seats) {
|
|
4496
|
+
x += s.x;
|
|
4497
|
+
y += s.y;
|
|
4498
|
+
}
|
|
4499
|
+
return { x: x / seats.length, y: y / seats.length };
|
|
4500
|
+
}
|
|
4501
|
+
function isPremium(seat) {
|
|
4502
|
+
return seat.commercial?.premium === true;
|
|
4503
|
+
}
|
|
4504
|
+
function pickBestAvailable(seats, available, opts) {
|
|
4505
|
+
const qty = Math.floor(opts.qty);
|
|
4506
|
+
if (!Number.isFinite(qty) || qty <= 0) return { labels: [], reason: "sold_out" };
|
|
4507
|
+
const { categoryKey, focal, preferPremium } = opts;
|
|
4508
|
+
const rows = /* @__PURE__ */ new Map();
|
|
4509
|
+
const eligibleAll = [];
|
|
4510
|
+
seats.forEach((seat, i) => {
|
|
4511
|
+
const elig = available.has(seat.label) && (!categoryKey || seat.categoryKey === categoryKey);
|
|
4512
|
+
let arr = rows.get(seat.rowId);
|
|
4513
|
+
if (!arr) {
|
|
4514
|
+
arr = [];
|
|
4515
|
+
rows.set(seat.rowId, arr);
|
|
4516
|
+
}
|
|
4517
|
+
arr.push({ seat, index: seatIndex2(seat, i), elig });
|
|
4518
|
+
if (elig) eligibleAll.push(seat);
|
|
4519
|
+
});
|
|
4520
|
+
if (eligibleAll.length === 0) return { labels: [], reason: "sold_out" };
|
|
4521
|
+
let best = null;
|
|
4522
|
+
const better = (c) => {
|
|
4523
|
+
if (!best) return true;
|
|
4524
|
+
if (preferPremium && c.nonPremium !== best.nonPremium) return c.nonPremium < best.nonPremium;
|
|
4525
|
+
if (c.orphan !== best.orphan) return c.orphan < best.orphan;
|
|
4526
|
+
if (c.d2 !== best.d2) return c.d2 < best.d2;
|
|
4527
|
+
const r = c.rowId.localeCompare(best.rowId);
|
|
4528
|
+
if (r !== 0) return r < 0;
|
|
4529
|
+
return c.startIndex < best.startIndex;
|
|
4530
|
+
};
|
|
4531
|
+
for (const [rowId, slotsUnsorted] of rows) {
|
|
4532
|
+
const slots = [...slotsUnsorted].sort((a, b) => a.index - b.index);
|
|
4533
|
+
let i = 0;
|
|
4534
|
+
while (i < slots.length) {
|
|
4535
|
+
if (!slots[i].elig) {
|
|
4536
|
+
i++;
|
|
4537
|
+
continue;
|
|
4538
|
+
}
|
|
4539
|
+
let j = i;
|
|
4540
|
+
while (j < slots.length && slots[j].elig) j++;
|
|
4541
|
+
const segLen = j - i;
|
|
4542
|
+
for (let p = 0; p + qty <= segLen; p++) {
|
|
4543
|
+
const leftRem = p;
|
|
4544
|
+
const rightRem = segLen - (p + qty);
|
|
4545
|
+
const orphan = leftRem === 1 || rightRem === 1 ? 1 : 0;
|
|
4546
|
+
const runSeats = slots.slice(i + p, i + p + qty).map((s) => s.seat);
|
|
4547
|
+
const c = {
|
|
4548
|
+
labels: runSeats.map((s) => s.label),
|
|
4549
|
+
seats: runSeats,
|
|
4550
|
+
rowId,
|
|
4551
|
+
startIndex: slots[i + p].index,
|
|
4552
|
+
orphan,
|
|
4553
|
+
nonPremium: preferPremium ? runSeats.reduce((n, s) => n + (isPremium(s) ? 0 : 1), 0) : 0,
|
|
4554
|
+
d2: dist2(centroid(runSeats), focal)
|
|
4555
|
+
};
|
|
4556
|
+
if (better(c)) best = c;
|
|
4557
|
+
}
|
|
4558
|
+
i = j;
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
if (best) return { labels: best.labels };
|
|
4562
|
+
if (eligibleAll.length < qty) return { labels: [], reason: "not_enough_together" };
|
|
4563
|
+
const ranked = eligibleAll.map((seat, i) => ({ seat, i, d2: dist2(seat, focal) })).sort((a, b) => {
|
|
4564
|
+
if (preferPremium) {
|
|
4565
|
+
const pa = isPremium(a.seat) ? 0 : 1;
|
|
4566
|
+
const pb = isPremium(b.seat) ? 0 : 1;
|
|
4567
|
+
if (pa !== pb) return pa - pb;
|
|
4568
|
+
}
|
|
4569
|
+
if (a.d2 !== b.d2) return a.d2 - b.d2;
|
|
4570
|
+
const r = a.seat.rowId.localeCompare(b.seat.rowId);
|
|
4571
|
+
if (r !== 0) return r;
|
|
4572
|
+
return seatIndex2(a.seat, a.i) - seatIndex2(b.seat, b.i);
|
|
4573
|
+
});
|
|
4574
|
+
return { labels: ranked.slice(0, qty).map((r) => r.seat.label) };
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4478
4577
|
// src/picker/PickerController.ts
|
|
4479
4578
|
var DEFAULT_MAX_SELECTION = 10;
|
|
4480
4579
|
var MAX_BACKOFF_MS = 15e3;
|
|
@@ -4826,10 +4925,77 @@ var PickerController = class {
|
|
|
4826
4925
|
this.setHold({ holdId: result.holdId, labels: combined.map((s) => s.label), expiresAt: result.expiresAt, items: result.items });
|
|
4827
4926
|
return this.hold_;
|
|
4828
4927
|
}
|
|
4829
|
-
/**
|
|
4830
|
-
|
|
4928
|
+
/** True when any seat on the visible chart carries the `premium` commercial
|
|
4929
|
+
* flag — gates the buyer widget's "★ Best seats" premium quick-pick (chip is
|
|
4930
|
+
* present-only, exactly like the accessibility filter chips). */
|
|
4931
|
+
hasPremiumSeats() {
|
|
4932
|
+
for (const seat of this.labelToSeat.values()) {
|
|
4933
|
+
if (seat.commercial?.premium) return true;
|
|
4934
|
+
}
|
|
4935
|
+
return false;
|
|
4936
|
+
}
|
|
4937
|
+
/**
|
|
4938
|
+
* Client-side premium pre-pass: find the best contiguous block of `qty`
|
|
4939
|
+
* PREMIUM-flagged free seats (orphan-avoiding, closest to the focal point)
|
|
4940
|
+
* using the local seat geometry + live availability, mirroring the server's
|
|
4941
|
+
* held-back exclusion via the visible (hidden-stripped) chart and current
|
|
4942
|
+
* seat status. Returns a FULL premium block of `qty`, or null when none
|
|
4943
|
+
* exists (the caller then falls back to the normal server pick).
|
|
4944
|
+
*/
|
|
4945
|
+
pickPremiumBlock(qty, categoryKey) {
|
|
4946
|
+
const doc = this.visibleDoc();
|
|
4947
|
+
if (!doc?.objects?.length) return null;
|
|
4948
|
+
const focal = doc.focalPoint ?? { x: 0, y: 0 };
|
|
4949
|
+
const seats = expandChart(doc);
|
|
4950
|
+
const held = new Set(this.hold_?.labels ?? []);
|
|
4951
|
+
const available = /* @__PURE__ */ new Set();
|
|
4952
|
+
for (const seat of seats) {
|
|
4953
|
+
if (held.has(seat.label)) continue;
|
|
4954
|
+
const id = this.labelToId.get(seat.label);
|
|
4955
|
+
const status = id ? this.getStatus(id) : void 0;
|
|
4956
|
+
if ((status ?? "free") === "free") available.add(seat.label);
|
|
4957
|
+
}
|
|
4958
|
+
const pick = pickBestAvailable(seats, available, { qty, categoryKey, focal, preferPremium: true });
|
|
4959
|
+
if (pick.labels.length !== qty) return null;
|
|
4960
|
+
const allPremium = pick.labels.every((l) => this.labelToSeat.get(l)?.commercial?.premium);
|
|
4961
|
+
return allPremium ? [...pick.labels] : null;
|
|
4962
|
+
}
|
|
4963
|
+
/**
|
|
4964
|
+
* Server-picks `qty` best free seats and holds them atomically. Throws on
|
|
4965
|
+
* failure. With `preferPremium`, first tries to hold the best PREMIUM block
|
|
4966
|
+
* locally; when none matches the requested quantity it falls back to the
|
|
4967
|
+
* normal server pick (the widget surfaces a subtle note on that fallback).
|
|
4968
|
+
*/
|
|
4969
|
+
async bestAvailable(qty, categoryKey, opts = {}) {
|
|
4831
4970
|
const r = this.renderer;
|
|
4832
4971
|
if (!r) return null;
|
|
4972
|
+
if (opts.preferPremium) {
|
|
4973
|
+
const block = this.pickPremiumBlock(qty, categoryKey);
|
|
4974
|
+
if (block) {
|
|
4975
|
+
if (this.hold_ && !await this.release()) return null;
|
|
4976
|
+
try {
|
|
4977
|
+
const selection = block.map((label) => {
|
|
4978
|
+
const seat = this.labelToSeat.get(label);
|
|
4979
|
+
const resolved = seat ? this.toSeat(seat) : null;
|
|
4980
|
+
return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
|
|
4981
|
+
});
|
|
4982
|
+
const result = await this.api.hold(this.key, selection);
|
|
4983
|
+
r.clearSelection();
|
|
4984
|
+
const ids = block.map((l) => this.labelToId.get(l)).filter((v) => !!v);
|
|
4985
|
+
if (ids.length) r.setStatus(ids, "held");
|
|
4986
|
+
this.setHold({ holdId: result.holdId, labels: [...block], expiresAt: result.expiresAt, items: result.items });
|
|
4987
|
+
const seats = block.map((l) => this.labelToSeat.get(l)).filter((s) => !!s).map((s) => this.toSeat(s));
|
|
4988
|
+
this.opts.onSelectionChange?.(seats);
|
|
4989
|
+
return this.hold_;
|
|
4990
|
+
} catch (err) {
|
|
4991
|
+
const { status, reason } = errInfo(err);
|
|
4992
|
+
if (status === 409 && reason === "event_closed") {
|
|
4993
|
+
this.opts.onSalesClosed?.();
|
|
4994
|
+
throw err;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4833
4999
|
if (this.hold_ && !await this.release()) return null;
|
|
4834
5000
|
try {
|
|
4835
5001
|
const result = await this.api.bestAvailable(this.key, qty, categoryKey);
|
|
@@ -5702,9 +5868,9 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
5702
5868
|
|
|
5703
5869
|
// src/i18n/bundles.ts
|
|
5704
5870
|
var LOADERS = {
|
|
5705
|
-
es: () => import("./es-
|
|
5706
|
-
de: () => import("./de-
|
|
5707
|
-
fr: () => import("./fr-
|
|
5871
|
+
es: () => import("./es-N73CIVTC.js").then((m) => ({ default: m.es })),
|
|
5872
|
+
de: () => import("./de-DRWMZ2FV.js").then((m) => ({ default: m.de })),
|
|
5873
|
+
fr: () => import("./fr-SOY2OB4P.js").then((m) => ({ default: m.fr }))
|
|
5708
5874
|
};
|
|
5709
5875
|
var loaded = /* @__PURE__ */ new Set(["en"]);
|
|
5710
5876
|
async function loadLocale(code) {
|