@seatlayer/core 0.21.1 → 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-HLJXYBWA.js → de-DRWMZ2FV.js} +8 -2
- package/dist/{de-HLJXYBWA.js.map → de-DRWMZ2FV.js.map} +1 -1
- package/dist/{es-LDI3FESI.js → es-N73CIVTC.js} +8 -2
- package/dist/{es-LDI3FESI.js.map → es-N73CIVTC.js.map} +1 -1
- package/dist/{fr-H4KTK4F5.js → fr-SOY2OB4P.js} +8 -2
- package/dist/{fr-H4KTK4F5.js.map → fr-SOY2OB4P.js.map} +1 -1
- package/dist/index.cjs +242 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -2
- package/dist/index.d.ts +42 -2
- package/dist/index.js +224 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1275,6 +1275,9 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1275
1275
|
private categoryHighlight;
|
|
1276
1276
|
/** Price-band filter (F4): dim free seats whose category is NOT in this set. */
|
|
1277
1277
|
private categoryFilter;
|
|
1278
|
+
/** Commercial "hide limited-view seats" toggle: dim free seats flagged
|
|
1279
|
+
* restrictedView/obstructedView. Parallel to the accessibility filter. */
|
|
1280
|
+
private commercialLimitedFilter;
|
|
1278
1281
|
private sections;
|
|
1279
1282
|
private zones;
|
|
1280
1283
|
private seatSection;
|
|
@@ -1433,6 +1436,12 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1433
1436
|
setAccessibleFilter(on: boolean): void;
|
|
1434
1437
|
setAccessibilityFilter(types: AccessibilityType[] | null): void;
|
|
1435
1438
|
private sameAccessFilter;
|
|
1439
|
+
/**
|
|
1440
|
+
* "Hide limited-view seats" toggle (commercial): when on, dim free, unselected
|
|
1441
|
+
* seats flagged restrictedView or obstructedView — the same visual dimming the
|
|
1442
|
+
* accessibility filter uses, just keyed off the seat's commercial attributes.
|
|
1443
|
+
*/
|
|
1444
|
+
setCommercialLimitedFilter(on: boolean): void;
|
|
1436
1445
|
/**
|
|
1437
1446
|
* Price-band filter (F4): dim free, unselected seats whose category key is NOT
|
|
1438
1447
|
* in `keys`. `null` clears the filter (all categories fully visible). The
|
|
@@ -1768,6 +1777,10 @@ interface PickerSeat {
|
|
|
1768
1777
|
tiers?: CategoryTier[];
|
|
1769
1778
|
/** The chosen tier's id — defaults to the first tier; absent when no tiers. */
|
|
1770
1779
|
tierId?: string;
|
|
1780
|
+
/** Commercial selling/view attributes (restricted/obstructed view, premium,
|
|
1781
|
+
* note) resolved for this seat; absent when the seat carries none. Lets the
|
|
1782
|
+
* buyer cart + confirm surface flag limited-view/premium seats. */
|
|
1783
|
+
commercial?: SeatCommercialAttributes;
|
|
1771
1784
|
}
|
|
1772
1785
|
/**
|
|
1773
1786
|
* Rich hover payload for seat tooltips — the PickerSeat plus everything a
|
|
@@ -2054,8 +2067,28 @@ declare class PickerController {
|
|
|
2054
2067
|
tierId?: string | null;
|
|
2055
2068
|
ttlMs?: number;
|
|
2056
2069
|
}): Promise<HoldInfo | null>;
|
|
2057
|
-
/**
|
|
2058
|
-
|
|
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>;
|
|
2059
2092
|
/**
|
|
2060
2093
|
* Complete a booking. Requires a transport that supports book() (the SDK
|
|
2061
2094
|
* deliberately does not). `bookingRef` MUST be a real reference from the caller.
|
|
@@ -2089,6 +2122,13 @@ declare class PickerController {
|
|
|
2089
2122
|
};
|
|
2090
2123
|
setSelectionFocus(seatId: string | null): void;
|
|
2091
2124
|
setAccessibilityFilter(types: string[] | null): void;
|
|
2125
|
+
/**
|
|
2126
|
+
* "Hide limited-view seats" toggle — dims free seats flagged
|
|
2127
|
+
* restricted/obstructed view. Additive commercial parallel to the
|
|
2128
|
+
* accessibility filter; called via the concrete renderer (not on the shared
|
|
2129
|
+
* ISeatmapRenderer interface).
|
|
2130
|
+
*/
|
|
2131
|
+
setCommercialLimitedFilter(on: boolean): void;
|
|
2092
2132
|
/** Floors for the buyer's switcher (>1 ⇒ show it). Single-floor ⇒ one entry. */
|
|
2093
2133
|
getFloors(): {
|
|
2094
2134
|
id: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1275,6 +1275,9 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1275
1275
|
private categoryHighlight;
|
|
1276
1276
|
/** Price-band filter (F4): dim free seats whose category is NOT in this set. */
|
|
1277
1277
|
private categoryFilter;
|
|
1278
|
+
/** Commercial "hide limited-view seats" toggle: dim free seats flagged
|
|
1279
|
+
* restrictedView/obstructedView. Parallel to the accessibility filter. */
|
|
1280
|
+
private commercialLimitedFilter;
|
|
1278
1281
|
private sections;
|
|
1279
1282
|
private zones;
|
|
1280
1283
|
private seatSection;
|
|
@@ -1433,6 +1436,12 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1433
1436
|
setAccessibleFilter(on: boolean): void;
|
|
1434
1437
|
setAccessibilityFilter(types: AccessibilityType[] | null): void;
|
|
1435
1438
|
private sameAccessFilter;
|
|
1439
|
+
/**
|
|
1440
|
+
* "Hide limited-view seats" toggle (commercial): when on, dim free, unselected
|
|
1441
|
+
* seats flagged restrictedView or obstructedView — the same visual dimming the
|
|
1442
|
+
* accessibility filter uses, just keyed off the seat's commercial attributes.
|
|
1443
|
+
*/
|
|
1444
|
+
setCommercialLimitedFilter(on: boolean): void;
|
|
1436
1445
|
/**
|
|
1437
1446
|
* Price-band filter (F4): dim free, unselected seats whose category key is NOT
|
|
1438
1447
|
* in `keys`. `null` clears the filter (all categories fully visible). The
|
|
@@ -1768,6 +1777,10 @@ interface PickerSeat {
|
|
|
1768
1777
|
tiers?: CategoryTier[];
|
|
1769
1778
|
/** The chosen tier's id — defaults to the first tier; absent when no tiers. */
|
|
1770
1779
|
tierId?: string;
|
|
1780
|
+
/** Commercial selling/view attributes (restricted/obstructed view, premium,
|
|
1781
|
+
* note) resolved for this seat; absent when the seat carries none. Lets the
|
|
1782
|
+
* buyer cart + confirm surface flag limited-view/premium seats. */
|
|
1783
|
+
commercial?: SeatCommercialAttributes;
|
|
1771
1784
|
}
|
|
1772
1785
|
/**
|
|
1773
1786
|
* Rich hover payload for seat tooltips — the PickerSeat plus everything a
|
|
@@ -2054,8 +2067,28 @@ declare class PickerController {
|
|
|
2054
2067
|
tierId?: string | null;
|
|
2055
2068
|
ttlMs?: number;
|
|
2056
2069
|
}): Promise<HoldInfo | null>;
|
|
2057
|
-
/**
|
|
2058
|
-
|
|
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>;
|
|
2059
2092
|
/**
|
|
2060
2093
|
* Complete a booking. Requires a transport that supports book() (the SDK
|
|
2061
2094
|
* deliberately does not). `bookingRef` MUST be a real reference from the caller.
|
|
@@ -2089,6 +2122,13 @@ declare class PickerController {
|
|
|
2089
2122
|
};
|
|
2090
2123
|
setSelectionFocus(seatId: string | null): void;
|
|
2091
2124
|
setAccessibilityFilter(types: string[] | null): void;
|
|
2125
|
+
/**
|
|
2126
|
+
* "Hide limited-view seats" toggle — dims free seats flagged
|
|
2127
|
+
* restricted/obstructed view. Additive commercial parallel to the
|
|
2128
|
+
* accessibility filter; called via the concrete renderer (not on the shared
|
|
2129
|
+
* ISeatmapRenderer interface).
|
|
2130
|
+
*/
|
|
2131
|
+
setCommercialLimitedFilter(on: boolean): void;
|
|
2092
2132
|
/** Floors for the buyer's switcher (>1 ⇒ show it). Single-floor ⇒ one entry. */
|
|
2093
2133
|
getFloors(): {
|
|
2094
2134
|
id: string;
|
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 ?? []];
|
|
@@ -806,7 +806,13 @@ var en = {
|
|
|
806
806
|
"picker.on": "ON",
|
|
807
807
|
"picker.off": "OFF",
|
|
808
808
|
"picker.panorama360": "360\xB0 venue photo",
|
|
809
|
-
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m from stage"
|
|
809
|
+
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m from stage",
|
|
810
|
+
"picker.restrictedView": "Restricted view",
|
|
811
|
+
"picker.obstructedView": "Obstructed view",
|
|
812
|
+
"picker.premiumSeat": "Premium seat",
|
|
813
|
+
"picker.hideLimitedView": "Hide limited-view seats",
|
|
814
|
+
"picker.bestSeatsPremium": "Best seats",
|
|
815
|
+
"picker.premiumFallbackNote": "No premium block of {count} \u2014 showing best overall"
|
|
810
816
|
};
|
|
811
817
|
|
|
812
818
|
// src/i18n/index.ts
|
|
@@ -1180,6 +1186,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1180
1186
|
this.categoryHighlight = null;
|
|
1181
1187
|
/** Price-band filter (F4): dim free seats whose category is NOT in this set. */
|
|
1182
1188
|
this.categoryFilter = null;
|
|
1189
|
+
/** Commercial "hide limited-view seats" toggle: dim free seats flagged
|
|
1190
|
+
* restrictedView/obstructedView. Parallel to the accessibility filter. */
|
|
1191
|
+
this.commercialLimitedFilter = false;
|
|
1183
1192
|
// Section/zone overlays (bgLayer) — the 3-rung LOD: seats → section blocks →
|
|
1184
1193
|
// zone blocks. Kept for the melt restyle and for hit-testing a zoomed-out tap.
|
|
1185
1194
|
this.sections = [];
|
|
@@ -2043,6 +2052,26 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2043
2052
|
if (cur === null || next === null) return cur === next;
|
|
2044
2053
|
return cur.length === next.length && cur.every((t2, i) => t2 === next[i]);
|
|
2045
2054
|
}
|
|
2055
|
+
/**
|
|
2056
|
+
* "Hide limited-view seats" toggle (commercial): when on, dim free, unselected
|
|
2057
|
+
* seats flagged restrictedView or obstructedView — the same visual dimming the
|
|
2058
|
+
* accessibility filter uses, just keyed off the seat's commercial attributes.
|
|
2059
|
+
*/
|
|
2060
|
+
setCommercialLimitedFilter(on) {
|
|
2061
|
+
if (this.commercialLimitedFilter === on) return;
|
|
2062
|
+
this.commercialLimitedFilter = on;
|
|
2063
|
+
for (const seat of this.seats) {
|
|
2064
|
+
const c = this.circleById.get(seat.id);
|
|
2065
|
+
if (c) this.paintSeat(c, seat.id);
|
|
2066
|
+
}
|
|
2067
|
+
this.updateLabels();
|
|
2068
|
+
if (this.cached) {
|
|
2069
|
+
this.seatLayer.clearCache();
|
|
2070
|
+
this.cacheSeatLayer();
|
|
2071
|
+
} else {
|
|
2072
|
+
this.seatLayer.batchDraw();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2046
2075
|
/**
|
|
2047
2076
|
* Price-band filter (F4): dim free, unselected seats whose category key is NOT
|
|
2048
2077
|
* in `keys`. `null` clears the filter (all categories fully visible). The
|
|
@@ -2482,6 +2511,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2482
2511
|
if (this.categoryFilter && status === "free" && !selected && !this.categoryFilter.has(seat.categoryKey)) {
|
|
2483
2512
|
c.opacity(0.22);
|
|
2484
2513
|
}
|
|
2514
|
+
if (this.commercialLimitedFilter && status === "free" && !selected && (seat.commercial?.restrictedView || seat.commercial?.obstructedView)) {
|
|
2515
|
+
c.opacity(0.22);
|
|
2516
|
+
}
|
|
2485
2517
|
if (this.dimmedSections.size) {
|
|
2486
2518
|
const sec = this.seatSection.get(id);
|
|
2487
2519
|
if (sec && (this.dimmedSections.has(sec.id) || this.dimmedSections.has(sec.logicalId) || sec.zone != null && this.dimmedSections.has(sec.zone))) {
|
|
@@ -2986,7 +3018,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2986
3018
|
* Membership and category mix are still precomputed for the detailed state.
|
|
2987
3019
|
*/
|
|
2988
3020
|
renderSection(obj) {
|
|
2989
|
-
const
|
|
3021
|
+
const centroid2 = polygonLabelPoint(obj.outline, obj.holes);
|
|
2990
3022
|
const palette = overviewPalette(this.canvasBackground);
|
|
2991
3023
|
const memberIds = [];
|
|
2992
3024
|
const catCounts = /* @__PURE__ */ new Map();
|
|
@@ -3048,8 +3080,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3048
3080
|
const preferredInk = labelStyle?.color ?? palette.sectionInk;
|
|
3049
3081
|
const labelScale = (labelStyle?.size ?? 18) / 18;
|
|
3050
3082
|
const nameLabel = new Text({
|
|
3051
|
-
x: obj.labelPresentation?.position?.x ??
|
|
3052
|
-
y: obj.labelPresentation?.position?.y ??
|
|
3083
|
+
x: obj.labelPresentation?.position?.x ?? centroid2.x,
|
|
3084
|
+
y: obj.labelPresentation?.position?.y ?? centroid2.y,
|
|
3053
3085
|
text: obj.displayLabel ?? obj.label,
|
|
3054
3086
|
rotation: obj.labelPresentation?.rotation ?? 0,
|
|
3055
3087
|
visible: obj.labelPresentation?.visible !== false,
|
|
@@ -3064,8 +3096,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3064
3096
|
nameLabel.offsetY(nameLabel.height() / 2);
|
|
3065
3097
|
bgTarget.add(nameLabel);
|
|
3066
3098
|
const subLabel = new Text({
|
|
3067
|
-
x:
|
|
3068
|
-
y:
|
|
3099
|
+
x: centroid2.x,
|
|
3100
|
+
y: centroid2.y,
|
|
3069
3101
|
text: t("map.seatsLeft", { count: free }),
|
|
3070
3102
|
fontSize: 12,
|
|
3071
3103
|
fontStyle: "700",
|
|
@@ -3084,8 +3116,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3084
3116
|
outline: obj.outline,
|
|
3085
3117
|
...obj.outlinePath ? { outlinePath: obj.outlinePath } : {},
|
|
3086
3118
|
holes: obj.holes ?? [],
|
|
3087
|
-
centroid,
|
|
3088
|
-
labelAnchors: polygonLabelCandidates(obj.outline, obj.holes ?? [],
|
|
3119
|
+
centroid: centroid2,
|
|
3120
|
+
labelAnchors: polygonLabelCandidates(obj.outline, obj.holes ?? [], centroid2),
|
|
3089
3121
|
zone: obj.zone,
|
|
3090
3122
|
memberIds,
|
|
3091
3123
|
total: memberIds.length,
|
|
@@ -4445,6 +4477,103 @@ function strandedSingles(seats, statusOf, selectedIds) {
|
|
|
4445
4477
|
return out;
|
|
4446
4478
|
}
|
|
4447
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
|
+
|
|
4448
4577
|
// src/picker/PickerController.ts
|
|
4449
4578
|
var DEFAULT_MAX_SELECTION = 10;
|
|
4450
4579
|
var MAX_BACKOFF_MS = 15e3;
|
|
@@ -4796,10 +4925,77 @@ var PickerController = class {
|
|
|
4796
4925
|
this.setHold({ holdId: result.holdId, labels: combined.map((s) => s.label), expiresAt: result.expiresAt, items: result.items });
|
|
4797
4926
|
return this.hold_;
|
|
4798
4927
|
}
|
|
4799
|
-
/**
|
|
4800
|
-
|
|
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 = {}) {
|
|
4801
4970
|
const r = this.renderer;
|
|
4802
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
|
+
}
|
|
4803
4999
|
if (this.hold_ && !await this.release()) return null;
|
|
4804
5000
|
try {
|
|
4805
5001
|
const result = await this.api.bestAvailable(this.key, qty, categoryKey);
|
|
@@ -4969,6 +5165,15 @@ var PickerController = class {
|
|
|
4969
5165
|
setAccessibilityFilter(types) {
|
|
4970
5166
|
this.renderer?.setAccessibilityFilter?.(types);
|
|
4971
5167
|
}
|
|
5168
|
+
/**
|
|
5169
|
+
* "Hide limited-view seats" toggle — dims free seats flagged
|
|
5170
|
+
* restricted/obstructed view. Additive commercial parallel to the
|
|
5171
|
+
* accessibility filter; called via the concrete renderer (not on the shared
|
|
5172
|
+
* ISeatmapRenderer interface).
|
|
5173
|
+
*/
|
|
5174
|
+
setCommercialLimitedFilter(on) {
|
|
5175
|
+
this.renderer?.setCommercialLimitedFilter?.(on);
|
|
5176
|
+
}
|
|
4972
5177
|
// ---- multi-floor (Batch 5) ------------------------------------------------
|
|
4973
5178
|
/** Floors for the buyer's switcher (>1 ⇒ show it). Single-floor ⇒ one entry. */
|
|
4974
5179
|
getFloors() {
|
|
@@ -5162,9 +5367,10 @@ var PickerController = class {
|
|
|
5162
5367
|
}
|
|
5163
5368
|
// ---- internals ------------------------------------------------------------
|
|
5164
5369
|
toSeat(s) {
|
|
5370
|
+
const commercial = s.commercial ? { commercial: s.commercial } : void 0;
|
|
5165
5371
|
const tiers = this.tiersFor(s.categoryKey);
|
|
5166
5372
|
if (!tiers) {
|
|
5167
|
-
return { id: s.id, label: s.label, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey) };
|
|
5373
|
+
return { id: s.id, label: s.label, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial };
|
|
5168
5374
|
}
|
|
5169
5375
|
const chosen = tiers.find((t2) => t2.id === this.seatTiers.get(s.id)) ?? tiers[0];
|
|
5170
5376
|
return {
|
|
@@ -5173,7 +5379,8 @@ var PickerController = class {
|
|
|
5173
5379
|
categoryKey: s.categoryKey,
|
|
5174
5380
|
price: chosen.price,
|
|
5175
5381
|
tiers,
|
|
5176
|
-
tierId: chosen.id
|
|
5382
|
+
tierId: chosen.id,
|
|
5383
|
+
...commercial
|
|
5177
5384
|
};
|
|
5178
5385
|
}
|
|
5179
5386
|
/** Tooltip payload for a hovered seat — see PickerCallbacks.onSeatHover. */
|
|
@@ -5661,9 +5868,9 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
5661
5868
|
|
|
5662
5869
|
// src/i18n/bundles.ts
|
|
5663
5870
|
var LOADERS = {
|
|
5664
|
-
es: () => import("./es-
|
|
5665
|
-
de: () => import("./de-
|
|
5666
|
-
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 }))
|
|
5667
5874
|
};
|
|
5668
5875
|
var loaded = /* @__PURE__ */ new Set(["en"]);
|
|
5669
5876
|
async function loadLocale(code) {
|