@seatlayer/js 0.77.2 → 0.79.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.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { PickerSeat, PickerGAArea, PickerMapTheme, PickerTransport, PickerSelectionValidator, RendererViewMode, PickerSelectionValidity, ChartTheme, AccessibilityType, FloorLabelStyle, LodRung, SectionSummary, PickerAccessNeed, SeatHoverDetails, AccessibleSectionStep, AvailabilityRefreshOutcome, PickerController, ExpandedSeat } from '@seatlayer/core';
1
+ import { PickerSeat, PickerGAArea, PickerMapTheme, PickerTransport, PickerSelectionValidator, RendererViewMode, PickerSelectionValidity, ChartTheme, AccessibilityType, FloorLabelStyle, LodRung, SectionSummary, PickerAccessNeed, SeatHoverDetails, AccessibleSectionStep, AvailabilityRefreshOutcome, PickerController, ExpandedSeat, TableSelectionDetails, SeatStatus } from '@seatlayer/core';
2
2
  export { ExpandedSeat, PickerMapTheme, PickerSelectionValidator, PickerSelectionValidity, PickerSelectionViolation, RendererViewMode, SeatHoverDetails } from '@seatlayer/core';
3
3
  import { T as ThemeMode } from './channelsMode-goczEzyt.js';
4
4
  export { A as AccessIntentForbidsDetails, a as AccessLinkRecord, b as AccessLinkReveal, c as AccessLinkState, d as AccessLinkStatus, e as AccessLinkStatusRecord, f as ArchiveBlockedDetails, g as AssignmentBuckets, h as AssignmentDropDetails, i as AssignmentResult, B as BucketRow, C as ChannelAccessIntent, j as ChannelAccessSummary, k as ChannelAllocationPage, l as ChannelAttribution, m as ChannelAuditEntry, n as ChannelAuditPage, o as ChannelCounts, p as ChannelListResult, q as ChannelPreviewProjection, r as ChannelRecord, s as ChannelReport, t as ChannelReportLinkRecord, u as ChannelReportLinkReveal, v as ChannelReportResult, w as ChannelReportRow, x as ChannelSeatStatus, y as ChannelState, z as ChannelsCapabilities, D as ChannelsClient, E as ChannelsMode, F as ChannelsModeHost, G as ChannelsRowView, H as ChannelsSeatView, I as ControlRoomActivityEntry, J as ControlRoomSectionMetric, K as ControlRoomSnapshot, L as EventCategoryAssignmentResult, M as EventScopedManageToken, N as EventTableBookingMode, O as EventTableBookingResult, P as IntentSwitchBlockedDetails, Q as InventoryBooking, R as InventoryBookingActivity, S as InventoryBookingDetail, U as InventoryBookingObject, V as InventoryBookingState, W as InventoryBookingsPage, X as InventoryBookingsQuery, Y as LogEntry, Z as LogPage, _ as ManageApi, $ as ManageApiError, a0 as ReportByStatus, a1 as ReportCategoryMeta, a2 as ReportCategoryRow, a3 as ReportResult, a4 as SeatManager, a5 as SeatManagerActionResult, a6 as SeatManagerActivity, a7 as SeatManagerCapability, a8 as SeatManagerConnection, a9 as SeatManagerFilteredSection, aa as SeatManagerMode, ab as SeatManagerOptions, ac as SeatManagerSelectionValidity, ad as SeatManagerTallies, ae as SelectionSourceRow } from './channelsMode-goczEzyt.js';
5
5
  import * as _seatlayer_core_core_seatConfidence from '@seatlayer/core/core/seatConfidence';
6
6
  import { SeatConfidenceDisclosure } from '@seatlayer/core/core/seatConfidence';
7
- import { Venue3DHandle } from '@seatlayer/core/view3d';
7
+ import { SeatViewStopName, Venue3DHandle, SeatState3D, SeatView } from '@seatlayer/core/view3d';
8
8
 
9
9
  /**
10
10
  * Buyer access context — the browser half of the Sales Channels contract
@@ -267,6 +267,19 @@ interface StatusChange {
267
267
  label: string;
268
268
  status: WireStatus;
269
269
  }
270
+ /**
271
+ * What else the frame that carried a section change was carrying.
272
+ *
273
+ * The one thing a section listener needs to decide is whether it has to go and
274
+ * read the map. A connect snapshot names the hidden and closed sections AND
275
+ * every unit's status in the same frame; a standalone `type:'hidden'` frame
276
+ * names only the sections. Treating the two alike cost every fresh connection
277
+ * one redundant HTTP inventory read.
278
+ */
279
+ interface SectionsMeta {
280
+ /** This frame also carried the seat statuses, so no read is owed. */
281
+ withInventory: boolean;
282
+ }
270
283
  /** Where reconstructed inventory goes. Implemented over PickerController. */
