@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/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
- /** Server-picks `qty` best free seats and holds them atomically. Throws on failure. */
2058
- bestAvailable(qty: number, categoryKey?: string): Promise<HoldInfo | null>;
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
- /** Server-picks `qty` best free seats and holds them atomically. Throws on failure. */
2058
- bestAvailable(qty: number, categoryKey?: string): Promise<HoldInfo | null>;
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 centroid = polygonCentroid(outer);
327
- if (pointInPolygonWithHoles(centroid, outer, holes)) return centroid;
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 centroid = polygonLabelPoint(obj.outline, obj.holes);
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 ?? centroid.x,
3052
- y: obj.labelPresentation?.position?.y ?? centroid.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: centroid.x,
3068
- y: centroid.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 ?? [], centroid),
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
- /** Server-picks `qty` best free seats and holds them atomically. Throws on failure. */
4800
- async bestAvailable(qty, categoryKey) {
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-LDI3FESI.js").then((m) => ({ default: m.es })),
5665
- de: () => import("./de-HLJXYBWA.js").then((m) => ({ default: m.de })),
5666
- fr: () => import("./fr-H4KTK4F5.js").then((m) => ({ default: m.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) {