271
284
  interface RealtimeSink {
272
285
  /** Apply a batch of label→status changes. Implementations must paint this as
@@ -287,9 +300,15 @@ interface RealtimeSink {
287
300
  * cannot be diffed against what we hold (first snapshot, or the scope's
288
301
  * default itself changed, which redefines every unit we were never told
289
302
  * about), and the sink cannot take a whole projection. */
290
- resync(): void | Promise<void>;
291
- /** Section availability changed (channel-agnostic; identical for every scope). */
292
- onSections?(hidden: string[], closed: string[]): void;
303
+ resync(opts?: {
304
+ reason?: 'connect' | 'frame';
305
+ }): void | Promise<void>;
306
+ /** Section availability changed (channel-agnostic; identical for every scope).
307
+ *
308
+ * `withInventory` says whether the SAME frame also carried the seat
309
+ * statuses — which is the difference between "go and read the map" and
310
+ * "the map is right here". See {@link SectionsMeta}. */
311
+ onSections?(hidden: string[], closed: string[], meta?: SectionsMeta): void;
293
312
  /** Scope-projected presence counters. */
294
313
  onPresence?(counts: {
295
314
  shoppingSessions: number;
@@ -395,15 +414,49 @@ interface PickerControllerLike {
395
414
  label: string;
396
415
  }>;
397
416
  deselect(ids: string[]): void;
417
+ /** FORCED re-read. Reserved for "I just changed something, tell me the truth". */
398
418
  refresh(): Promise<void>;
419
+ /**
420
+ * COALESCED re-read, and the one a socket should use.
421
+ *
422
+ * `refresh()` was this sink's only option, and it is the forced call: every
423
+ * connect and every section frame bypassed the controller's snapshot
424
+ * coalescer and issued its own `/objects?compact=1`. On the measured DesiPass
425
+ * open that was two redundant full snapshots landing ~1.5 s after the map was
426
+ * already painted from the bootstrap bundle.
427
+ *
428
+ * Optional so a host pinning an older `@seatlayer/core` still works — that
429
+ * engine simply keeps the forced behaviour it always had.
430
+ */
431
+ resync?(opts?: {
432
+ reason?: 'connect' | 'frame';
433
+ }): Promise<void>;
434
+ /**
435
+ * Paint a whole authoritative projection with no round trip at all.
436
+ *
437
+ * Optional for the same reason: where the engine can take one, the socket's
438
+ * own connect frame IS the snapshot and the HTTP resync never happens.
439
+ */
440
+ applyProjection?(projection: {
441
+ default: string;
442
+ exceptions: Record<string, string>;
443
+ }): void;
399
444
  }
400
445
  interface ControllerSinkOptions {
401
446
  /** Pulse seats other buyers take, as the controller's own socket does. */
402
447
  flashOnLiveChange?: boolean;
403
448
  /** Selected-but-unheld units that stopped being selectable. */
404
449
  onSelectedObjectUnavailable?: (labels: string[], reason: 'ineligible' | 'taken') => void;
450
+ /**
451
+ * Labels the buyer is holding RIGHT NOW — the hold request is in flight and
452
+ * `currentHold()` is still null. The server broadcasts `held` before the
453
+ * hold response lands, so without this the buyer's own echo reads as
454
+ * "someone else took your seat", the selection is evicted, and the winning
455
+ * hold is left with nobody attached to it.
456
+ */
457
+ ownLabels?: () => Iterable<string>;
405
458
  /** Section availability changed and the chart itself needs rebuilding. */
406
- onSections?: (hidden: string[], closed: string[]) => void;
459
+ onSections?: (hidden: string[], closed: string[], meta?: SectionsMeta) => void;
407
460
  onStatusChange?: () => void;
408
461
  }
409
462
  declare function createControllerSink(controller: PickerControllerLike, options?: ControllerSinkOptions): RealtimeSink;
@@ -1815,6 +1868,8 @@ declare class SeatingChart {
1815
1868
  private readonly buyerAssetUrls;
1816
1869
  private readonly immersive;
1817
1870
  private realtime;
1871
+ /** Labels whose hold request is in flight — see ControllerSinkOptions.ownLabels. */
1872
+ private holdingLabels;
1818
1873
  constructor(options: SeatingChartOptions);
1819
1874
  /**
1820
1875
  * Camera x/y/scale is renderer-local and deliberately absent from the native
@@ -2535,6 +2590,370 @@ declare class EmbeddedDesigner {
2535
2590
  private handleMessage;
2536
2591
  }
2537
2592
 
2593
+ /**
2594
+ * pickerLegend — the two places the picker explains its colours.
2595
+ *
2596
+ * The price LIST (`.sl-price-row`) is the detail view: colour, name, how many
2597
+ * are left, the offer name, the exact price or spread. It lives in the side
2598
+ * panel on desktop and behind a disclosure inside the bottom sheet on a phone.
2599
+ *
2600
+ * The price RAIL (`.sl-rc`) is the phone's always-visible legend: colour and
2601
+ * price, nothing else, pinned above the map. It exists because on a narrow
2602
+ * layout the list is two gates deep — open the sheet, then open the disclosure
2603
+ * — so a buyer looking at five coloured sections had nothing on screen telling
2604
+ * them what any of them cost.
2605
+ *
2606
+ * They are deliberately NOT the same rendering. A 34px chip cannot carry "N
2607
+ * left" or an offer name, and a rail that truncated its categories would be
2608
+ * worse than one that scrolls. What they must never do is disagree, so both are
2609
+ * painted from the same `sellable`/`left` pass in `syncPrices`, and both take
2610
+ * their dot colour from `colorOf` — which asks the RENDERER, not the document,
2611
+ * because colorblind-safe mode remaps hues inside the renderer and never
2612
+ * touches `doc.categories[].color`.
2613
+ *
2614
+ * Pure: these take values and return strings. Event wiring stays with the
2615
+ * widget, in the same style as pickerTray.ts.
2616
+ */
2617
+
2618
+ /** The fields either legend needs off a category. */
2619
+ interface LegendCategory {
2620
+ key: string;
2621
+ label: string;
2622
+ color: string;
2623
+ price?: number;
2624
+ tiers?: {
2625
+ id?: string;
2626
+ price: number;
2627
+ }[];
2628
+ }
2629
+ /** An offer as the widget resolves it. `previousPrice` is nullable, not just
2630
+ * optional — older workers omit it and current ones send null when there is
2631
+ * no strike-through price. */
2632
+ interface LegendOffer {
2633
+ previousPrice?: number | null;
2634
+ price: number;
2635
+ offerName?: string;
2636
+ }
2637
+ /**
2638
+ * The half of a legend's inputs both views share. Held as one object so the
2639
+ * widget resolves them once and the two renderings cannot be handed different
2640
+ * answers to the same question.
2641
+ */
2642
+ interface LegendDeps {
2643
+ /** On-screen colour — renderer first, document as fallback. */
2644
+ colorOf(category: LegendCategory): string;
2645
+ /** Min/max across tiers, or undefined when the chart carries no price. */
2646
+ rangeOf(category: LegendCategory): {
2647
+ min: number;
2648
+ max: number;
2649
+ } | undefined;
2650
+ /** Single price when there are no tiers. */
2651
+ priceOf(category: LegendCategory): number | undefined;
2652
+ offerOf(key: string): LegendOffer | null | undefined;
2653
+ money(amount: number): string;
2654
+ esc(value: string): string;
2655
+ }
2656
+
2657
+ type BuyerAssetLoader = (eventKey: string, asset: string) => Promise<Blob>;
2658
+ /**
2659
+ * One picker-lifetime cache. Reusing a blob URL avoids downloading an 8K
2660
+ * panorama again when a buyer opens the same row/venue view from another seat.
2661
+ */
2662
+ declare class BuyerAssetObjectUrls {
2663
+ private readonly eventKey;
2664
+ private readonly load?;
2665
+ private readonly pending;
2666
+ private readonly created;
2667
+ private disposed;
2668
+ constructor(eventKey: string, load?: BuyerAssetLoader | undefined);
2669
+ /**
2670
+ * External organizer/CDN URLs pass through unchanged. SeatLayer event assets
2671
+ * never do: they require the transport, and a reference for another Event is
2672
+ * refused instead of being loaded anonymously.
2673
+ */
2674
+ resolve(reference: string): Promise<string | null>;
2675
+ dispose(): void;
2676
+ }
2677
+
2678
+ /**
2679
+ * One performance of the group, already formatted by the wrapper.
2680
+ *
2681
+ * The wrapper owns the date formatting (it has the group's timezone and the
2682
+ * buyer's locale), so the picker never re-derives a date and the night strip in
2683
+ * the tooltip can never disagree with the dates list in the group chrome.
2684
+ */
2685
+ interface PerformanceGroupNight {
2686
+ eventKey: string;
2687
+ name: string;
2688
+ /** Short, locale-aware date label for a pill — e.g. "Sat 23 Aug". */
2689
+ shortDate: string;
2690
+ }
2691
+ /**
2692
+ * A structural mirror of the engine's `SeatMark`.
2693
+ *
2694
+ * The engine type is not on the package barrel, and this seam must not force a
2695
+ * deep import into `@seatlayer/core/core/contracts/renderer` (which is not an
2696
+ * exported subpath). The shapes are identical, so `setSeatMarks` accepts this.
2697
+ */
2698
+ interface PartialSeatMark {
2699
+ kind: 'partial';
2700
+ fraction?: number;
2701
+ data?: unknown;
2702
+ }
2703
+ interface PerformanceGroupPickerInternal {
2704
+ /** Distinguishes dynamic Performance Groups from fixed Season packages. */
2705
+ surface?: 'performance_group' | 'season';
2706
+ performanceCount: number;
2707
+ selectionMode?: 'same_seat' | 'per_performance';
2708
+ submissionState?: () => {
2709
+ complete: number;
2710
+ total: number;
2711
+ ready: boolean;
2712
+ };
2713
+ /**
2714
+ * Chronological performances. Present for both modes: the night strip needs
2715
+ * every night, in order, whichever mode the buyer is in.
2716
+ */
2717
+ nights?: () => PerformanceGroupNight[];
2718
+ /**
2719
+ * PULL, not push. The widget asks for the current marks straight after it has
2720
+ * applied a status batch, so the two always land on the same tick and in the
2721
+ * only order that paints correctly (status first, mark second). A pushed mark
2722
+ * would race the availability read that produced it.
2723
+ *
2724
+ * Returns the full desired mark set PLUS an explicit `null` for every label
2725
+ * that has left `partial` since the last pull — `setSeatMarks` merges, so a
2726
+ * dropped label would otherwise keep its two-tone paint forever.
2727
+ */
2728
+ seatMarks?: () => Record<string, PartialSeatMark | null>;
2729
+ /**
2730
+ * Which performance the tray's seat cards belong to, already formatted —
2731
+ * "Matinée · Sun, Aug 30", or just the date when the performance is unnamed.
2732
+ *
2733
+ * Per-performance mode shows ONE night's chart and therefore one night's
2734
+ * seats, and a card that says only "Section Arena B · Row A · Seat 3" under a
2735
+ * four-row "Your seats by night" list reads as if the other three nights were
2736
+ * dropped. Null in same-seat mode, where every card is every night and the
2737
+ * eyebrow would be noise.
2738
+ */
2739
+ activePerformanceLabel?: () => string | null;
2740
+ /**
2741
+ * Exact dates represented by a same-seat card after a flexible group was
2742
+ * narrowed. Null for an all-dates package and outside Performance Groups.
2743
+ */
2744
+ selectedPerformanceDates?: () => {
2745
+ summary: string;
2746
+ labels: string[];
2747
+ } | null;
2748
+ /**
2749
+ * Switch the map to one night. The wrapper routes this into the SAME path a
2750
+ * date tab takes, so the guards (a live hold, an undecided operation, the
2751
+ * night already being active) are enforced once, in one place.
2752
+ */
2753
+ selectNight?: (eventKey: string) => void;
2754
+ /**
2755
+ * Per-night summary rows for the "Your seats" pane (per-performance only).
2756
+ * `active` marks the night the map is currently showing.
2757
+ */
2758
+ nightSummary?: () => Array<{
2759
+ eventKey: string;
2760
+ name: string;
2761
+ labels: string[];
2762
+ active?: boolean;
2763
+ }>;
2764
+ /**
2765
+ * What the buyer has chosen across the WHOLE run.
2766
+ *
2767
+ * Every count the picker computes for itself — the tray header, the footer,
2768
+ * the booked overlay — is a count of the chart currently on screen, and for a
2769
+ * group that chart is one performance out of several. "1 selected" over a
2770
+ * four-night package is not a small imprecision; it is the wrong number, and
2771
+ * it is the number the buyer checks before paying. Only the wrapper knows the
2772
+ * other nights, so only the wrapper can answer this.
2773
+ *
2774
+ * `seats` is the run TOTAL (tickets), `perPerformance` is the per-night count
2775
+ * when every chosen night has the same one and null when they differ, and
2776
+ * `performancesChosen` is how many nights have any seat at all. Null (or
2777
+ * absent) outside a group, where the picker's own counts are correct.
2778
+ */
2779
+ groupSelectionSummary?: () => {
2780
+ seats: number;
2781
+ performances: number;
2782
+ performancesChosen: number;
2783
+ perPerformance: number | null;
2784
+ } | null;
2785
+ /**
2786
+ * The quiet way out of same-seat selection, when the host opted in with
2787
+ * `allowSelectionModeSwitch`. Null (or absent) means the buyer is told to
2788
+ * pick a seat that is free every night instead — never a dead end.
2789
+ */
2790
+ selectionModeSwitch?: {
2791
+ label: string;
2792
+ activate: () => void;
2793
+ } | null;
2794
+ /** Select one partly-available seat for exactly the dates where it is free. */
2795
+ partialSeatSelection?: {
2796
+ label: (availableCount: number) => string;
2797
+ /** Use the renderer's stable id so reselect targets the exact expanded seat. */
2798
+ activate: (seatId: string, availableEventKeys: string[]) => void;
2799
+ } | null;
2800
+ /** Group-only date controls inside the ordinary seat confirmation card. */
2801
+ confirmDateSelection?: {
2802
+ includedEventKeys: () => string[];
2803
+ availableEventKeys: (seatId: string) => string[];
2804
+ label: (selectedCount: number) => string;
2805
+ apply: (seatId: string, eventKeys: string[]) => Promise<boolean>;
2806
+ } | null;
2807
+ /**
2808
+ * Suppress every per-performance money surface (L7).
2809
+ *
2810
+ * A Performance Group is sold as ONE package: a per-performance subtotal, a
2811
+ * tier picker, or a chip amount is a number the buyer cannot act on and
2812
+ * cannot reconcile with what they will be charged. This used to be a
2813
+ * `display:none` sheet in the wrapper's stylesheet, which left the prices in
2814
+ * the DOM for screen readers, copy-paste and the accessibility tree.
2815
+ */
2816
+ suppressPricing?: boolean;
2817
+ }
2818
+
2819
+ /**
2820
+ * pickerConfirmCard — the card that stands between a tapped seat and the cart.
2821
+ *
2822
+ * It is one seat's whole story in one anchored popover: who the seat is (the
2823
+ * identity grid), what it costs (the category band), what can be seen from it
2824
+ * (the view thumbnail and the sightline line), and the two buttons that decide
2825
+ * the matter. Raising it, placing it against the seat, wiring its six controls,
2826
+ * committing it and taking it down are all here.
2827
+ *
2828
+ * Extracted from SeatPicker.ts (2026-09-03) so the widget has room to grow
2829
+ * under the app's file-size ratchet, which pins the vendored mirror. Bodies are
2830
+ * verbatim; the only edits are mechanical — the card's own two pieces of state
2831
+ * (`el`, `seat`) live on this class, and every other value the code read off
2832
+ * `this` now comes through {@link ConfirmCardHost}.
2833
+ *
2834
+ * THE HOST IS DELIBERATELY LATE-BOUND. Every member of it is a function the
2835
+ * widget implements by calling its own method, never a captured value: the
2836
+ * picker's tests replace `dismissConfirm`, `reducedMotion`, `scheduleMotion`,
2837
+ * `syncTray`, `canOffer3d`, `enter3d` and `buyerAssetUrls` on the instance, and
2838
+ * a card holding a bound reference from construction time would quietly ignore
2839
+ * all of them. That seam is the same one `SectionCard` keeps for
2840
+ * `sectionNeighbours`.
2841
+ */
2842
+
2843
+ /**
2844
+ * The two questions this one card asks.
2845
+ *
2846
+ * `add` is the original: a seat the buyer has tapped but not yet taken.
2847
+ * `remove` is the same card about a seat already in their cart, raised by a
2848
+ * SECOND tap on it — which used to drop the seat silently, with no card and no
2849
+ * notice (client report, 2026-09-04). Same layout, same identity, same price;
2850
+ * only the primary button changes, and Cancel keeps the seat rather than
2851
+ * dropping it.
2852
+ *
2853
+ * Deliberately not a separate card. A buyer taps a seat and expects the thing
2854
+ * that seat's tap always produces; two popovers for one gesture is a second
2855
+ * thing to learn for no gain.
2856
+ */
2857
+ type ConfirmCardMode = 'add' | 'remove';
2858
+
2859
+ /**
2860
+ * pickerMinimap — the F3 venue-overview minimap: build, layout, static paint,
2861
+ * viewport rectangle, and the pointer/keyboard interactions that pan or focus
2862
+ * from it.
2863
+ *
2864
+ * Lifted out of SeatPicker.ts with the method bodies verbatim — statement for
2865
+ * statement, including canvas call order, which matters here because a 2D
2866
+ * context is a state machine and jsdom cannot exercise it. The ONLY edits are
2867
+ * the indirections through {@link MinimapHost}: `this.controller` →
2868
+ * `this.host.controller`, `this.cssVar` → `this.host.cssVar`, `this.tf` →
2869
+ * the module-level `tfStatic` it always delegated to, and the widget root and
2870
+ * focused-section label behind accessors (SeatPicker replaces both across a
2871
+ * re-mount, so they must be read late, not captured).
2872
+ *
2873
+ * `build()` also takes its mount element as an argument rather than reading
2874
+ * SeatPicker's region map. Everything else — the two-canvas base/blit split,
2875
+ * the drag-capture bookkeeping, the click suppression after a drag — is
2876
+ * unchanged.
2877
+ */
2878
+
2879
+ /** What the minimap needs from the widget around it. */
2880
+ interface MinimapHost {
2881
+ readonly controller: PickerController;
2882
+ /** The widget root — it carries `data-minimap-open`. A function, not a
2883
+ * field: SeatPicker rebuilds its root, and a captured one goes stale. */
2884
+ root(): HTMLElement | null;
2885
+ /** Resolved `--sl-*` token value (canvas needs a real colour, not `var()`). */
2886
+ cssVar(name: string): string;
2887
+ /** Label of the last surfaced section card. Backs the minimap caption when
2888
+ * the renderer snapshot reports no active section. */
2889
+ focusedSectionLabel(): string | undefined;
2890
+ }
2891
+ declare class PickerMinimap {
2892
+ private readonly host;
2893
+ private miniCanvas;
2894
+ private miniBase;
2895
+ private miniWrap;
2896
+ private miniToggle;
2897
+ private miniLabel;
2898
+ private miniTf;
2899
+ private miniDrag;
2900
+ private miniSuppressClick;
2901
+ /** The buyer dismissed the overview by hand; zoom must not undo that. */
2902
+ private userClosed;
2903
+ constructor(host: MinimapHost);
2904
+ /**
2905
+ * Take the overview off the page entirely.
2906
+ *
2907
+ * The phone does not get a minimap at all any more, and a picker can cross
2908
+ * that breakpoint by rotating. Hiding it with CSS would leave a canvas the
2909
+ * widget keeps redrawing on every pan of a map it is not on screen for, plus
2910
+ * a toggle button in the tab order for a panel nobody can open. So the
2911
+ * instance leaves, and `SeatPicker.minimap` goes back to null.
2912
+ */
2913
+ dispose(): void;
2914
+ /**
2915
+ * Build the overview minimap: a static venue thumbnail (section outlines, or
2916
+ * seat dots when the chart has no sections) with the live viewport rectangle
2917
+ * drawn on top. The rect tracks pan/zoom via the constructor's onViewChange.
2918
+ */
2919
+ build(mount: HTMLElement): void;
2920
+ /**
2921
+ * Reveal or retire the overview from ZOOM rather than from a tap.
2922
+ *
2923
+ * A buyer deep in the seats has lost every label; a buyer at the overview is
2924
+ * looking at the venue already and does not need a second, smaller copy of
2925
+ * it. The toggle button stays exactly as it was — this only decides the
2926
+ * default, and an explicit close still wins until they zoom back out, which
2927
+ * is also where that decision is forgotten.
2928
+ */
2929
+ setAutoOpen(deep: boolean): void;
2930
+ /**
2931
+ * `moveFocus` is false for the zoom-driven reveal. Following focus into a
2932
+ * panel is right when the buyer PRESSED something — they asked to be there.
2933
+ * It is wrong when the panel opened by itself: the buyer is mid-pinch on the
2934
+ * map, and taking the caret to a canvas they never asked for interrupts the
2935
+ * gesture and strands a keyboard user somewhere they did not navigate to.
2936
+ */
2937
+ private setMinimapOpen;
2938
+ /** Re-fit the thumbnail whenever the active floor or complete overview
2939
+ * changes bounds. This is presentation-only; the source chart is untouched. */
2940
+ private layoutMinimap;
2941
+ private syncMinimapLabel;
2942
+ /** Repaint the static overview + rect (floor switch, live open/close). */
2943
+ refreshMinimap(): void;
2944
+ /** Paint the venue overview into the offscreen base canvas. */
2945
+ private drawMinimapStatic;
2946
+ /** Blit the base overview, then stroke the current viewport rectangle on top. */
2947
+ drawMinimapRect(): void;
2948
+ private minimapWorldPoint;
2949
+ private minimapPointerDown;
2950
+ private minimapPointerMove;
2951
+ private minimapPointerUp;
2952
+ private minimapKeydown;
2953
+ /** Minimap click → focus an available section, otherwise pan there. */
2954
+ private minimapJump;
2955
+ }
2956
+
2538
2957
  /** What the saved-seat comparison chip needs from the widget around it. */
2539
2958
  interface CompareChipHost {
2540
2959
  /** The live 3D overlay the chip mounts into, or null when 3D is down. */
@@ -2563,6 +2982,12 @@ declare class View3dCompareChip {
2563
2982
  mainButton(): HTMLElement | null;
2564
2983
  }
2565
2984
 
2985
+ interface SeatViewChrome {
2986
+ /** Called with every stop change, and with null when the seat view ends. */
2987
+ onStopChange(stop: SeatViewStopName | null): void;
2988
+ dispose(): void;
2989
+ }
2990
+
2566
2991
  /**
2567
2992
  * The cart's arithmetic, in ONE place.
2568
2993
  *
@@ -2705,14 +3130,14 @@ declare class SeatPicker implements GaPromptPicker {
2705
3130
  readonly opts: SeatPickerOptions;
2706
3131
  /** @internal */ readonly api: PickerTransport;
2707
3132
  /** Authenticated view media, cached only for this picker lifetime. */
2708
- private readonly buyerAssetUrls;
3133
+ /** @internal */ readonly buyerAssetUrls: BuyerAssetObjectUrls;
2709
3134
  /** Our own public client, or null when the host injected a transport. */
2710
3135
  private readonly pubApi;
2711
3136
  /** Null for the ordinary public picker — the tokenless path is untouched. */
2712
3137
  private readonly access;
2713
3138
  private realtime;
2714
3139
  private accessEl;
2715
- private readonly apiBase;
3140
+ /** @internal */ readonly apiBase: string;
2716
3141
  /** @internal */ readonly controller: PickerController;
2717
3142
  /** @internal */ maxTickets: number;
2718
3143
  /** Exact ticket count required before checkout; null keeps ordinary 1..max behavior. */
@@ -2720,9 +3145,9 @@ declare class SeatPicker implements GaPromptPicker {
2720
3145
  private lastSelectionValidity;
2721
3146
  /** Original host pricing, kept separate from live server offer overrides. */
2722
3147
  private readonly hostPricing;
2723
- private readonly performanceGroup;
3148
+ /** @internal */ readonly performanceGroup: PerformanceGroupPickerInternal | null;
2724
3149
  /** Structural (not CSS) removal of per-performance money — see pickerInternal. */
2725
- private readonly suppressPricing;
3150
+ /** @internal */ readonly suppressPricing: boolean;
2726
3151
  /** @internal */ root: HTMLDivElement | null;
2727
3152
  private mapHost;
2728
3153
  private rendered;
@@ -2736,10 +3161,14 @@ declare class SeatPicker implements GaPromptPicker {
2736
3161
  private localeGeneration;
2737
3162
  /** @internal */ els: Record<string, HTMLElement>;
2738
3163
  /** Feature 6 anchor regions — positioned flex containers over the map. */
2739
- private regions;
3164
+ /** @internal */ regions: Record<string, HTMLElement>;
2740
3165
  private ro;
2741
- private holdTimer;
3166
+ /** @internal */ holdTimer: ReturnType<typeof setInterval> | null;
2742
3167
  private toastTimer;
3168
+ /** The availability read every other caller joins. See refreshOfferAvailability. */
3169
+ private offerReadInFlight;
3170
+ /** When the last SUCCESSFUL availability read landed (0 = never). */
3171
+ private offerReadSettledAt;
2743
3172
  private offerRefreshTimer;
2744
3173
  /** Armed only when the offer schedule has a known future transition (or as a
2745
3174
  * bounded retry after a failed read) — never a fixed-cadence poll. */
@@ -2757,11 +3186,11 @@ declare class SeatPicker implements GaPromptPicker {
2757
3186
  /** @internal */ readonly pricesByChannel: Map<string, ChannelPriceOverride[]>;
2758
3187
  /** @internal */ hold: HoldResult | null;
2759
3188
  /** Latest server expiry for the open hold (moves on extend). */
2760
- private holdExpiresAt;
3189
+ /** @internal */ holdExpiresAt: number;
2761
3190
  /** True once we handed off to checkout — arms booked-confirmation detection. */
2762
- private handedOff;
3191
+ /** @internal */ handedOff: boolean;
2763
3192
  /** Guards single onBooked + single success overlay per hold. */
2764
- private bookedShown;
3193
+ /** @internal */ bookedShown: boolean;
2765
3194
  /**
2766
3195
  * The last hold attempt lost its seats.
2767
3196
  *
@@ -2788,8 +3217,12 @@ declare class SeatPicker implements GaPromptPicker {
2788
3217
  private paymentOptions;
2789
3218
  /** The mounted payment card, while one is up. */
2790
3219
  private checkoutPanel;
2791
- private extendEl;
3220
+ /** @internal */ extendEl: HTMLDivElement | null;
2792
3221
  private bookedEl;
3222
+ /** The blocking "your seats were released" dialog, while it is up. */
3223
+ private holdExpiredEl;
3224
+ /** What that hold actually held, captured before the state was cleared. */
3225
+ private holdExpiredUnits;
2793
3226
  /** @internal Pending GA units per (area, tier) — see pickerGaPrompt.ts. */
2794
3227
  gaQty: GaQtyMap;
2795
3228
  /** @internal The open quantity prompt (popover or sheet), while one is up. */
@@ -2820,14 +3253,14 @@ declare class SeatPicker implements GaPromptPicker {
2820
3253
  private seatsRevealed;
2821
3254
  private seatsRevealedReport;
2822
3255
  /** The seat the confirm card is about — the same facade as `confirmEl`. */
2823
- private get confirmSeat();
2824
- private set confirmSeat(value);
3256
+ /** @internal */ get confirmSeat(): ExpandedSeat | null;
3257
+ /** @internal */ set confirmSeat(seat: ExpandedSeat | null);
2825
3258
  /** The confirm card owns its element and its candidate seat. */
2826
3259
  private confirmCardCache;
2827
3260
  private suppressConfirmationSeatId;
2828
3261
  private tableDialogEl;
2829
- private tableDialog;
2830
- private tableDialogHeld;
3262
+ /** @internal */ tableDialog: TableSelectionDetails | null;
3263
+ /** @internal */ tableDialogHeld: boolean;
2831
3264
  private tableDialogReturnFocus;
2832
3265
  /** @internal */ srEl: HTMLDivElement | null;
2833
3266
  private baQty;
@@ -2856,24 +3289,24 @@ declare class SeatPicker implements GaPromptPicker {
2856
3289
  private cbSafe;
2857
3290
  private rungsEl;
2858
3291
  private projectionEl;
2859
- private buyerView;
3292
+ /** @internal */ buyerView: 'map' | 'venue3d';
2860
3293
  /** @internal */ view3dEl: HTMLDivElement | null;
2861
3294
  /** @internal */ view3dHandle: Venue3DHandle | null;
2862
3295
  /** Monotonic token so a stale async mount (buyer left before OGL finished
2863
3296
  * loading) never installs its handle over a newer state. */
2864
- private view3dGen;
3297
+ /** @internal */ view3dGen: number;
2865
3298
  /** Seat whose 2D confirm card launched "See it in 3D"; re-shown on return. */
2866
- private view3dReturnSeat;
3299
+ /** @internal */ view3dReturnSeat: ExpandedSeat | null;
2867
3300
  /** Current premium 3D journey depth. `null` is the venue; a seat id is the
2868
3301
  * fixed seat-eye state. It lets Back unwind one step before leaving 3D. */
2869
- private view3dTargetSeatId;
3302
+ /** @internal */ view3dTargetSeatId: string | null;
2870
3303
  /** Inspection-only comparison. These ids never represent cart selection. */
2871
3304
  /** @internal */ view3dCompareSeatIds: string[];
2872
3305
  /** The saved-seat comparison chip — owned by pickerView3dNav.ts. */
2873
3306
  /** @internal */ readonly view3dCompareChip: View3dCompareChip;
2874
3307
  /** @internal */ view3dCompareEl: HTMLDivElement | null;
2875
3308
  /** @internal */ view3dCompareCleanup: (() => void) | null;
2876
- private view3dSeatViewChrome;
3309
+ /** @internal */ view3dSeatViewChrome: SeatViewChrome | null;
2877
3310
  /** @internal */ view3dPassportEl: HTMLDivElement | null;
2878
3311
  /** @internal */ view3dPassportCleanup: (() => void) | null;
2879
3312
  private floorsEl;
@@ -2885,45 +3318,45 @@ declare class SeatPicker implements GaPromptPicker {
2885
3318
  /** The 3D "Back to map"/"Back to venue" control while 3D is on. It lives in
2886
3319
  * the top-left chrome region rather than in the 3D overlay, so it outlives
2887
3320
  * the overlay's own teardown and must be removed explicitly. */
2888
- private view3dBack;
3321
+ /** @internal */ view3dBack: HTMLButtonElement | null;
2889
3322
  /** The levels/areas rail while 3D is on. Region chrome for the same reason
2890
3323
  * the back control is (it shared twelve pixels with the ♿ menu), so like
2891
3324
  * the back control it outlives the overlay and is removed by hand. */
2892
- private view3dNav;
3325
+ /** @internal */ view3dNav: HTMLElement | null;
2893
3326
  /** Which chips the price rail last drew, so a repaint that changes them can
2894
3327
  * send the scroller back to the start without stealing a buyer's own scroll. */
2895
- private lastRailSignature;
3328
+ /** @internal */ lastRailSignature: string;
2896
3329
  /** The panel's price-band <select>, so the rail's reset chip can keep it in
2897
3330
  * step. Null when the chart has fewer than two bands and none was built. */
2898
- private priceSelect;
2899
- private minimap;
3331
+ /** @internal */ priceSelect: HTMLSelectElement | null;
3332
+ /** @internal */ minimap: PickerMinimap | null;
2900
3333
  private pickerLayoutSize;
2901
- private priceBandKeys;
2902
- private focusedCatKey;
3334
+ /** @internal */ priceBandKeys: Set<string> | null;
3335
+ /** @internal */ focusedCatKey: string | null;
2903
3336
  /** "Hide limited-view seats" — mirrored into 3D by `seatState3dFor`. */
2904
- private limitedViewFilter;
2905
- private pricesExpanded;
3337
+ /** @internal */ limitedViewFilter: boolean;
3338
+ /** @internal */ pricesExpanded: boolean;
2906
3339
  /** The section card owns its element, collapse state and shown-at clock. */
2907
3340
  private sectionCardCache;
2908
3341
  /** Last surfaced section summary (re-rendered when the price band changes). */
2909
3342
  /** The tapped section the card is holding. A facade over the card in both
2910
3343
  * directions: reading it must not build chrome, and writing it must not
2911
3344
  * repaint — see SectionCard.adopt. */
2912
- private get lastSection();
2913
- private set lastSection(value);
3345
+ /** @internal */ get lastSection(): SectionSummary | null;
3346
+ /** @internal */ set lastSection(summary: SectionSummary | null);
2914
3347
  /** Previous tray ticket count — first 0→n transition auto-expands the mobile sheet. */
2915
- private lastTrayCount;
3348
+ /** @internal */ lastTrayCount: number;
2916
3349
  /** The opt-in language switcher, when `languages` named at least two. */
2917
3350
  private langSel;
2918
3351
  /** Previous computed total — drives a single explanatory value bump. */
2919
- private lastTrayTotal;
3352
+ /** @internal */ lastTrayTotal: number;
2920
3353
  /** Stable item keys prevent tray chips re-animating on unrelated realtime syncs. */
2921
3354
  private lastTrayKeys;
2922
3355
  private bestAvailableBusy;
2923
3356
  private releasingLabels;
2924
3357
  /** Selected labels awaiting the hold response; their own realtime echo can arrive first. */
2925
3358
  private holdingLabels;
2926
- private ctaPhase;
3359
+ /** @internal */ ctaPhase: 'idle' | 'holding' | 'checkout';
2927
3360
  /** The ♿ control and its menu — one node, in the map's bottom-left corner. */
2928
3361
  private a11yMenu;
2929
3362
  private detachAttention;
@@ -2931,19 +3364,19 @@ declare class SeatPicker implements GaPromptPicker {
2931
3364
  * part is the engine's; see pickerHoldLapse.ts. */
2932
3365
  private readonly lapse;
2933
3366
  /** Rail scroll listener attached once; the rail repaints many times. */
2934
- private railEdgesBound;
2935
- private fsFallback;
3367
+ /** @internal */ railEdgesBound: boolean;
3368
+ /** @internal */ fsFallback: boolean;
2936
3369
  private fsChangeHandler;
2937
3370
  private fsEscHandler;
2938
3371
  /** Host-level event chrome owns the duplicate identity outside full screen. */
2939
3372
  private eventDetailsHidden;
2940
3373
  /** True while the hold note reads "N more selected" — the tick must not overwrite it. */
2941
- private holdCopyPending;
3374
+ /** @internal */ holdCopyPending: boolean;
2942
3375
  /** Wide-layout ticket panel collapsed (map owns the full width). */
2943
3376
  private sideCollapsed;
2944
3377
  private sideToggleEl;
2945
3378
  /** True once we've asked the host page to pin us fullscreen (framed, no native). */
2946
- private framedFs;
3379
+ /** @internal */ framedFs: boolean;
2947
3380
  /** Last height (px) posted to a host frame; dedupes redundant reports. */
2948
3381
  private lastPostedHeight;
2949
3382
  /** See `./seatViewThumb`, which the native bridge answers from as well. */
@@ -2969,7 +3402,33 @@ declare class SeatPicker implements GaPromptPicker {
2969
3402
  /** Post `seatlayer:height` to the host when framed and the value changed. */
2970
3403
  private reportFramedHeight;
2971
3404
  /** Full screen via the native API, falling back to a fixed-position overlay (iOS Safari). */
2972
- private toggleFullscreen;
3405
+ /** @internal */ toggleFullscreen(): void;
3406
+ /**
3407
+ * Is the picker already as big as the screen it is on?
3408
+ *
3409
+ * Measured, not inferred. A host that opens the widget inside its own
3410
+ * full-height modal — DesiPass does exactly this on a phone — hands us a
3411
+ * root whose rect IS the viewport, and there is nothing left for "Full
3412
+ * screen" to give. The tolerance absorbs a hairline of host chrome, a
3413
+ * fractional device pixel ratio and a rubber-banding iOS toolbar; anything
3414
+ * larger than that is a real embed with page around it.
3415
+ */
3416
+ private fillsViewport;
3417
+ /**
3418
+ * Show the "Full screen" pill only where pressing it can change something.
3419
+ *
3420
+ * On a phone inside a host's full-height modal the pill was a dead control
3421
+ * (client report on DesiPass, 2026-09-04): iOS Safari has no element
3422
+ * fullscreen, so the tap fell through to the fixed-position fallback — which
3423
+ * pins the widget to a viewport it was already filling. Nothing moved, and
3424
+ * an affordance that does nothing is worse than one that is not there.
3425
+ *
3426
+ * Narrow only. A desktop embed that happens to be viewport-sized is still
3427
+ * surrounded by browser chrome that native fullscreen removes, so the pill
3428
+ * has something to do there. And an ACTIVE fullscreen always keeps its pill:
3429
+ * that is the only way back out.
3430
+ */
3431
+ private syncFullscreenAffordance;
2973
3432
  private syncFullscreenButtons;
2974
3433
  /**
2975
3434
  * Native element-fullscreen was unavailable or rejected. When framed, a CSS
@@ -3112,7 +3571,9 @@ declare class SeatPicker implements GaPromptPicker {
3112
3571
  * falsely block standing room) — mirrors the public page. Clears live when WS
3113
3572
  * frees a seat up.
3114
3573
  */
3115
- private syncSoldout;
3574
+ /** @internal */ syncSoldout(categories: Array<{
3575
+ key: string;
3576
+ }>, left: Record<string, number>): void;
3116
3577
  /**
3117
3578
  * Pure sold-out predicate: every SEATED category's free count is 0, there is at
3118
3579
  * least one seated category, and there are no GA areas (GA capacity isn't
@@ -3148,44 +3609,25 @@ declare class SeatPicker implements GaPromptPicker {
3148
3609
  /** Restart one finite CSS animation without leaving a permanent state class. */
3149
3610
  private animateOnce;
3150
3611
  /** Selection feedback belongs on the selected seat, not across the whole map. */
3151
- private flashPickedSeat;
3612
+ /** @internal */ flashPickedSeat(id: string): void;
3152
3613
  /** A completed hold gets one short map ripple per concrete seat. */
3153
3614
  private flashHeldSeats;
3154
3615
  /** Update only the action affordance; selection callbacks must not refire. */
3155
3616
  /** @internal */ committedSelection(): PickerSeat[];
3156
- private pendingSelectionCount;
3157
- private heldTicketCount;
3617
+ /** @internal */ pendingSelectionCount(): number;
3618
+ /** @internal */ heldTicketCount(): number;
3158
3619
  /** @internal */ totalTicketCount(): number;
3159
3620
  /** Held tickets and standing quantities consume the same order-wide cap. */
3160
- private updateSelectionCapacity;
3161
- private syncCta;
3162
- /**
3163
- * Narrate the money path.
3164
- *
3165
- * THE PHASE BELONGS TO THE PROMISE, NOT TO A CLOCK. `checkout` used to clear
3166
- * itself after a fixed 1,100ms whatever the unawaited hold or handoff was
3167
- * doing — so the button re-armed while the request was still in flight, and a
3168
- * buyer could press it again into a hold that had already failed. The phase
3169
- * now ends where the work ends: `runCheckoutHandoff` returns it to idle when
3170
- * the handoff settles, and `handleCta`'s catch returns it on failure.
3171
- */
3172
- private setCtaPhase;
3173
- /**
3174
- * Hand the buyer to checkout and hold the "Opening secure checkout…" state
3175
- * for exactly as long as that takes.
3176
- *
3177
- * The default `onCheckout` is usually synchronous and usually navigates; the
3178
- * hosted path is a real promise. Awaiting both means the one visible signal
3179
- * that money is moving cannot expire early, and cannot outlive the work
3180
- * either.
3181
- */
3621
+ /** @internal */ updateSelectionCapacity(): void;
3622
+ /** @internal */ syncCta(count?: number, pending?: number): void;
3623
+ /** @internal */ setCtaPhase(phase: 'idle' | 'holding' | 'checkout'): void;
3182
3624
  private runCheckoutHandoff;
3183
3625
  /** Session-scoped capability key: isolated by API origin and event. */
3184
- private holdStorageKey;
3185
- private rememberedHoldId;
3186
- private rememberHold;
3187
- private forgetHold;
3188
- private resumeHoldFromServer;
3626
+ /** @internal */ holdStorageKey(): string;
3627
+ /** @internal */ rememberedHoldId(): string | null;
3628
+ /** @internal */ rememberHold(hold: HoldResult): void;
3629
+ /** @internal */ forgetHold(): void;
3630
+ /** @internal */ resumeHoldFromServer(holdId: string, automatic: boolean): Promise<HoldResult | null>;
3189
3631
  private restoreRememberedHold;
3190
3632
  /** Section-bearing objects on the active floor (single-floor → doc.objects). */
3191
3633
  private activeFloorObjects;
@@ -3222,15 +3664,15 @@ declare class SeatPicker implements GaPromptPicker {
3222
3664
  * this would be two ways for the map, the legend, the floors and the 3D
3223
3665
  * snapshot to end up disagreeing about what is filtered.
3224
3666
  */
3225
- private applyPriceBand;
3667
+ /** @internal */ applyPriceBand(keys: string[] | null): void;
3226
3668
  /** Build projection, rung and floor controls for arena-scale charts. */
3227
3669
  private buildArenaChrome;
3228
3670
  /** Reflect the engine's current LOD rung onto the pill group. */
3229
- private syncRung;
3230
- private syncProjection;
3671
+ /** @internal */ syncRung(): void;
3672
+ /** @internal */ syncProjection(): void;
3231
3673
  /** Can this picker offer the 3D venue view? Requires the option (default on),
3232
3674
  * WebGL2, a chart to render, and a chart small enough to render WELL. */
3233
- private canOffer3d;
3675
+ /** @internal */ canOffer3d(): boolean;
3234
3676
  /**
3235
3677
  * The seat ceiling for offering 3D. See `max3DSeats` — an explicit host value
3236
3678
  * always wins; otherwise a small/low-core device gets half the desktop budget,
@@ -3238,16 +3680,15 @@ declare class SeatPicker implements GaPromptPicker {
3238
3680
  */
3239
3681
  private max3dSeats;
3240
3682
  /** Reflect the active floor onto the switcher rail. */
3241
- private syncFloors;
3683
+ /** @internal */ syncFloors(): void;
3242
3684
  /** The tapped-section summary card in all three of its forms. Built lazily:
3243
3685
  * it reads `els`/`root`, which are assigned after construction. */
3244
3686
  private get sectionCard();
3245
3687
  /** Show (or clear, on null) the tapped-section summary card. */
3246
- private showSectionCard;
3688
+ /** @internal */ showSectionCard(summary: SectionSummary | null): void;
3247
3689
  private renderSectionCard;
3248
3690
  private collapseSectionCard;
3249
3691
  private sectionCardOnView;
3250
- private fitSectionStripCount;
3251
3692
  /**
3252
3693
  * Open sections next to the focused one, in authored order.
3253
3694
  *
@@ -3265,8 +3706,8 @@ declare class SeatPicker implements GaPromptPicker {
3265
3706
  */
3266
3707
  private markedSeatAnnouncement;
3267
3708
  /** aria-live readout when keyboard focus lands on a seat. */
3268
- private announceSeat;
3269
- private showTableDialog;
3709
+ /** @internal */ announceSeat(seat: ExpandedSeat | null): void;
3710
+ /** @internal */ showTableDialog(table: TableSelectionDetails, held: boolean, returnFocus?: HTMLElement | null): void;
3270
3711
  private renderTableDialogState;
3271
3712
  private confirmTableDialog;
3272
3713
  private cancelTableDialog;
@@ -3280,9 +3721,9 @@ declare class SeatPicker implements GaPromptPicker {
3280
3721
  * `buyerAssetUrls` on the INSTANCE, and the card has to see those.
3281
3722
  */
3282
3723
  private get confirmCard();
3283
- /** @internal */ showConfirm(seat: ExpandedSeat): void;
3724
+ /** @internal */ showConfirm(seat: ExpandedSeat, mode?: ConfirmCardMode): void;
3284
3725
  private reanchorConfirm;
3285
- private dismissConfirm;
3726
+ /** @internal */ dismissConfirm(): void;
3286
3727
  /** @internal The card's own button calls the card; this is the seam the
3287
3728
  * widget's tests press. */
3288
3729
  /** @internal */ commitConfirm(): Promise<void>;
@@ -3292,7 +3733,7 @@ declare class SeatPicker implements GaPromptPicker {
3292
3733
  * call and replace both on the instance. */
3293
3734
  private inviteCommit;
3294
3735
  private addSeatLabel;
3295
- private seatViewEnabled;
3736
+ /** @internal */ seatViewEnabled(): boolean;
3296
3737
  /** Every bookable seat (cached) — neighbor heads for the generated panorama. */
3297
3738
  /** @internal */ allSeats(): ExpandedSeat[];
3298
3739
  /**
@@ -3319,7 +3760,32 @@ declare class SeatPicker implements GaPromptPicker {
3319
3760
  * visibilitychange handler owns catching that tab up.
3320
3761
  */
3321
3762
  private scheduleOfferBoundary;
3322
- /** Debounce the no-store offer read behind a burst of seat-status frames. */
3763
+ /**
3764
+ * Is the answer we hold current enough that asking again would learn nothing?
3765
+ *
3766
+ * True while a read is in flight (its answer is on its way) or while one that
3767
+ * just settled is inside the quiet window.
3768
+ */
3769
+ private offerReadIsCurrent;
3770
+ /**
3771
+ * Debounce the no-store offer read behind a burst of seat-status frames.
3772
+ *
3773
+ * THE SKIP IS DECIDED HERE, WHEN THE REFRESH IS ASKED FOR — not 180 ms later
3774
+ * when the timer fires. Deciding at fire time made the dedupe a race between
3775
+ * this debounce and the quiet window, with 250 − 180 = 70 ms of margin: if
3776
+ * the timer ran even that little late, the window had lapsed and a second,
3777
+ * identical availability read went out. Seventy milliseconds of scheduler
3778
+ * jitter is nothing on a loaded machine, and this optimisation exists for
3779
+ * mid-range phones, where it is nothing on a good day.
3780
+ *
3781
+ * (Caught by the app's test/pickerOpenPathConcurrency.test.ts failing once in
3782
+ * a 462-file parallel run and passing in isolation — the flake was real, and
3783
+ * it was here rather than in the test.)
3784
+ *
3785
+ * The rule is unchanged, only the instant it is evaluated at: a refresh asked
3786
+ * for while the answer is still current is answered by the read that just
3787
+ * happened. A batch arriving past the window still schedules and still reads.
3788
+ */
3323
3789
  private scheduleOfferRefresh;
3324
3790
  /**
3325
3791
  * Pull the server's resolved answer. A failed refresh keeps the last truthful
@@ -3327,7 +3793,9 @@ declare class SeatPicker implements GaPromptPicker {
3327
3793
  * offer is worse than a temporarily stale remaining count.
3328
3794
  */
3329
3795
  private refreshOfferAvailability;
3330
- private offerPrice;
3796
+ /** The read itself. Never rejects — see the catch. */
3797
+ private readOfferAvailability;
3798
+ /** @internal */ offerPrice(categoryKey: string | undefined): TicketOfferPrice | null;
3331
3799
  private applyChannelPricing;
3332
3800
  private syncOffer;
3333
3801
  /**
@@ -3339,26 +3807,29 @@ declare class SeatPicker implements GaPromptPicker {
3339
3807
  /** @internal */ paidPrice(categoryKey: string | undefined, tierId: string | null | undefined, fallback: number, objectLabel?: string): number;
3340
3808
  /** The half of a legend's inputs the rail and the price list share, resolved
3341
3809
  * once so the two renderings cannot be given different answers. */
3342
- private legendDeps;
3810
+ /** @internal */ legendDeps(): LegendDeps;
3343
3811
  /** Paint the phone's always-visible legend; the chip HTML lives in
3344
3812
  * pickerLegend.ts. A legend of ONE teaches nothing, so it stays away. */
3345
- private syncPriceRail;
3346
- private syncPrices;
3813
+ /** @internal */ syncPriceRail(categories: LegendCategory[], left: Record<string, number>, deps?: LegendDeps): void;
3814
+ /** @internal */ syncPrices(): void;
3347
3815
  /** Tap a price row → filter + frame that category on the map; tap again to
3348
3816
  * clear. Shares `priceBandKeys` with the band selector so the row-dim state
3349
3817
  * has one source of truth (and each control resets the other). */
3350
- private focusCategory;
3818
+ /** @internal */ focusCategory(key: string): void;
3351
3819
  /**
3352
3820
  * Live-activity strip: turn WS availability deltas into one quiet line of
3353
3821
  * social proof ("2 seats just taken in VIP · 118 left"). Diffs per-category
3354
3822
  * counts on every status change — no per-seat payload needed. Skips the very
3355
3823
  * first computation (initial load is not "activity").
3356
3824
  */
3357
- private narrateAvailability;
3358
- private lastCatAvail;
3359
- private lastAvailFloorId;
3360
- private availQuietUntil;
3361
- private liveTimer;
3825
+ /** @internal */ narrateAvailability(categories: Array<{
3826
+ key: string;
3827
+ label: string;
3828
+ }>, left: Record<string, number>): void;
3829
+ /** @internal */ lastCatAvail: Record<string, number> | null;
3830
+ /** @internal */ lastAvailFloorId: string;
3831
+ /** @internal */ availQuietUntil: number;
3832
+ /** @internal */ liveTimer: ReturnType<typeof setTimeout> | null;
3362
3833
  /** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
3363
3834
  private evictTakenSelections;
3364
3835
  /** @internal */ syncTray(): void;
@@ -3379,7 +3850,7 @@ declare class SeatPicker implements GaPromptPicker {
3379
3850
  private fitTranslatedRows;
3380
3851
  private emitSelectionValidity;
3381
3852
  /** Paint the bottom-sheet's collapsed one-liner (pickerTray.ts builds it). */
3382
- private renderPeek;
3853
+ /** @internal */ renderPeek(count: number, total: number, pendingCount: number, bump?: boolean): void;
3383
3854
  /**
3384
3855
  * Release one buyer-visible held LINE. That is one label for a seat, and every
3385
3856
  * unit label behind a grouped GA line — a buyer who presses × on
@@ -3388,11 +3859,11 @@ declare class SeatPicker implements GaPromptPicker {
3388
3859
  private removeHeldLabels;
3389
3860
  private handleChangeSeats;
3390
3861
  /** @internal */ handleCta(): Promise<void>;
3391
- private startHoldTimer;
3392
- private stopHoldTimer;
3862
+ /** @internal */ startHoldTimer(expiresAt: number): void;
3863
+ /** @internal */ stopHoldTimer(): void;
3393
3864
  /** Show/refresh (or hide) the "Need more time?" prompt with the live seconds left. */
3394
- private setExtendPrompt;
3395
- private handleExtend;
3865
+ /** @internal */ setExtendPrompt(show: boolean, ms: number): void;
3866
+ /** @internal */ handleExtend(): Promise<void>;
3396
3867
  /**
3397
3868
  * Fire the booked-confirmation state once the buyer's held seats settle to
3398
3869
  * booked. The controller clears its own hold the moment every held label reads
@@ -3431,7 +3902,7 @@ declare class SeatPicker implements GaPromptPicker {
3431
3902
  * The default branch is the literal call that stood here before hosted
3432
3903
  * checkout existed, unchanged, so nothing about an existing integration moves.
3433
3904
  */
3434
- private checkoutHandoff;
3905
+ /** @internal */ checkoutHandoff(hold: HoldResult, seats: PickerSeat[]): Promise<void>;
3435
3906
  /**
3436
3907
  * Take the money ourselves, through the organizer's own gateway.
3437
3908
  *
@@ -3457,8 +3928,8 @@ declare class SeatPicker implements GaPromptPicker {
3457
3928
  private openCheckoutPanel;
3458
3929
  private closeCheckoutPanel;
3459
3930
  /** Assemble the stable {@link CheckoutHandoff} from a hold's server line items. */
3460
- private buildHandoff;
3461
- private emitHoldChange;
3931
+ /** @internal */ buildHandoff(hold: HoldResult): CheckoutHandoff;
3932
+ /** @internal */ emitHoldChange(): void;
3462
3933
  /** @internal */ toast(msg: string, tone?: 'neutral' | 'success' | 'warning' | 'error', action?: {
3463
3934
  label: string;
3464
3935
  onClick: () => void;
@@ -3681,55 +4152,20 @@ declare class SeatPicker implements GaPromptPicker {
3681
4152
  * source compatibility but coerce to `flat` with a one-time deprecation warn. */
3682
4153
  private perspectiveWarned;
3683
4154
  private normalizeInitialView;
4155
+ private emitAnalytics;
4156
+ /** @internal */ emit3dAnalytics(event: string, props?: Record<string, unknown>): void;
3684
4157
  /** Current buyer view: the flat map, or the interactive 3D venue. */
3685
4158
  getBuyerView(): SeatPickerBuyerView;
3686
- /**
3687
- * Switch between the flat seat **Map** and the interactive **3D venue** view —
3688
- * the same control the buyer's on-widget `Map | 3D` toggle drives.
3689
- *
3690
- * Entering `'venue3d'` with `opts.flyToSeatId` runs the cinematic tour: the
3691
- * camera flies to the seat and holds in the live scene (a chip offers the
3692
- * 360° view-from-seat). When the widget is **already** in the 3D
3693
- * view, the camera simply flies to the requested seat — the GL scene is not
3694
- * torn down or rebuilt, so there is no flash or re-entry.
3695
- *
3696
- * No-op when the view is unchanged and no `flyToSeatId` is given, or when 3D is
3697
- * unavailable for this chart.
3698
- *
3699
- * @param view `'map'` for the flat picker, `'venue3d'` for the 3D venue.
3700
- * @param opts.flyToSeatId When entering (or already in) `'venue3d'`, the seat
3701
- * id to fly the camera to — cinematic → live seat view.
3702
- * Ignored when `view` is `'map'`.
3703
- *
3704
- * @example
3705
- * // Public 3D tour entry — enter 3D and fly straight to the buyer's seat:
3706
- * picker.setBuyerView('venue3d', { flyToSeatId: 'A-12' });
3707
- */
4159
+ /** Switch the buyer between the map and the 3D venue. */
3708
4160
  setBuyerView(view: SeatPickerBuyerView, opts?: SeatPickerBuyerViewOptions): void;
3709
- /** SeatStatus → the view3d palette state. Selection is layered separately. */
3710
- private seatState3dFor;
3711
- /** Push the full live availability snapshot into the 3D handle (selection is
3712
- * preserved inside the module). Cheap enough per status delta. */
3713
- private pushAvailabilityTo3d;
3714
- /** Mirror the authoritative widget selection into the 3D handle. */
4161
+ /** @internal */ seatState3dFor(seat: ExpandedSeat): SeatState3D;
4162
+ /** @internal */ pushAvailabilityTo3d(): void;
3715
4163
  /** @internal */ syncSelectionTo3d(): void;
3716
- /** Build the view-from-seat panorama the cinematic dissolves into — reuses the
3717
- * exact input path as the 2D `openSeatView` (organizer photo, else generated). */
3718
- private seatViewFor3d;
3719
- /** Route decoupled analytics into the host callback, tagged buyer. */
3720
- private emitAnalytics;
3721
- /** @internal */ emit3dAnalytics(event: string, props?: Record<string, unknown>): void;
3722
- /** A 3D seat tap runs the SAME selection path as a 2D tap: toggle through the
3723
- * controller, then raise the shared confirm card (bottom-sheeted in 3D). */
3724
- private onView3dSeatPick;
3725
- /** The arrival caption for a seat-eye landing; assembled in pickerSeatViewChrome. */
3726
- private seatViewCaption;
3727
- /** Explain a 3D seat that INVENTORY refuses — never one the buyer's own
3728
- * filters merely dimmed, which stays buyable exactly as it is in 2D. Category
3729
- * colour remains visible in the venue; this card names the availability state
3730
- * explicitly so yellow never has to carry both meanings. */
3731
- private showUnavailable3dSeat;
3732
- private dismissUnavailable3dSeat;
4164
+ /** @internal */ seatViewFor3d(seatId: string): Promise<SeatView | null>;
4165
+ /** @internal */ onView3dSeatPick(seatId: string): void;
4166
+ /** @internal */ seatViewCaption(seatId: string): string;
4167
+ /** @internal */ showUnavailable3dSeat(seat: ExpandedSeat, visualState: SeatState3D, status: Exclude<SeatStatus, 'free'>): void;
4168
+ /** @internal */ dismissUnavailable3dSeat(): void;
3733
4169
  private saveView3dComparisonSeat;
3734
4170
  /** @internal */ clearView3dComparison(): void;
3735
4171
  /** @internal */ view3dComparisonSnapshot(seatId: string): {
@@ -3746,26 +4182,14 @@ declare class SeatPicker implements GaPromptPicker {
3746
4182
  accessibility: string;
3747
4183
  confidence: _seatlayer_core_core_seatConfidence.SeatConfidenceDisclosure;
3748
4184
  } | null;
4185
+ /** @internal */ enter3d(flySeatId?: string): Promise<void>;
4186
+ /** @internal */ publish3dTopInset(): void;
4187
+ /** @internal */ exit3d(): void;
3749
4188
  /** The two dialogs INSIDE the 3D overlay — see pickerView3dModals.ts. */
3750
4189
  private openSeatConfidencePassport;
3751
- private closeSeatConfidencePassport;
3752
- private openView3dComparison;
3753
- private closeView3dComparison;
3754
- private enter3d;
3755
- /**
3756
- * Publish how much room the picker's own top-left chrome takes, as
3757
- * `--sl-3d-top-inset` on the widget root.
3758
- *
3759
- * The 3D layer draws its "Live seat-eye view · …" caption in the same twelve
3760
- * pixels. That caption is engine surface with its position written inline, so
3761
- * the widget cannot move it; publishing the measurement is the seam the
3762
- * engine needs to read one day, and it costs nothing until it does. The
3763
- * widget's own rules already use it, which is why it is measured rather than
3764
- * assumed: the region wraps, and a chart with an accessibility flag beside
3765
- * the back button is two rows tall.
3766
- */
3767
- private publish3dTopInset;
3768
- private exit3d;
4190
+ /** @internal */ closeSeatConfidencePassport(restoreFocus?: boolean): void;
4191
+ /** @internal */ openView3dComparison(): void;
4192
+ /** @internal */ closeView3dComparison(restoreFocus?: boolean): void;
3769
4193
  /** Current active/restored hold reflected in the tray. */
3770
4194
  getCurrentHold(): HoldResult | null;
3771
4195
  /** Explicit host-driven hold restore (automatic session restore is on by default). */
@@ -3790,6 +4214,21 @@ declare class SeatPicker implements GaPromptPicker {
3790
4214
  * when a fresh bearer is held; the map and the realtime feed resume with it.
3791
4215
  */
3792
4216
  refreshAccess(): Promise<boolean>;
4217
+ /**
4218
+ * Clear the decision surfaces before an interrupt covers them.
4219
+ *
4220
+ * A confirm card, a table dialog and a GA prompt are all mid-decision UI
4221
+ * about a specific seat, and every one of them is asking a question the
4222
+ * buyer can no longer answer once we are raising an interrupt: the hold's
4223
+ * seats went back on sale, or the session that could see them has ended.
4224
+ * Left up, they sit behind the veil still showing a candidate and an
4225
+ * Add button, so the interrupt is not the only surface and the widget looks
4226
+ * like it is asking two things at once (owner review at 390px).
4227
+ *
4228
+ * Focus is deliberately NOT restored by any of these: the interrupt takes it
4229
+ * a frame later, and handing it back to the map first would fight that.
4230
+ */
4231
+ private clearDecisionSurfaces;
3793
4232
  /**
3794
4233
  * The buyer-facing access state. Plain language, no internal vocabulary, and
3795
4234
  * never a channel name, id or count — the buyer is told what happened and
@@ -3800,7 +4239,45 @@ declare class SeatPicker implements GaPromptPicker {
3800
4239
  * inventory and never silently drops a buyer's cart (guide §9).
3801
4240
  */
3802
4241
  private showAccessPanel;
4242
+ /**
4243
+ * What the recovery screen's button actually does.
4244
+ *
4245
+ * IN PLACE FIRST, ALWAYS. `refreshAccess` re-bootstraps the session, re-reads
4246
+ * the chart and restarts the socket without the map ever going away, and it
4247
+ * takes this screen down itself the moment the fresh session is live. That is
4248
+ * the whole of the happy path, and it is the one the buyer should get: a page
4249
+ * reload throws away the map, the scroll position and the zoom, and on a
4250
+ * hosted checkout it can cost them the query string that got them here.
4251
+ *
4252
+ * A RELOAD IS THE LAST RESORT, AND ONLY WHEN NOBODY ELSE IS LISTENING. If the
4253
+ * host passed `onAccessUnavailable` it has already been told and may be
4254
+ * running its own recovery — navigating its page out from under it would
4255
+ * destroy that. So the fallback fires only for an unattended embed, where
4256
+ * there is genuinely nothing else left to try.
4257
+ */
4258
+ private recoverAccess;
3803
4259
  private dismissAccessPanel;
4260
+ /**
4261
+ * The hold ran out under the buyer's feet — say so, and block.
4262
+ *
4263
+ * A toast was the wrong shape for this: it left a map still showing the
4264
+ * seats as theirs, a cart still totalling them and a CTA that could not
4265
+ * work, and it was gone in four seconds. Everything the buyer can see is
4266
+ * stale, so the picker stops until they have acknowledged it.
4267
+ */
4268
+ private showHoldExpired;
4269
+ private dismissHoldExpired;
4270
+ /**
4271
+ * "Start again": put the picker back to the state it would have been in had
4272
+ * the buyer just arrived.
4273
+ *
4274
+ * The hold state itself was already cleared by `onHoldExpired` — what is
4275
+ * left is everything that was still SHOWING it. The selection goes (with any
4276
+ * confirm card or table dialog on top of it), the lapse note goes, the cart
4277
+ * and CTA are recomputed from the now-empty selection, and the map walks
4278
+ * back out to the venue so the buyer starts where they started.
4279
+ */
4280
+ private restartAfterHoldExpiry;
3804
4281
  destroy(): void;
3805
4282
  }
3806
4283
 
@@ -4378,7 +4855,8 @@ declare class SeasonPicker {
4378
4855
  * picker wire contract:
4379
4856
  *
4380
4857
  * • `{ type: 'seatlayer:height', px:number }` — grow the iframe to `px`.
4381
- * • `{ type: 'seatlayer:fullscreen', on:boolean }` — pin/unpin over the host.
4858
+ * • `{ type: 'seatlayer:fullscreen', on:boolean, background?:string }`
4859
+ * — pin/unpin over the host.
4382
4860
  *
4383
4861
  * A framed picker cannot escape its own iframe with CSS, so it delegates both
4384
4862
  * concerns to the host. `attachPickerFrame` wires those two behaviours onto a