@seatlayer/js 0.71.2 → 0.71.4

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,9 +1,9 @@
1
- import { PickerSeat, PickerGAArea, PickerSelectionValidator, PickerMapTheme, RendererViewMode, SeatHoverDetails, PickerSelectionValidity, SectionSummary, ChartTheme, LodRung, PickerTransport, PickerController, AccessibilityType, ExpandedSeat } from '@seatlayer/core';
1
+ import { PickerSeat, PickerGAArea, PickerMapTheme, PickerTransport, PickerSelectionValidator, RendererViewMode, PickerSelectionValidity, SeatHoverDetails, FloorLabelStyle, SectionSummary, ChartTheme, LodRung, PickerController, AccessibilityType, ExpandedSeat } from '@seatlayer/core';
2
2
  export { ExpandedSeat, PickerMapTheme, PickerSelectionValidator, PickerSelectionValidity, PickerSelectionViolation, RendererViewMode, SeatHoverDetails } from '@seatlayer/core';
3
- import * as _seatlayer_core_core_seatConfidence from '@seatlayer/core/core/seatConfidence';
4
- import { Venue3DHandle } from '@seatlayer/core/view3d';
5
3
  import { T as ThemeMode } from './channelsMode-goczEzyt.js';
6
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
+ import * as _seatlayer_core_core_seatConfidence from '@seatlayer/core/core/seatConfidence';
6
+ import { Venue3DHandle } from '@seatlayer/core/view3d';
7
7
 
8
8
  /**
9
9
  * Buyer access context — the browser half of the Sales Channels contract
@@ -620,169 +620,318 @@ interface GaPromptPicker {
620
620
 
621
621
  type SeatingChartBuyerView = 'map' | 'venue3d';
622
622
  type SeatingChartNavigationMode = 'orbit' | 'pan';
623
+ /** What the 2D seat-view panorama is showing, and what it would say about it. */
624
+ interface SeatingChartSeatViewInfo {
625
+ seatId: string;
626
+ /** e.g. `View from Stalls D · C-6`. */
627
+ title: string;
628
+ /** The disclosure line: an authored capture's provenance, or the generated
629
+ * illustration's distance-from-stage note. */
630
+ caption: string;
631
+ /** The badge word — the localized `Real 360°` or `Preview`. */
632
+ badge: string;
633
+ /** True for an AUTHORED capture of the real seat; false for one the engine
634
+ * drew from the venue's geometry. */
635
+ real: boolean;
636
+ /** True when the panorama was synthesised rather than fetched. */
637
+ generated: boolean;
638
+ /** The pointer instruction the modal would print over the image. */
639
+ dragHint: string;
640
+ }
623
641
 
624
642
  /**
625
- * SeatingChart the embeddable buyer picker.
643
+ * Buyer-safe ticket-offer availability shared by the canonical picker and the
644
+ * hosted event-page templates.
626
645
  *
627
- * A thin wrapper over the shared PickerController (src/picker/PickerController):
628
- * it owns the mount <div> + the public embed contract (hold-only the SDK hands
629
- * the holdId to the host page for a server-side book) and delegates all transport
630
- * + booking to the controller, so the SDK inherits every fix made for the live
631
- * buyer page and the demo picker.
646
+ * This module intentionally owns the wire parser. A hosted page, iframe, popup
647
+ * and SDK mount must refuse or accept the same payload; duplicating this reader
648
+ * is how one surface eventually prints a price another surface will not charge.
632
649
  */
633
-
634
- /** A seat as surfaced to the host page (prices resolved from the chart's categories). */
635
- type SelectedSeat = PickerSeat;
636
- interface GAAreaAvailability {
637
- id: string;
638
- label: string;
639
- capacity: number;
640
- available: number;
650
+ type SaleState = 'on-sale' | 'low' | 'sold-out' | 'presale' | 'closed';
651
+ interface TicketOfferSummary {
652
+ /** Legacy ordering fields kept for older page templates and payloads. */
653
+ index: number;
654
+ count: number;
655
+ /** Units currently available at this offer price; null means unlimited. */
656
+ remaining: number | null;
657
+ /** Buyer-facing fields added by the offers UX. Absent on older workers. */
658
+ id?: string;
659
+ name?: string;
660
+ categoryKey?: string | null;
661
+ startsAt?: number | null;
662
+ endsAt?: number | null;
663
+ }
664
+ interface TicketOfferPrice {
641
665
  categoryKey: string;
666
+ /** Major units. What a hold on this category is charged right now. */
642
667
  price: number;
643
- currency: string;
644
- /** Buyer-facing copy authored separately from stable inventory identity. */
645
- displayLabel?: string;
646
- displayType?: string;
647
- tiers?: Array<{
648
- id: string;
649
- name: string;
650
- price: number;
668
+ /** Major units, or null. Printed only when genuinely higher. */
669
+ previousPrice: number | null;
670
+ /** Offer provenance for the price row. Absent on older workers. */
671
+ offerId?: string;
672
+ offerName?: string;
673
+ remaining?: number | null;
674
+ startsAt?: number | null;
675
+ endsAt?: number | null;
676
+ }
677
+ interface ChannelPriceOverride {
678
+ categoryKey: string;
679
+ tierId: string | null;
680
+ price: number;
681
+ }
682
+ /** Pricing configuration projected only for the private channels in this grant. */
683
+ interface BuyerChannelPricing {
684
+ assignmentVersion: number;
685
+ channels: Array<{
686
+ channelId: string;
687
+ pricingVersion: number;
688
+ priceOverrides: ChannelPriceOverride[];
689
+ }>;
690
+ objects: Array<{
691
+ label: string;
692
+ channelId: string;
651
693
  }>;
652
694
  }
695
+ interface TicketOfferAvailability {
696
+ state: SaleState;
697
+ /** Currently advertised offer price, in minor units. */
698
+ fromPrice: number | null;
699
+ previousPrice: number | null;
700
+ currency: string | null;
701
+ /** The highest-priority active buy offer, when one exists. */
702
+ release: TicketOfferSummary | null;
703
+ /** The next scheduled price offer. It does not close ordinary ticket sales. */
704
+ upcoming: TicketOfferSummary | null;
705
+ /** Server-resolved active offer prices by category, in major units. */
706
+ prices: TicketOfferPrice[];
707
+ channelPricing: BuyerChannelPricing | null;
708
+ }
709
+ /** Parse a public offer payload without repairing a half-understood price. */
710
+ declare function parseTicketOfferAvailability(body: unknown): TicketOfferAvailability | null;
711
+ /** Translate the server-resolved category map into SeatPicker pricing. */
712
+ declare function ticketOfferPrices(availability: TicketOfferAvailability | null): Record<string, number>;
713
+
653
714
  /**
654
- * Serializable state exposed to hosted native picker chrome. It deliberately
655
- * contains buyer-display data only: never API keys, buyer-access bearers,
656
- * provider callbacks, or the active hold id.
715
+ * pickerTypes the SeatPicker public option/theme/pricing contract, plus the
716
+ * two internal shapes (`SectionLike`, `PriceBand`) the picker reads off a
717
+ * ChartDoc.
718
+ *
719
+ * Split out of SeatPicker.ts verbatim; these declarations are the widget's
720
+ * published API surface, so SeatPicker.ts re-exports every public name from
721
+ * here and `@seatlayer/js`'s entry (src/index.ts) is untouched. Types only —
722
+ * nothing in this file emits a byte of runtime JavaScript.
657
723
  */
658
- interface SeatingChartPickerState {
659
- event: {
660
- key: string;
661
- name: string;
662
- venue: string | null;
663
- startsAt: number | null;
664
- timezone: string | null;
665
- currency: string;
666
- locale: string | null;
667
- posterUrl: string | null;
668
- mode: 'live' | 'test';
669
- salesClosed: boolean;
670
- };
671
- branding: {
672
- accent?: ChartTheme['accent'];
673
- accentInk?: ChartTheme['accentInk'];
674
- background?: ChartTheme['background'];
675
- textColor?: ChartTheme['textColor'];
676
- brandName?: ChartTheme['brandName'];
677
- logoUrl?: ChartTheme['logoUrl'];
678
- /** Native must render SeatLayer attribution when it owns chrome and this is true. */
679
- attributionRequired: boolean;
680
- };
681
- features: {
682
- ga: boolean;
683
- tiers: boolean;
684
- floors: boolean;
685
- zones: boolean;
686
- sections: boolean;
687
- bestAvailable: boolean;
688
- accessibilityFilter: boolean;
689
- limitedViewFilter: boolean;
690
- /** Real lazy-loaded WebGL venue view, not the retired isometric projection. */
691
- venue3d: boolean;
692
- /** Authored or chart-derived view-from-seat panorama. */
693
- seatView: boolean;
694
- };
695
- catalog: {
696
- categories: Array<{
697
- key: string;
698
- label: string;
699
- color: string;
700
- price: number;
701
- available: number;
702
- tiers: Array<{
703
- id: string;
704
- name: string;
705
- price: number;
706
- currency?: string;
707
- }>;
708
- }>;
709
- zones: Array<{
710
- id: string;
711
- label: string;
712
- color?: string;
713
- }>;
714
- sections: Array<{
715
- id: string;
716
- label: string;
717
- displayLabel?: string;
718
- zoneId?: string;
719
- }>;
720
- gaAreas: GAAreaAvailability[];
721
- bestAvailableZones: Array<{
722
- id: string;
723
- label: string;
724
- }>;
725
- };
726
- map: {
727
- floors: Array<{
728
- id: string;
729
- name: string;
730
- }>;
731
- activeFloorId: string | null;
732
- rung: LodRung;
733
- focusedSectionId: string | null;
734
- focusedSection: SectionSummary | null;
735
- viewMode: RendererViewMode;
736
- buyerView: SeatingChartBuyerView;
737
- view3dTargetSeatId: string | null;
738
- view3dNavigationMode: SeatingChartNavigationMode;
739
- colorblindSafe: boolean;
740
- categoryFilter: string[] | null;
741
- accessibilityFilter: string[] | null;
742
- hideLimitedView: boolean;
743
- };
744
- selection: {
745
- seats: SelectedSeat[];
746
- validity: PickerSelectionValidity | null;
747
- maxSelection: number;
748
- };
749
- access: {
750
- configured: boolean;
751
- status: 'public' | 'ready' | 'expired' | 'unavailable';
752
- reason?: string;
753
- };
754
- }
755
- interface SeatingChartOptions {
756
- /** CSS selector or an HTMLElement to render into. */
757
- container: string | HTMLElement;
758
- /** Event key, e.g. `ev_xxx`. */
759
- event: string;
760
- /** API origin. Defaults to https://api.seatlayer.io. */
761
- apiBase?: string;
724
+
725
+ /**
726
+ * Stable checkout-handoff contract (P4). Passed as the THIRD argument to
727
+ * `onCheckout(hold, seats, handoff)` — additive, so the legacy `(hold, seats)`
728
+ * shape used by DesiPass web-v2 (SDK 0.7.3+) is untouched. This is the object to
729
+ * build your order against: it is self-contained (holdId, expiry, currency, and
730
+ * per-line tier + price) and never changes shape across minor releases.
731
+ */
732
+ interface CheckoutLineItem {
733
+ /** Seat label (or GA synthetic-unit label) — the stable booking identity. */
734
+ label: string;
762
735
  /**
763
- * Publishable account key for direct Public-sale bootstrap of a Platform/SDK
764
- * Event. It is never buyer identity and cannot request private inventory.
765
- * An explicit buyer-access provider/token below always takes precedence.
736
+ * Buyer-facing name (the designer's `displayLabel` override), when set.
737
+ * Show this in YOUR order summary; `label` stays the booking identity you
738
+ * pass to the book call. Absent = no override, fall back to `label`.
766
739
  */
767
- publicKey?: string;
740
+ displayLabel?: string;
768
741
  /**
769
- * Buyer access session provider the recommended way to render private
770
- * channel inventory (Sales Channels guide §6).
771
- *
772
- * Called with a `reason` whenever the SDK needs a bearer: first acquisition,
773
- * a near/actual expiry, a 401 `buyer_access_expired`, a realtime reconnect,
774
- * or `refreshAccess()`. It should POST to YOUR backend, which mints the
775
- * session with your secret key and returns `{ token, expiresAt }`.
742
+ * Buyer-facing type word override (seats.io "Displayed type", e.g. "Table",
743
+ * "Bench", "Box"), when the designer set one. Absent = the default word.
744
+ */
745
+ displayType?: string;
746
+ /** Chart object id (row/booth/GA area) the unit belongs to. */
747
+ objectId: string;
748
+ objectType: 'seat' | 'booth' | 'ga' | 'table';
749
+ categoryKey: string;
750
+ /** Chosen ticket tier id (Adult/Child/…), or null when the category has no tiers. */
751
+ tierId: string | null;
752
+ /** Unit price in MAJOR currency units (e.g. 45 = 45.00). Server-authoritative. */
753
+ unitPrice: number;
754
+ /** ISO-4217, resolved server-side (per-event override → org → USD). */
755
+ currency: string;
756
+ quantity: number;
757
+ /**
758
+ * The renderer's own seat id, when this line resolves to a placed object.
776
759
  *
777
- * The token lives in memory for the widget's lifetime and nowhere else: never
778
- * in storage, never in a URL, never in a log or an error message. Refresh
779
- * returns the same or a narrower scope the SDK never widens to Public sale
780
- * on its own, and a failed refresh stops the scoped operation rather than
781
- * retrying it anonymously.
760
+ * Present so a host can join a cart line to the same unit in `selection[]`
761
+ * by identity rather than by string label. Absent for a GA synthetic unit,
762
+ * which has no seat, and for a held line whose label no longer resolves.
782
763
  */
783
- buyerAccessTokenProvider?: BuyerAccessTokenProvider;
764
+ seatId?: string;
784
765
  /**
785
- * One-shot escape hatch for hosts that already own the session lifecycle.
766
+ * Buyer-facing spatial address the same three fields a tapped seat carries.
767
+ *
768
+ * Best Available and a resumed hold both produce lines that were never in
769
+ * the renderer selection, so a host joining cart→selection to render "Stalls
770
+ * D · Row C · Seat 6" got nothing back and the line arrived anonymous. The
771
+ * address now travels ON the line, which is where the host reads it anyway.
772
+ * Absent when the chart does not carry that level of address.
773
+ */
774
+ sectionLabel?: string;
775
+ rowLabel?: string;
776
+ seatNumber?: string;
777
+ }
778
+ interface CheckoutHandoff {
779
+ /** Server hold id — pass this to YOUR book call. */
780
+ holdId: string;
781
+ /** Epoch ms the hold expires (after any extensions). */
782
+ expiresAt: number;
783
+ /** ISO-4217 currency for the whole order. */
784
+ currency: string;
785
+ /** Priced line items (tier + unit price + currency), server-authoritative. */
786
+ lineItems: CheckoutLineItem[];
787
+ /** Convenience total in major units (Σ unitPrice × quantity). */
788
+ total: number;
789
+ }
790
+ /** Host-authoritative pricing — see {@link SeatPickerOptions.pricing}. */
791
+ interface SeatPickerPricing {
792
+ /** Unit prices by category key: a flat number, or `{ base, tiers: { tierId: price } }`. */
793
+ prices?: Record<string, number | {
794
+ base?: number;
795
+ tiers?: Record<string, number>;
796
+ }>;
797
+ /** Custom money renderer (e.g. `(n) => n + '€'`). Defaults to Intl currency formatting. */
798
+ formatter?: (amount: number, currency: string) => string;
799
+ }
800
+ /** Optional constraints for the server-authoritative best-available pick. */
801
+ interface SeatPickerBestAvailableOptions {
802
+ /** Prefer a contiguous premium block, falling back to the best overall block. */
803
+ preferPremium?: boolean;
804
+ /** Restrict the search to one configured chart zone. */
805
+ zoneId?: string;
806
+ }
807
+ /** Buyer-facing surface shown by the full picker widget. */
808
+ type SeatPickerBuyerView = 'map' | 'venue3d';
809
+ /** Optional camera intent when switching the buyer-facing surface. */
810
+ interface SeatPickerBuyerViewOptions {
811
+ /** Enter (or remain in) 3D and fly the camera to this seat id. */
812
+ flyToSeatId?: string;
813
+ /** When already in 3D, return the camera to the venue overview. */
814
+ resetView?: boolean;
815
+ }
816
+ /** Host theme overrides — any subset; unset keys fall back to the org's chart theme, then defaults. */
817
+ interface SeatPickerTheme {
818
+ /**
819
+ * Light, dark, or follow the reader's own `prefers-color-scheme`.
820
+ *
821
+ * OPT-IN. Omit it and the widget renders exactly what it always did: the
822
+ * chart's authored ground, the organizer's colours, your own overrides. Set
823
+ * it and the built-in token set for that side takes the GROUND roles —
824
+ * background, surface, ink, muted ink, hairlines, and the drawn map's own
825
+ * canvas — while the organizer's accent, font, radius and logo, and every
826
+ * colour you state explicitly below, stay exactly as they are.
827
+ *
828
+ * `'auto'` is re-read live: a buyer who flips their phone into dark mode
829
+ * mid-purchase keeps their seats and their hold.
830
+ */
831
+ mode?: ThemeMode;
832
+ /** Brand accent (CTA, active chips, hold pill). */
833
+ accent?: string;
834
+ /** Ink on the accent (button labels). */
835
+ accentInk?: string;
836
+ /** Widget background. */
837
+ background?: string;
838
+ /** Panel/card surface color. */
839
+ surface?: string;
840
+ /** Primary text color. */
841
+ text?: string;
842
+ /** Secondary text color. */
843
+ muted?: string;
844
+ /** Hairline/border color. */
845
+ line?: string;
846
+ /** Font stack for all widget chrome. */
847
+ fontFamily?: string;
848
+ /** Corner radius base (px). */
849
+ radius?: number;
850
+ /** Header logo URL (falls back to the org logo from the chart theme, then a monogram). */
851
+ logoUrl?: string;
852
+ /** Brand/event fallback name for the monogram. */
853
+ brandName?: string;
854
+ /**
855
+ * The DRAWN MAP, which the tokens above deliberately do not reach.
856
+ *
857
+ * Everything else on this interface is CSS: it re-inks panels, buttons and
858
+ * the sidebar. The seat map is a canvas, painted from the chart document's
859
+ * own `ChartTheme`, so a host could re-ink the whole widget and still be
860
+ * looking at somebody else's dark venue in the middle of it (which is exactly
861
+ * what SeatLayer's own light event-page palettes did, found 2026-08-07).
862
+ *
863
+ * Nested rather than flattened because `background` already means the
864
+ * WIDGET's background here and the canvas ground is a different surface —
865
+ * two things one word cannot carry.
866
+ *
867
+ * Set it only when you can vouch for the result: these colours are drawn
868
+ * behind and beside live seat statuses (held, sold, selected), and the map is
869
+ * the one part of this widget a buyer has to be able to read.
870
+ */
871
+ map?: PickerMapTheme;
872
+ }
873
+ /** Fine-grained WebMCP exposure — see `SeatPickerOptions.webMcp`. */
874
+ interface SeatPickerWebMcpOptions {
875
+ /** Also register `seatlayer_hold_selection`, which reserves the selection.
876
+ * Default false: holds stay a human tap. */
877
+ holds?: boolean;
878
+ /**
879
+ * Exact, trustworthy origins whose in-page agents may discover these tools
880
+ * across an iframe boundary, for example `https://tickets.example.com`.
881
+ *
882
+ * Same-origin and browser-provided agents do not need this. Cross-origin
883
+ * embeds also need `allow="tools"` on the iframe. Never derive this list from
884
+ * request data; keep it as an explicit integrator allowlist.
885
+ */
886
+ exposedTo?: string[];
887
+ }
888
+ interface SeatPickerOptions {
889
+ /** CSS selector or element to mount into. Omit when using SeatPicker.open(). */
890
+ container?: string | HTMLElement;
891
+ /** Event key, e.g. `ev_xxx`. */
892
+ event: string;
893
+ /** API origin. Defaults to https://api.seatlayer.io. */
894
+ apiBase?: string;
895
+ /**
896
+ * Custom data transport. Defaults to the CORS-trivial PubApi against
897
+ * `apiBase`. Inject to run the widget against another backend adapter (the
898
+ * SeatLayer dashboard's own transport) or a fully local mock (demos).
899
+ */
900
+ transport?: PickerTransport;
901
+ /**
902
+ * Publishable account key (`pk_live_…` / `pk_test_…`) for a public
903
+ * Platform/SDK Event. With no explicit buyer-access token/provider, the SDK
904
+ * uses it to bootstrap one origin-bound, Public-sale-only session directly
905
+ * from SeatLayer and receives chart + inventory in the same request.
906
+ *
907
+ * This is not buyer identity and cannot request private channels. An explicit
908
+ * `buyerAccessTokenProvider` or `buyerAccessToken` below always takes
909
+ * precedence for login, presale, partner or other scoped inventory.
910
+ */
911
+ publicKey?: string;
912
+ /**
913
+ * Buyer access session provider — the recommended way to show private channel
914
+ * inventory (Sales Channels guide §6).
915
+ *
916
+ * Called with a `reason` whenever the widget needs a bearer: first
917
+ * acquisition, a near/actual expiry, a 401 `buyer_access_expired`, a realtime
918
+ * reconnect, or `refreshAccess()`. It should POST to YOUR backend, which
919
+ * mints the session with your secret key and returns `{ token, expiresAt }`.
920
+ *
921
+ * The token lives in memory for the widget's lifetime and nowhere else: never
922
+ * in storage, never in a URL, never in a log or an error message. Refresh
923
+ * returns the same or a narrower scope; the widget never widens to Public
924
+ * sale on its own, and a failed refresh stops the scoped operation rather
925
+ * than retrying it anonymously. Any held seats stay held — a hold is
926
+ * relinquished by its own opaque capability, not by channel access, so
927
+ * losing access never strands inventory (guide §9).
928
+ *
929
+ * Ignored when a custom `transport` is supplied: that host owns its own
930
+ * credentials.
931
+ */
932
+ buyerAccessTokenProvider?: BuyerAccessTokenProvider;
933
+ /**
934
+ * One-shot escape hatch for hosts that already own the session lifecycle.
786
935
  * Cannot be renewed — when it lapses the widget reports `onAccessExpired`
787
936
  * and then `onAccessUnavailable`. Prefer `buyerAccessTokenProvider`.
788
937
  */
@@ -793,1444 +942,1441 @@ interface SeatingChartOptions {
793
942
  selectedObjects?: string[];
794
943
  /** Object ids or public labels the buyer may select. Omit for all free objects. */
795
944
  selectableObjects?: string[] | null;
796
- /** Require exactly this many seated/table guest units before the selection is valid. */
945
+ /** Require exactly this many total seated/table/GA guest units before checkout. */
797
946
  numberOfPlacesToSelect?: number;
798
- /** Optional local selection guards, enforced again before hold. */
947
+ /** Optional sale guards evaluated after every change and enforced before a
948
+ * hold: minimum quantity, consecutive seats, and/or no stranded singles. */
799
949
  selectionValidators?: PickerSelectionValidator[];
800
950
  /**
801
- * BCP 47 language for the widget UI `'de'`, `'es-MX'`, etc. Falls back to
802
- * the browser language, then English. 37 languages ship. The German
803
- * bundle (etc.) is fetched on demand so unused languages cost nothing.
951
+ * BCP 47 language for the widget UI. 37 languages ship; omit this and each
952
+ * buyer gets their own `navigator.languages`, falling back to English.
953
+ * Read once at render — there is no way to change it on a live chart.
804
954
  */
805
955
  locale?: string;
806
- /**
807
- * Per-key string overrides layered over the active locale — white-label copy
808
- * without shipping a whole bundle, e.g. `{ 'map.fromPrice': 'ab {price}' }`.
809
- */
956
+ /** Per-key string overrides layered over the active locale. */
810
957
  messages?: Record<string, string>;
811
958
  /**
812
- * Show an in-chart language switcher offering exactly these languages.
959
+ * Show an in-picker language switcher offering exactly these languages.
813
960
  *
814
- * Opt-in and curated on purpose: the chart sits inside someone else's page,
815
- * a multilingual host already has its own switcher, and two that can
816
- * disagree is worse than either alone. A curated list also beats all 37 — a
817
- * German venue wants de/en/pl in the menu, not Welsh. Fewer than two
818
- * resolvable entries renders nothing.
961
+ * Opt-in and curated on purpose. Omit it and there is no control: the widget
962
+ * sits inside someone else's page, and a multilingual host site already has
963
+ * its own switcher — two that can disagree is worse than either alone. A
964
+ * curated list also beats offering all 37: a German venue wants de/en/pl in
965
+ * the menu, not Welsh.
966
+ *
967
+ * Switching keeps the buyer's selection; it does not re-render the chart.
819
968
  */
820
969
  languages?: string[];
821
- /** ISO 4217 currency for on-map prices (default USD). */
970
+ /** ISO 4217 currency fallback (the org/event currency on the chart wins). */
822
971
  currency?: string;
972
+ /** Colorblind-safe rendering (Okabe-Ito palette, hollow booked seats). */
973
+ colorblindSafe?: boolean;
823
974
  /**
824
- * Colorblind-safe rendering: category hues switch to an Okabe-Ito palette
825
- * and booked seats render hollow, so state never relies on hue alone.
826
- * Toggleable later with setColorblindSafe().
975
+ * How a big chart's seats arrive: 'progressive' (default) shows the venue's
976
+ * sections as soon as the chart lands and fills the seats in a section at a
977
+ * time, nearest the viewport first; 'plain' builds the whole scene in one
978
+ * step. Small charts are built in one step either way.
827
979
  */
828
- colorblindSafe?: boolean;
829
- /** Host-authoritative colors for the drawn canvas, separate from native chrome. */
830
- mapTheme?: PickerMapTheme;
831
- /** Initial canvas projection.
832
- * @deprecated `'isometric'` and `'perspective'` are retired in favour of the
833
- * real 3D venue view (`setBuyerView('venue3d')`); they remain accepted for
834
- * source compatibility and will be removed in the next major. Use `'flat'`. */
980
+ loadingReveal?: 'progressive' | 'plain';
981
+ /** Initial map projection. Buyers now toggle **Map (flat 2D) + 3D** only; the
982
+ * legacy `perspective` (2.5D) value is still ACCEPTED for source compatibility
983
+ * but is deprecated — it is coerced to `flat` with a one-time console warning.
984
+ * The 3D venue view is entered from the Map/3D control, not this option. */
835
985
  initialView?: RendererViewMode;
836
- /** Offer the real, lazy-loaded interactive venue view. Default true. */
986
+ /**
987
+ * Offer the interactive 3D venue view (Map | 3D toggle + a "See it in 3D"
988
+ * action on the seat-confirm card). Default true. The 3D button is shown only
989
+ * when this is not false AND the browser exposes WebGL2; there are ZERO GL
990
+ * bytes on the wire until the buyer actually opens 3D (the OGL chunk is
991
+ * dynamically imported on first use). Set false for embed hosts that must
992
+ * stay strictly 2D. */
837
993
  enable3D?: boolean;
838
- /** Offer authored/generated view-from-seat inspection. Default true. */
839
- enableSeatView?: boolean;
840
- /** Optional device-specific ceiling for offering the venue WebGL scene. */
994
+ /**
995
+ * Seat-count ceiling above which 3D is not offered. Default: 60,000 seats on
996
+ * desktop, reduced to 30,000 on a device that reports itself as small/low-core.
997
+ *
998
+ * The 53,018-seat evidence is desktop-only; that scale remains unmeasured on
999
+ * phones, which is why the small-device default stays conservative. The 3D
1000
+ * scene holds every seat resident until a streaming rung exists. */
841
1001
  max3DSeats?: number;
842
1002
  /**
843
- * Built-in seat tooltip on mouse hover (seat · category · price · status).
844
- * Rendered inside the widget so every host gets it; default true. Turn off
845
- * to draw your own popover from onSeatHover.
1003
+ * Expose this picker to an AI agent running in the buyer's browser (WebMCP).
1004
+ *
1005
+ * Off by default and inert everywhere the API is absent (every WebView, every
1006
+ * browser without it) — an integrator opts in per picker. `true` registers
1007
+ * four tools: describe the event, search for seats (read-only — it never
1008
+ * touches inventory), select seats on the map, and read the basket.
1009
+ *
1010
+ * `{ holds: true }` adds a fifth that reserves the selection, i.e. presses
1011
+ * the checkout button. That is deliberately separate: a hold takes real
1012
+ * inventory off the market, and an agent looping over events must not be able
1013
+ * to lock a venue. Leave it off unless you want an agent to be able to commit.
1014
+ *
1015
+ * Cross-origin embeds: the HOST page must mark the iframe `allow="tools"`
1016
+ * and the picker must list that host's exact origin in `exposedTo`, or an
1017
+ * in-page agent in the host cannot discover the frame's tools.
846
1018
  */
847
- seatTooltip?: boolean;
848
- /** Internal hosted-runtime control. Defaults true for direct web embeds. */
849
- showTestModeIndicator?: boolean;
850
- /** Internal hosted-runtime control. Defaults true, subject to white-label entitlement. */
851
- showAttribution?: boolean;
1019
+ webMcp?: boolean | SeatPickerWebMcpOptions;
852
1020
  /**
853
- * Seat hover with everything a popover needs (category label/color, resolved
854
- * tier-aware price, live status, currency); null on hover-out. Fires whether
855
- * or not the built-in tooltip is enabled.
1021
+ * Fires when the buyer enters/leaves 3D or targets a seat there. Hosts can
1022
+ * mirror this small, non-sensitive state into a shareable URL.
856
1023
  */
857
- onSeatHover?: (details: SeatHoverDetails | null) => void;
858
- onSelectionChange?: (seats: SelectedSeat[]) => void;
859
- /** Selection-rule state after each selection change. */
860
- onSelectionValidityChange?: (state: PickerSelectionValidity) => void;
861
- /** The configured selection rules have just become valid. */
862
- onSelectionValid?: (seats: SelectedSeat[]) => void;
863
- /** The configured selection rules have just become invalid. */
864
- onSelectionInvalid?: (state: PickerSelectionValidity) => void;
865
- /** A manual or programmatic selection reached the active maximum. */
866
- onSelectionLimit?: (maxSelection: number) => void;
867
- onHold?: (result: HoldResult) => void;
868
- /** A prior active hold was restored with resumeHold(). */
869
- onHoldRestored?: (result: HoldResult) => void;
870
- onHoldExpired?: () => void;
871
- onGAClick?: (area: GAAreaAvailability) => void;
1024
+ onBuyerViewChange?: (state: {
1025
+ view: SeatPickerBuyerView;
1026
+ seatId?: string;
1027
+ }) => void;
872
1028
  /**
873
- * Mirror of {@link SeatPickerOptions.onGAPrompt}. The low-level chart draws no
874
- * quantity UI of its own, so this fires purely as the "the buyer wants to pick
875
- * a quantity here" signal, with the same pre-computed `min`/`max`/`tiers` the
876
- * full widget would have used; the return value is accepted and ignored.
877
- */
878
- onGAPrompt?: (prompt: GAPromptRequest) => boolean | void;
1029
+ * Optional analytics sink for the widget's journey and performance events.
1030
+ * Emits `chart_rendered` once per successful load with total time, step
1031
+ * timings and anonymous chart-size counts, plus the 3D venue-view journey
1032
+ * (`3d_opened`, `3d_orbit_engaged`, `3d_seat_picked`,
1033
+ * `3d_cinematic_played`/`_skipped`/`_cancelled`, panorama outcomes, and WebGL
1034
+ * context loss/recovery)
1035
+ * with `{ surface: 'buyer' }` merged into the props. A throwing sink never
1036
+ * breaks the widget. Route it to your product analytics (e.g. PostHog). */
1037
+ onAnalytics?: (event: string, props: Record<string, unknown>) => void;
879
1038
  /**
880
- * The buyer access session lapsed. `refreshed` says whether the provider
881
- * already recovered it — false means private inventory is now unavailable and
882
- * `onAccessUnavailable` follows. Distinct from `onError` on purpose: this is
883
- * never a network failure (guide §10).
1039
+ * Every seat of a progressively revealed chart is now built and painted.
1040
+ *
1041
+ * `render()` resolves with the section shells and the nearest seats — the
1042
+ * rest arrive in idle slices. A host that needs "the whole venue is on
1043
+ * screen" waits for this rather than for `render()`. Fires exactly once per
1044
+ * chart, and never for a chart small enough to build in one step (there is
1045
+ * nothing to wait for: `render()` already was that moment).
884
1046
  */
885
- onAccessExpired?: (event: BuyerAccessExpiredEvent) => void;
1047
+ onSeatsRevealed?: () => void;
886
1048
  /**
887
- * Private inventory is unavailable and refreshing will not fix it — revoked,
888
- * paused, wrong origin/event/mode, or the provider failed. Carries a reason,
889
- * never a channel name, id, colour or count.
1049
+ * Optional organiser-defined campaign/referral label (max 120 characters).
1050
+ * On the public SeatLayer transport it is frozen onto the hold, then copied
1051
+ * to the Managed order, organiser reporting and booking webhook. It grants no
1052
+ * inventory access and is ignored by host-supplied transports.
890
1053
  */
891
- onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
1054
+ referral?: string;
892
1055
  /**
893
- * Selected-but-unheld units stopped being selectable someone else took
894
- * them, or an allocation change moved them out of this buyer's scope. The
895
- * widget has already dropped them from the selection.
1056
+ * Hide the "Powered by SeatLayer" attribution badge in the side panel foot.
1057
+ * The chart theme's own `hideBadge` flag (paid orgs) also hides it the badge
1058
+ * is shown only when BOTH this option and the theme flag are unset/false.
896
1059
  */
897
- onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
898
- onError?: (err: unknown) => void;
1060
+ hideBadge?: boolean;
899
1061
  /**
900
- * What the BUYER sees when the chart cannot load.
901
- *
902
- * `'message'` (the default) renders a plain, styleable notice with a Try
903
- * again button. This used to be silent unconditionally: `render()` returned
904
- * with an EMPTY mounted div and only `onError` fired, so a host that had not
905
- * wired `onError` — or had wired it to a logger — showed buyers a blank
906
- * rectangle where the seat map belongs, on the host's own domain, which
907
- * reads as a broken website rather than a temporary fault. `SeatPicker` has
908
- * always failed loud with a retry; this is the embed class catching up.
1062
+ * Hide the picker's event identity (logo, event name and venue/date metadata)
1063
+ * when the host surface already presents the same event heading. The hold
1064
+ * timer, sales status and modal close controls remain available. The identity
1065
+ * is restored automatically while the picker is full screen so it never
1066
+ * loses context on a small device or an expanded map. Default false.
909
1067
  *
910
- * `'none'` restores the silent behaviour for hosts that render their own
911
- * failure UI from `onError`.
1068
+ * A mounted host can update this through `setEventDetailsHidden()` when its
1069
+ * own event chrome arrives asynchronously.
912
1070
  */
913
- errorDisplay?: 'message' | 'none';
1071
+ hideEventDetails?: boolean;
914
1072
  /**
915
- * Multi-floor charts only: fires when the buyer taps a deck in the stacked
916
- * 3D view, after the picker switches to that floor — lets the host page sync
917
- * its own floor UI (tabs, labels) with the map.
1073
+ * Start the wide-layout ticket panel collapsed so the map owns the full
1074
+ * width (map-first hosts, small embeds). The buyer reopens it with the
1075
+ * "Tickets" pill beside Map|3D, and it opens itself the moment a seat lands
1076
+ * in the cart — a collapsed panel must never hide a checkout. Narrow
1077
+ * layouts ignore this (the bottom sheet is already the collapse). A mounted
1078
+ * host can drive it later through `setPanelCollapsed()`. Default false.
918
1079
  */
919
- onDeckTap?: (floorId: string) => void;
920
- /** Big-venue section focus state for host-owned navigation chrome. */
921
- onSectionFocus?: (summary: SectionSummary | null) => void;
922
- /** Availability changed after a live frame or explicit refresh. */
923
- onStatusChange?: () => void;
924
- /** The server closed sales for this event. */
925
- onSalesClosed?: () => void;
926
- /** A programmatic/native map-control action changed serializable map state. */
927
- onMapStateChange?: () => void;
928
- /** rAF-coalesced camera-frame signal for in-page overlays; not native state. */
929
- onViewChange?: () => void;
930
- /** Real buyer-view transitions and 3D camera targets. */
931
- onBuyerViewChange?: (state: {
932
- view: SeatingChartBuyerView;
933
- seatId?: string;
934
- }) => void;
1080
+ panelCollapsed?: boolean;
1081
+ /** Host theme overrides see SeatPickerTheme. */
1082
+ theme?: SeatPickerTheme;
935
1083
  /**
936
- * Non-blocking, localized selection advice currently the orphan-seat hint
937
- * (the selection would strand a single free seat between taken neighbors).
938
- * `null` clears it. Purely informational; nothing is ever prevented.
1084
+ * Host-authoritative pricing. When your shop charges different prices than
1085
+ * the chart's stored category prices, pass them here so the buyer sees the
1086
+ * price they will actually pay on the map tooltip, confirm popover, price
1087
+ * panel, tray, totals, and in the checkout handoff's line items. Keyed by
1088
+ * category key; per-tier overrides nest under `tiers`. Unlisted categories
1089
+ * fall back to the chart price.
939
1090
  */
940
- onHint?: (message: string | null) => void;
941
- }
942
- declare class SeatingChart {
943
- private readonly opts;
944
- private readonly controller;
945
- /** Publishable key used only by the public-only direct bootstrap path. */
946
- readonly publicKey?: string;
947
- private mount;
948
- private hostEl;
1091
+ pricing?: SeatPickerPricing;
949
1092
  /**
950
- * Native hosts can render a Flutter/Swift/Kotlin decision surface above this
951
- * DOM tree. UIKit platform views still participate in hit testing beneath a
952
- * composited native overlay, so the runtime must be able to make its own DOM
953
- * inert rather than relying on the host's visual stacking alone.
1093
+ * Fires when the server-resolved active offer changes. Hosted event pages use
1094
+ * this to keep their headline, sticky bar and the canonical picker on the
1095
+ * same live fact. The picker remains fully functional when it is omitted.
954
1096
  */
955
- private interactionEnabled_;
956
- /** The organizer's language for this event, learned when the chart resolves. */
957
- private eventLocale;
958
- private rendered;
959
- private mode_;
960
- private eventInfo;
961
- private maxSelection_;
962
- private colorblindSafe_;
963
- private categoryFilter_;
964
- private accessibilityFilter_;
965
- private hideLimitedView_;
966
- private focusedSection_;
967
- /** Last rung already exposed to native chrome; raw camera frames stay in-canvas. */
968
- private lastReportedViewRung_;
969
- private accessState_;
970
- private tipEl;
971
- private tipPos;
972
- private onTipMove;
973
- /** Null for the ordinary public chart — the tokenless path is untouched. */
974
- private readonly access;
975
- private readonly api;
976
- private readonly apiBase;
977
- private readonly buyerAssetUrls;
978
- private readonly immersive;
979
- private realtime;
980
- constructor(options: SeatingChartOptions);
1097
+ onOfferAvailabilityChange?: (availability: TicketOfferAvailability | null) => void;
1098
+ /** Hold TTL in ms passed to hold(); server clamps to its own limits. */
1099
+ holdTtlMs?: number;
981
1100
  /**
982
- * Camera x/y/scale is renderer-local and deliberately absent from the native
983
- * picker snapshot. Keep pan and pinch frames on the canvas; only a semantic
984
- * rung transition needs to wake native chrome.
1101
+ * An opaque hold id supplied by the host to restore after navigation. It is
1102
+ * verified against the event and active server state before anything renders
1103
+ * as owned by this buyer.
985
1104
  */
986
- private handleViewChange;
987
- /** Record the current rung before publishing an explicit state mutation. */
988
- private notifyMapStateChange;
989
- /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
990
- render(): Promise<this>;
1105
+ initialHoldId?: string;
991
1106
  /**
992
- * Attribution badge pinned to the embed's bottom-right, linking to
993
- * seatlayer.io. Rendered as an absolutely-positioned overlay with
994
- * self-contained inline styles the SDK embed ships no widget CSS, and an
995
- * overlay keeps it out of the layout flow so it never disturbs the SDK v0.22
996
- * fill-height resize contract. Mirrors the full widget's mark + wordmark and
997
- * reuses the `picker.poweredBy` i18n string.
1107
+ * Automatically remember the active hold id in sessionStorage and restore it
1108
+ * when this event's picker mounts again. Default true. Set false when the host
1109
+ * owns hold persistence and supplies initialHoldId itself.
998
1110
  */
999
- private buildBadge;
1000
- private placeTooltip;
1001
- private updateTooltip;
1111
+ restoreHold?: boolean;
1002
1112
  /**
1003
- * Whether the SERVED event is a live or a test event (`sk_test_` keys create
1004
- * test events, which never book real inventory). `null` before render()
1005
- * resolves the mode comes from the server with the chart, not from options.
1006
- *
1007
- * The widget already surfaces this visually with the test-mode ribbon; this
1008
- * getter is for hosts that draw their own chrome — notably a native WebView
1009
- * wrapper, which must be able to tell an integrator that the build they are
1010
- * about to ship is pointed at a test event.
1113
+ * Render the real chart and live seat statuses without allowing selection,
1114
+ * holds or checkout. This is for venue previews and pre-sale Website pages;
1115
+ * it is enforced by the widget even if the event later opens while mounted.
1116
+ * Default false.
1011
1117
  */
1118
+ readOnly?: boolean;
1012
1119
  /**
1013
- * The opt-in language switcher, offering exactly the languages the host
1014
- * named. Absent unless `languages` lists at least two a menu with one
1015
- * entry is furniture, and an unrequested one duplicates the switcher a
1016
- * multilingual host site already has.
1017
- *
1018
- * Each language names itself (`LOCALE_NAMES`): a reader scanning for their
1019
- * own language finds "Deutsch", never "German". An English list is
1020
- * unreadable to exactly the people who need it.
1120
+ * Confirm mode: tapping a seat shows a confirmation card with section, row,
1121
+ * seat, category, price and Select/Cancel before it enters the tray. Default
1122
+ * true for the full buyer picker; set false only when the host supplies its
1123
+ * own equivalent confirmation UI.
1021
1124
  */
1022
- private buildLanguageSwitcher;
1125
+ confirmSelection?: boolean;
1023
1126
  /**
1024
- * Change the language of a LIVE chart, keeping the buyer's selection.
1127
+ * Offer a "View from seat" 360° preview (confirm popover + tray chips). The
1128
+ * panorama is generated from the chart geometry, or the organizer's uploaded
1129
+ * photo when a seat carries one. Default true; set false to hide the affordance.
1130
+ */
1131
+ seatView?: boolean;
1132
+ /**
1133
+ * WHERE the buyer goes once their seats are held. Default `'handoff'`.
1025
1134
  *
1026
- * `locale` is otherwise read once at render, which is fine for a page that
1027
- * knows its language up front and useless for one with a language switcher.
1028
- * The competing embed API can only `rerender()`, and that clears the
1029
- * selection losing someone's seats because they changed language is not an
1030
- * acceptable trade, so this rebuilds the map through `refreshMapCopy()`,
1031
- * which restores the selection on the far side.
1135
+ * 'handoff' (default, and every integration that has ever existed) the
1136
+ * widget fires {@link onCheckout} with a holdId and priced line
1137
+ * items, and YOUR server takes the money. Nothing about this path
1138
+ * changes, and no payment code is even downloaded.
1139
+ * 'hosted' the widget takes the money through the gateway the ORGANIZER
1140
+ * connected, on their account — the "sell tickets with no
1141
+ * backend" path. Requires the org to be on hosted checkout and
1142
+ * the event to have a gateway assigned; when it does not, this
1143
+ * falls back to `'handoff'` for that buyer rather than dead-ending
1144
+ * them, and reports why through {@link onCheckoutUnavailable}.
1032
1145
  *
1033
- * Returns the locale that actually became active: an unsupported or
1034
- * not-yet-translated tag resolves to English rather than throwing, exactly as
1035
- * it does at first render.
1146
+ * Named for the destination rather than as a boolean flag because there is a
1147
+ * real third answer coming and `hostedCheckout: true` would have no room for
1148
+ * it; spelling the default out also makes a host's intent legible in their own
1149
+ * source instead of hiding it in an absent option.
1150
+ *
1151
+ * TWO THINGS ARE WORTH KNOWING BEFORE YOU SWITCH THIS ON:
1152
+ *
1153
+ * 1. It needs the widget's own transport. A host-supplied `transport` owns its
1154
+ * credentials and its backend, so hosted checkout stays off there (with one
1155
+ * console warning) rather than reaching past it to api.seatlayer.io.
1156
+ * 2. WHERE A HOSTED GATEWAY RETURNS THE BUYER is settled by {@link returnUrl}
1157
+ * and by the organizer. Without one — or from an origin the organizer has
1158
+ * not declared — the buyer comes back to SeatLayer's own buyer page and is
1159
+ * confirmed THERE, not in this widget. Declare the embedding site under
1160
+ * Embed domains in the dashboard and pass `returnUrl`, and the buyer
1161
+ * returns to your page instead. In-page gateways never navigate away at
1162
+ * all, so they are unaffected either way.
1036
1163
  */
1037
- setLocale(next: string | null | undefined): Promise<string>;
1038
- getMode(): 'live' | 'test' | null;
1039
- /** Atomic, token-free display state for native-owned picker chrome. */
1040
- getPickerState(): SeatingChartPickerState;
1041
- getCategoryAvailability(): Record<string, number>;
1042
- getBestAvailableZones(): Array<{
1043
- id: string;
1044
- label: string;
1045
- }>;
1046
- getActiveFloorId(): string | null;
1047
- getRung(): LodRung;
1048
- getFocusedSection(): string | null;
1049
- focusSection(sectionId: string): void;
1050
- overview(): void;
1051
- setRung(rung: LodRung): void;
1052
- setCategoryFilter(categoryKeys: string[] | null, focus?: boolean): void;
1053
- setAccessibilityFilter(types: string[] | null): void;
1054
- setCommercialLimitedFilter(on: boolean): void;
1055
- /** Current selection with prices resolved from the chart categories. */
1056
- getSelection(): SelectedSeat[];
1057
- /** Select free objects by engine id or public label. Returns newly selected objects. */
1058
- selectObjects(objects: string[]): SelectedSeat[];
1059
- /** Deselect objects by engine id or public label. */
1060
- deselectObjects(objects: string[]): void;
1061
- /** Clear every unheld selection. */
1062
- clearSelection(): void;
1063
- /** Select every selectable object in the named categories. */
1064
- selectCategories(categoryKeys: string[]): SelectedSeat[];
1065
- /** Deselect every selected object in the named categories. */
1066
- deselectCategories(categoryKeys: string[]): void;
1067
- /** Replace the buyer-selectable object allow-list without remounting. */
1068
- setSelectableObjects(objects: string[] | null): void;
1069
- /** Change the selection cap without remounting. */
1070
- setMaxSelection(maxSelection: number): void;
1071
- /** Current selection-rule validity, or null when no rules were configured. */
1072
- getSelectionValidity(): PickerSelectionValidity | null;
1073
- /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */
1074
- hold(options?: {
1075
- ttlMs?: number;
1076
- }): Promise<HoldResult | null>;
1164
+ checkout?: 'handoff' | 'hosted';
1077
1165
  /**
1078
- * @internal Like {@link hold} but RE-THROWS the structured API error (409
1079
- * `reason`/`code` + `conflicts`) instead of swallowing it into `onError` +
1080
- * `null`. The native WebView host adapter needs the throw so it can answer the
1081
- * originating command with a correlated error carrying the SPECIFIC reason
1082
- * (`sold_out` vs `not_enough_together`); the public method above keeps the
1083
- * catch-and-onError contract that direct web consumers rely on. Not a stable
1084
- * part of the embed API.
1166
+ * Where a redirecting gateway should send the buyer back to, for
1167
+ * `checkout: 'hosted'`.
1168
+ *
1169
+ * The server keeps this URL's path and query and adds only a non-secret
1170
+ * `seatlayer_checkout=success|cancelled` outcome. The SDK keeps the order id
1171
+ * in this tab's sessionStorage before leaving for the gateway. Point this at
1172
+ * a page on the same origin (often `window.location.href`) and mount a picker
1173
+ * there so it can resume without putting the order capability in a URL.
1174
+ *
1175
+ * It is validated, not trusted: the organizer declares their embed origins
1176
+ * in the dashboard, and an undeclared origin is ignored rather than
1177
+ * refused — the sale still completes, the buyer just finishes on
1178
+ * SeatLayer's page. Supplying a URL therefore cannot authorize it, which is
1179
+ * what stops a copied snippet from redirecting a paid buyer anywhere it
1180
+ * likes.
1181
+ * As with every script embed, code already running on the host origin can
1182
+ * read that origin's sessionStorage; iframe embedding provides SOP isolation.
1085
1183
  */
1086
- holdOrThrow(options?: {
1087
- ttlMs?: number;
1088
- }): Promise<HoldResult | null>;
1089
- /** Restore an active hold by its opaque id without extending its expiry. */
1090
- resumeHold(holdId: string): Promise<HoldResult | null>;
1091
- /** @internal Throwing variant of {@link resumeHold} for the native host adapter. See {@link holdOrThrow}. */
1092
- resumeHoldOrThrow(holdId: string): Promise<HoldResult | null>;
1184
+ returnUrl?: string;
1093
1185
  /**
1094
- * Push the OPEN hold's expiry out ("need more time?"). Resolves the refreshed
1095
- * hold, or `null` when there is nothing held or the server refused (the hold
1096
- * is gone, already expired, or at its renewal cap) refusal is a normal
1097
- * outcome, not an error, so the host decides the copy. The client-side expiry
1098
- * timer is re-armed to match, so `onHoldExpired` won't fire early.
1186
+ * Buyer pressed the CTA and the hold succeeded hand off to YOUR checkout.
1187
+ * `hold` and `seats` are the legacy args (unchanged since 0.6). `handoff` (P4)
1188
+ * is the stable, self-contained {@link CheckoutHandoff} to build your order
1189
+ * against holdId, expiry, currency and priced line items. Prefer it.
1190
+ *
1191
+ * Under `checkout: 'hosted'` this fires ONLY when hosted checkout cannot run
1192
+ * for this event, so a host can keep one code path for both. It never fires
1193
+ * alongside a payment the widget is taking itself.
1099
1194
  */
1100
- extendHold(ttlMs?: number): Promise<HoldResult | null>;
1101
- /** Current active hold known to this chart, if any. */
1102
- getCurrentHold(): HoldResult | null;
1103
- getGAAreas(): GAAreaAvailability[];
1104
- holdGA(areaId: string, qty: number, options?: {
1105
- tierId?: string | null;
1106
- ttlMs?: number;
1107
- }): Promise<HoldResult | null>;
1108
- /** @internal Throwing variant of {@link holdGA} for the native host adapter. See {@link holdOrThrow}. */
1109
- holdGAOrThrow(areaId: string, qty: number, options?: {
1110
- tierId?: string | null;
1111
- ttlMs?: number;
1112
- }): Promise<HoldResult | null>;
1195
+ onCheckout?: (hold: HoldResult, seats: PickerSeat[], handoff: CheckoutHandoff) => void;
1113
1196
  /**
1114
- * Ask the server for the `qty` best free seats and hold them atomically.
1115
- * `options.ttlMs` sets the checkout window exactly like {@link hold}; omit it
1116
- * and the server falls back to the event setting, then its own default.
1197
+ * `checkout: 'hosted'` was asked for and this event cannot take money.
1198
+ * The seats ARE held — the buyer is mid-journey so this is a routing
1199
+ * decision, not an error, and it is never collapsed into {@link onError}.
1200
+ *
1201
+ * `reason` carries the server's three-way answer verbatim, because two of the
1202
+ * three give opposite advice: `payments_off_for_event` means the organizer
1203
+ * deliberately does not sell this event online (nothing is wrong), while
1204
+ * `unavailable_for_event` means they switched it on and it is broken. Anything
1205
+ * unreadable — a failed lookup, an older worker — reads as `not_configured`,
1206
+ * which asserts the least about them.
1207
+ *
1208
+ * `onCheckout` fires immediately after this with the same hold. Supply either
1209
+ * (or both) and you own the next screen; supply NEITHER and the widget shows
1210
+ * the buyer an honest card of its own rather than swallowing the press.
1117
1211
  */
1118
- bestAvailable(qty: number, categoryKey?: string, options?: {
1119
- zoneId?: string;
1120
- preferPremium?: boolean;
1121
- ttlMs?: number;
1122
- }): Promise<BestAvailableResult | null>;
1123
- /** @internal Throwing variant of {@link bestAvailable} for the native host adapter. See {@link holdOrThrow}. */
1124
- bestAvailableOrThrow(qty: number, categoryKey?: string, options?: {
1125
- zoneId?: string;
1126
- preferPremium?: boolean;
1127
- ttlMs?: number;
1128
- }): Promise<BestAvailableResult | null>;
1212
+ onCheckoutUnavailable?: (event: {
1213
+ reason: PaymentOptionsReason;
1214
+ handoff: CheckoutHandoff;
1215
+ }) => void;
1129
1216
  /**
1130
- * Choose a ticket tier for a selected seat (e.g. Adult Child). The seat's
1131
- * available `tiers` are on each `SelectedSeat` from `getSelection()` /
1132
- * `onSelectionChange`. Re-emits the selection with the new tier + price, and
1133
- * the tier rides along in the next `hold()` / `onHold` per seat. `tierId=null`
1134
- * reverts to the default tier.
1217
+ * `checkout: 'hosted'` only the gateway's webhook landed and the order is
1218
+ * PAID. The one signal a host with no backend actually needs, and the only
1219
+ * place a receipt can come from on a page that has no server of its own.
1220
+ *
1221
+ * Distinct from {@link onBooked}, which reports the same sale seen from the
1222
+ * seat map over the realtime channel and cannot fire at all for a buyer whose
1223
+ * widget was torn down by a redirect to the gateway.
1135
1224
  */
1136
- setSeatTier(seatId: string, tierId: string | null): void;
1137
- /** Confirm/update an unheld variable table's guest quantity locally. */
1138
- setTableQuantity(label: string, quantity: number): boolean;
1225
+ onOrderConfirmed?: (order: OrderStatusResult) => void;
1139
1226
  /**
1140
- * Atomically replace an active variable-table hold with a new guest count.
1141
- * Returns null for an inapplicable/invalid quantity or a refused replacement.
1227
+ * The held seats were BOOKED (P4) your server completed payment and the
1228
+ * booking landed over the realtime channel while the widget was still open.
1229
+ * The widget shows a success state; use this to advance your own UI (receipt,
1230
+ * redirect). Fires once per hold.
1142
1231
  */
1143
- replaceTableQuantity(label: string, quantity: number, ttlMs?: number): Promise<HoldResult | null>;
1144
- /** @internal Throwing variant used by the correlated native bridge. */
1145
- replaceTableQuantityOrThrow(label: string, quantity: number, ttlMs?: number): Promise<HoldResult | null>;
1232
+ onBooked?: (handoff: CheckoutHandoff) => void;
1233
+ /** Selection changed (tap or best-available). */
1234
+ onSelectionChange?: (seats: PickerSeat[]) => void;
1235
+ /** Selection-rule state after each selection change. */
1236
+ onSelectionValidityChange?: (state: PickerSelectionValidity) => void;
1237
+ /** The configured selection rules have just become valid. */
1238
+ onSelectionValid?: (seats: PickerSeat[]) => void;
1239
+ /** The configured selection rules have just become invalid. */
1240
+ onSelectionInvalid?: (state: PickerSelectionValidity) => void;
1241
+ /** A selection attempt reached the active maximum. */
1242
+ onSelectionLimit?: (maxSelection: number) => void;
1146
1243
  /**
1147
- * Floors of a multi-floor chart `[{ id, name }]` (single-floor charts
1148
- * return one entry; empty before render()). Pair with setFloor() to build a
1149
- * host-side floor switcher.
1244
+ * Active hold changed because it was created, restored, extended, partially
1245
+ * released, or fully released. Hosts should persist this state for route
1246
+ * navigation and clear their checkout cart when `hold` becomes null.
1150
1247
  */
1151
- getFloors(): {
1152
- id: string;
1153
- name: string;
1154
- }[];
1155
- /** Switch the shown floor (2D). Warns + no-ops on single-floor charts. */
1156
- setFloor(floorId: string): void;
1157
- /** Toggle colorblind-safe rendering at runtime (see options.colorblindSafe). */
1158
- setColorblindSafe(on: boolean): void;
1159
- /** Switch the 2D canvas projection.
1160
- * @deprecated `'isometric'` and `'perspective'` are retired in favour of the
1161
- * real 3D venue view (`setBuyerView('venue3d')`); accepted for source
1162
- * compatibility until the next major. */
1163
- setViewMode(mode: RendererViewMode): void;
1164
- /** Current canvas projection. */
1165
- getViewMode(): RendererViewMode;
1166
- /** Zoom in one step (same increment as the wheel/pinch gesture). */
1167
- zoomIn(): void;
1168
- /** Zoom out one step. */
1169
- zoomOut(): void;
1170
- /** Reset the camera so the whole chart fits the container. */
1171
- zoomToFit(): void;
1172
- /** Enter or leave the same real lazy-loaded venue scene as SeatPicker. */
1173
- setBuyerView(view: SeatingChartBuyerView, options?: {
1174
- flyToSeatId?: string;
1175
- resetView?: boolean;
1176
- }): Promise<void>;
1177
- getBuyerView(): SeatingChartBuyerView;
1178
- /** Open an authored or chart-derived 360° view for one physical seat. */
1179
- openSeatView(seatId: string): Promise<void>;
1180
- /** Explicit Rotate / Move control for native and custom picker chrome. */
1181
- setVenue3DNavigationMode(mode: SeatingChartNavigationMode): void;
1248
+ onHoldChange?: (hold: HoldResult | null, seats: PickerSeat[], handoff: CheckoutHandoff | null) => void;
1249
+ /** The open hold expired server-side (widget already reset itself). */
1250
+ onHoldExpired?: () => void;
1251
+ /** A prior active hold was verified and restored into the tray. */
1252
+ onHoldRestored?: (hold: HoldResult, seats: PickerSeat[], handoff: CheckoutHandoff) => void;
1253
+ /** Modal only: the buyer closed the picker (ESC / scrim / ✕). */
1254
+ onClose?: () => void;
1182
1255
  /**
1183
- * Enable or suppress all buyer input inside this chart without unmounting it.
1184
- *
1185
- * Native SDKs use this while a native confirmation, quantity prompt, loading
1186
- * surface or error surface owns the chart area. Disabling input in the DOM is
1187
- * intentional: an iOS WKWebView can receive the same physical tap beneath a
1188
- * Flutter overlay even when the Flutter widget is wrapped in IgnorePointer.
1189
- * Programmatic bridge commands continue to work, so a prompt can still open
1190
- * view-from-seat or 3D and then re-enable interaction after the handoff.
1256
+ * The buyer access session lapsed. `refreshed` says whether the provider
1257
+ * already recovered it — false means private inventory is now unavailable and
1258
+ * `onAccessUnavailable` follows. Never collapsed into `onError`: an expiry is
1259
+ * a recoverable, buyer-explainable state, not a network failure (guide §10).
1191
1260
  */
1192
- setInteractionEnabled(enabled: boolean): void;
1193
- private applyInteractionState;
1194
- /** Release the current hold (if any). No-op when nothing is held. */
1195
- release(): Promise<void>;
1196
- /** Release selected labels from the current hold while keeping the remainder. */
1197
- releaseLabels(labels: string[]): Promise<boolean>;
1198
- /** Tear everything down: close the socket, stop timers, drop the canvas. */
1261
+ onAccessExpired?: (event: BuyerAccessExpiredEvent) => void;
1199
1262
  /**
1200
- * Realtime for an access-scoped chart.
1201
- *
1202
- * A tokenless chart never gets here: `access` is null, `PubApi.socketUrl()`
1203
- * returns the URL it always has, and PickerController keeps its own socket
1204
- * and its own legacy frames. Nothing about the public path changes.
1263
+ * Private inventory is unavailable and refreshing will not fix it — revoked,
1264
+ * paused, wrong origin/event/mode, or the provider failed. Carries a reason,
1265
+ * never a channel name, id, colour or count. The widget shows its own
1266
+ * explanatory panel; return nothing to keep it, or handle the state yourself.
1205
1267
  */
1206
- private startRealtime;
1207
- /** Re-read live inventory without remounting or dropping buyer state. */
1208
- refreshAvailability(): Promise<void>;
1268
+ onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
1209
1269
  /**
1210
- * Re-acquire the buyer access sessioncall after your app has re-authorized
1211
- * the buyer (a revoked session cannot be recovered any other way). Resolves
1212
- * true when a fresh bearer is held; the realtime feed restarts with it.
1270
+ * Selected-but-unheld units stopped being selectablesomeone else took
1271
+ * them, or an allocation change moved them out of this buyer's scope. The
1272
+ * widget has already dropped them from the tray.
1213
1273
  */
1214
- refreshAccess(): Promise<boolean>;
1274
+ onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
1215
1275
  /**
1216
- * The visible failure state. Deliberately inline-styled and dependency-free:
1217
- * this renders on a stranger's website, where our stylesheet may not have
1218
- * loaded (the chart fetch just failed) and where inheriting the host's own
1219
- * styles is likelier to produce something unreadable than something on-brand.
1220
- */
1221
- private showLoadFailure;
1222
- destroy(): void;
1223
- }
1224
-
1225
- /**
1226
- * seatingChartBinding the single source of truth the framework wrappers bind
1227
- * against.
1228
- *
1229
- * `@seatlayer/react`, `@seatlayer/vue` and `@seatlayer/angular` are supposed to
1230
- * be thin: mount a `SeatingChart`, forward the props, forward the callbacks,
1231
- * expose the imperative handle. In practice each of them hand-wrote all three
1232
- * lists, and they drifted — React grew `initialView` and `errorDisplay` props
1233
- * that Vue and Angular never got, and nothing in any build caught it, because a
1234
- * missing prop in a hand-written list is not a type error anywhere.
1235
- *
1236
- * So the lists live here, next to the class they describe, and the wrappers
1237
- * iterate them. Adding a method to `SeatingChart` that buyers should be able to
1238
- * call now means adding one entry to {@link SEATING_CHART_HANDLE_METHODS}; every
1239
- * wrapper picks it up, and the wrapper tests fail until each one exposes it.
1240
- *
1241
- * Types only against `SeatingChart` — this module imports no runtime value from
1242
- * it, so it costs a host nothing but the two arrays and the binder below.
1243
- */
1276
+ * A buyer tapped a general-admission area (on the map, or in the tray's
1277
+ * `Areas` list) and the widget is about to open its own quantity prompt.
1278
+ *
1279
+ * Return `true` to take the interaction over completely — the built-in
1280
+ * popover/sheet is suppressed, and the host drives the choice with whatever
1281
+ * UI it likes, landing the result through `prompt.confirm(quantity)` (a plain
1282
+ * number for a single-tier area, or `{ [tierId]: quantity }`) or dropping it
1283
+ * with `prompt.cancel()`. Return anything else (or nothing) and the built-in
1284
+ * prompt opens as usual, so this is also usable as a plain notification.
1285
+ *
1286
+ * `prompt.max` already folds in the area's live availability AND the
1287
+ * order-wide ticket cap minus everything chosen elsewhere; a `confirm` above
1288
+ * it is clamped rather than rejected.
1289
+ */
1290
+ onGAPrompt?: (prompt: GAPromptRequest) => boolean | void;
1291
+ onError?: (err: unknown) => void;
1292
+ }
1244
1293
 
1245
1294
  /**
1246
- * Imperative handle exposed by every wrapper call these to drive the picker
1247
- * from your app.
1295
+ * SeatingChartthe embeddable buyer picker.
1248
1296
  *
1249
- * Angular is the one exception to the naming: its component surfaces `hold` as
1250
- * `holdSelection`, because `hold` is already taken by its `@Output()` event.
1297
+ * A thin wrapper over the shared PickerController (src/picker/PickerController):
1298
+ * it owns the mount <div> + the public embed contract (hold-only — the SDK hands
1299
+ * the holdId to the host page for a server-side book) and delegates all transport
1300
+ * + booking to the controller, so the SDK inherits every fix made for the live
1301
+ * buyer page and the demo picker.
1251
1302
  */
1252
- interface SeatingChartHandle {
1253
- /** Hold the current selection. Resolves the hold, or `null` on a 409 conflict. */
1254
- hold(options?: {
1255
- ttlMs?: number;
1256
- }): Promise<HoldResult | null>;
1257
- /** Restore an active hold by its opaque id. */
1258
- resumeHold(holdId: string): Promise<HoldResult | null>;
1259
- /** Current active hold known to the chart. */
1260
- getCurrentHold(): HoldResult | null;
1261
- /** GA areas with live remaining capacity. */
1262
- getGAAreas(): GAAreaAvailability[];
1263
- /** Atomically hold a quantity from one GA area. */
1264
- holdGA(areaId: string, qty: number, options?: {
1265
- tierId?: string | null;
1266
- ttlMs?: number;
1267
- }): Promise<HoldResult | null>;
1268
- /** Ask the server for the `qty` best free seats and hold them atomically. */
1269
- bestAvailable(qty: number, categoryKey?: string): Promise<BestAvailableResult | null>;
1270
- /** Release the current hold (if any). */
1271
- release(): Promise<void>;
1272
- /** Release some held labels while keeping the remainder active. */
1273
- releaseLabels(labels: string[]): Promise<boolean>;
1274
- /** The current selection, with prices resolved from the chart categories. */
1275
- getSelection(): SelectedSeat[];
1276
- /** Select free objects by engine id or public label. */
1277
- selectObjects(objects: string[]): SelectedSeat[];
1278
- /** Deselect objects by engine id or public label. */
1279
- deselectObjects(objects: string[]): void;
1280
- /** Clear every unheld selection. */
1281
- clearSelection(): void;
1282
- /** Select every selectable object in the named categories. */
1283
- selectCategories(categoryKeys: string[]): SelectedSeat[];
1284
- /** Deselect every selected object in the named categories. */
1285
- deselectCategories(categoryKeys: string[]): void;
1286
- /** Replace the buyer-selectable object allow-list without rebuilding. */
1287
- setSelectableObjects(objects: string[] | null): void;
1288
- /** Change the selection cap without rebuilding. */
1289
- setMaxSelection(maxSelection: number): void;
1290
- /** Current exact-count validity, or null when no exact count was configured. */
1291
- getSelectionValidity(): PickerSelectionValidity | null;
1292
- /**
1293
- * Choose a ticket tier for a selected seat (e.g. Adult → Child). Available
1294
- * `tiers` are on each `SelectedSeat`; `tierId=null` reverts to the default.
1295
- */
1296
- setSeatTier(seatId: string, tierId: string | null): void;
1297
- /**
1298
- * Floors of a multi-floor chart — `[{ id, name }]` (single-floor charts
1299
- * return one entry; empty before render()). Pair with setFloor().
1300
- */
1301
- getFloors(): {
1303
+
1304
+ /** A seat as surfaced to the host page (prices resolved from the chart's categories). */
1305
+ type SelectedSeat = PickerSeat;
1306
+ interface GAAreaAvailability {
1307
+ id: string;
1308
+ label: string;
1309
+ capacity: number;
1310
+ available: number;
1311
+ categoryKey: string;
1312
+ price: number;
1313
+ currency: string;
1314
+ /** Buyer-facing copy authored separately from stable inventory identity. */
1315
+ displayLabel?: string;
1316
+ displayType?: string;
1317
+ tiers?: Array<{
1302
1318
  id: string;
1303
1319
  name: string;
1304
- }[];
1305
- /** Switch the shown floor (2D). Warns + no-ops on single-floor charts. */
1306
- setFloor(floorId: string): void;
1307
- /** Toggle colorblind-safe rendering at runtime (see the `colorblindSafe` prop). */
1308
- setColorblindSafe(on: boolean): void;
1309
- /** Zoom in one step (same increment as the wheel/pinch gesture). */
1310
- zoomIn(): void;
1311
- /** Zoom out one step. */
1312
- zoomOut(): void;
1313
- /** Reset the camera so the whole chart fits the container. */
1314
- zoomToFit(): void;
1315
- /**
1316
- * Re-acquire the buyer access session after your app re-authorizes the buyer
1317
- * (Sales Channels). Resolves false when the chart is not access-scoped.
1318
- */
1319
- refreshAccess(): Promise<boolean>;
1320
+ price: number;
1321
+ }>;
1320
1322
  }
1321
1323
  /**
1322
- * Every method a wrapper must forward. The wrappers build their handle by
1323
- * iterating this, so the list IS the contract rather than a description of it.
1324
- */
1325
- declare const SEATING_CHART_HANDLE_METHODS: readonly ["hold", "resumeHold", "getCurrentHold", "getGAAreas", "holdGA", "bestAvailable", "release", "releaseLabels", "getSelection", "selectObjects", "deselectObjects", "clearSelection", "selectCategories", "deselectCategories", "setSelectableObjects", "setMaxSelection", "getSelectionValidity", "setSeatTier", "getFloors", "setFloor", "setColorblindSafe", "zoomIn", "zoomOut", "zoomToFit", "refreshAccess"];
1326
- type SeatingChartHandleMethod = (typeof SEATING_CHART_HANDLE_METHODS)[number];
1327
- /**
1328
- * Build the forwarding object every wrapper exposes.
1329
- *
1330
- * `getInstance` is a function rather than the instance itself: a wrapper's
1331
- * handle is created ONCE and must keep working across rebuilds, so it has to
1332
- * read the current chart on each call instead of capturing one.
1333
- */
1334
- declare function bindSeatingChartHandle(getInstance: () => SeatingChart | null): SeatingChartHandle;
1335
- /**
1336
- * Props whose change means a DIFFERENT chart, so the wrapper tears the canvas
1337
- * down and builds a new one. Everything else is read live: a parent re-render
1338
- * must never destroy a canvas mid-selection.
1339
- *
1340
- * `initialView` and `errorDisplay` are here because they are read once at
1341
- * construction and never re-applied — leaving them out does not make them
1342
- * "live", it makes them silently ignored after mount, which is the worse of the
1343
- * two behaviours. React already treated them this way; Vue and Angular did not
1344
- * expose them at all.
1345
- */
1346
- declare const SEATING_CHART_IDENTITY_PROPS: readonly ["event", "apiBase", "maxSelection", "numberOfPlacesToSelect", "selectionValidators", "publicKey", "locale", "currency", "colorblindSafe", "initialView", "errorDisplay"];
1347
- type SeatingChartIdentityProp = (typeof SEATING_CHART_IDENTITY_PROPS)[number];
1348
- /** Non-callback options a wrapper accepts as props/inputs and passes straight through. */
1349
- declare const SEATING_CHART_VALUE_PROPS: readonly ["event", "apiBase", "maxSelection", "numberOfPlacesToSelect", "selectionValidators", "publicKey", "locale", "currency", "colorblindSafe", "initialView", "errorDisplay", "selectedObjects", "selectableObjects", "messages", "seatTooltip", "buyerAccessTokenProvider", "buyerAccessToken"];
1350
- type SeatingChartValueProp = (typeof SEATING_CHART_VALUE_PROPS)[number];
1351
- /** Every callback option a wrapper wires to its own event mechanism. */
1352
- declare const SEATING_CHART_CALLBACK_PROPS: readonly ["onSelectionChange", "onSelectionValidityChange", "onSelectionValid", "onSelectionInvalid", "onSelectionLimit", "onHold", "onHoldRestored", "onHoldExpired", "onGAClick", "onGAPrompt", "onError", "onDeckTap", "onHint", "onSeatHover", "onAccessExpired", "onAccessUnavailable", "onSelectedObjectUnavailable"];
1353
- type SeatingChartCallbackProp = (typeof SEATING_CHART_CALLBACK_PROPS)[number];
1354
- /** The value half of the options, as a wrapper holds it. */
1355
- type SeatingChartValues = Pick<SeatingChartOptions, SeatingChartValueProp>;
1356
- /** The callback half, already bound to the wrapper's event mechanism. */
1357
- type SeatingChartCallbacks = Pick<SeatingChartOptions, SeatingChartCallbackProp>;
1358
- /**
1359
- * Assemble the options literal for `new SeatingChart(...)`.
1360
- *
1361
- * Picks by the lists above rather than spreading, so a wrapper cannot pass a
1362
- * stray prop of its own (React's `className`/`style`, Vue's attrs) into the
1363
- * SDK, and cannot forget one either.
1364
- */
1365
- declare function buildSeatingChartOptions(container: HTMLElement, values: Partial<SeatingChartValues>, callbacks: SeatingChartCallbacks): SeatingChartOptions;
1366
-
1367
- /**
1368
- * A secure, framework-neutral host for the SeatLayer chart Designer.
1369
- *
1370
- * The Designer remains an iframe so a platform never gives its SeatLayer secret
1371
- * key to a browser. This class owns the iframe lifecycle and accepts messages
1372
- * only from that iframe's exact origin.
1324
+ * Serializable state exposed to hosted native picker chrome. It deliberately
1325
+ * contains buyer-display data only: never API keys, buyer-access bearers,
1326
+ * provider callbacks, or the active hold id.
1373
1327
  */
1374
- type EmbeddedDesignerEventType = 'seatlayer.designer.ready' | 'seatlayer.designer.saved' | 'seatlayer.designer.published' | 'seatlayer.designer.close' | 'seatlayer.designer.error';
1375
- interface EmbeddedDesignerMessage {
1376
- type: EmbeddedDesignerEventType;
1377
- chartId?: string;
1378
- workspaceId?: string;
1379
- expiresAt?: number;
1380
- code?: string;
1381
- message?: string;
1382
- meta?: unknown;
1383
- /**
1384
- * Set by the Designer on an error it raised while the editor was already
1385
- * running (a failed autosave, thumbnail upload, reload…). Such an error is
1386
- * about ONE operation, not about the session, so the SDK reports it to the
1387
- * host and leaves the live editor mounted instead of replacing it with the
1388
- * dead-end card. Absent on older Designer builds — see
1389
- * {@link EmbeddedDesigner} for the phase-based fallback.
1390
- */
1391
- fatal?: boolean;
1392
- /** The operation that failed, when `fatal` is `false` (e.g. `'save'`). */
1393
- action?: string;
1394
- }
1395
- interface EmbeddedDesignerOptions {
1396
- /** The short-lived URL returned by your backend's Designer-session call. */
1397
- designerUrl: string;
1398
- /** CSS selector or element where the iframe is mounted. */
1399
- container: string | HTMLElement;
1400
- /**
1401
- * Verify the message belongs to the chart your backend opened. When set, the
1402
- * id is required on `ready` and every runtime lifecycle message. Only an error
1403
- * raised before the iframe resolves its session may omit it.
1404
- */
1405
- expectedChartId?: string;
1406
- /**
1407
- * Verify the message belongs to the workspace your backend opened. Uses the
1408
- * same ready/runtime requirement and pre-session error exception as
1409
- * `expectedChartId`.
1410
- */
1411
- expectedWorkspaceId?: string;
1412
- title?: string;
1413
- className?: string;
1414
- style?: Partial<CSSStyleDeclaration>;
1415
- allow?: string;
1416
- referrerPolicy?: ReferrerPolicy;
1417
- /**
1418
- * Show the built-in branded loading skeleton and error/expiry card inside the
1419
- * container while the Designer boots. Defaults to `true`. Set `false` when the
1420
- * host renders its own loading and error chrome.
1421
- */
1422
- showLoadingState?: boolean;
1328
+ interface SeatingChartPickerState {
1329
+ event: {
1330
+ key: string;
1331
+ name: string;
1332
+ venue: string | null;
1333
+ startsAt: number | null;
1334
+ timezone: string | null;
1335
+ currency: string;
1336
+ locale: string | null;
1337
+ posterUrl: string | null;
1338
+ mode: 'live' | 'test';
1339
+ salesClosed: boolean;
1340
+ };
1341
+ branding: {
1342
+ accent?: ChartTheme['accent'];
1343
+ accentInk?: ChartTheme['accentInk'];
1344
+ background?: ChartTheme['background'];
1345
+ textColor?: ChartTheme['textColor'];
1346
+ brandName?: ChartTheme['brandName'];
1347
+ logoUrl?: ChartTheme['logoUrl'];
1348
+ /** Native must render SeatLayer attribution when it owns chrome and this is true. */
1349
+ attributionRequired: boolean;
1350
+ };
1351
+ features: {
1352
+ ga: boolean;
1353
+ tiers: boolean;
1354
+ floors: boolean;
1355
+ zones: boolean;
1356
+ sections: boolean;
1357
+ bestAvailable: boolean;
1358
+ accessibilityFilter: boolean;
1359
+ limitedViewFilter: boolean;
1360
+ /** Real lazy-loaded WebGL venue view, not the retired isometric projection. */
1361
+ venue3d: boolean;
1362
+ /** Authored or chart-derived view-from-seat panorama. */
1363
+ seatView: boolean;
1364
+ };
1365
+ catalog: {
1366
+ categories: Array<{
1367
+ key: string;
1368
+ label: string;
1369
+ color: string;
1370
+ price: number;
1371
+ available: number;
1372
+ tiers: Array<{
1373
+ id: string;
1374
+ name: string;
1375
+ price: number;
1376
+ currency?: string;
1377
+ }>;
1378
+ }>;
1379
+ zones: Array<{
1380
+ id: string;
1381
+ label: string;
1382
+ color?: string;
1383
+ }>;
1384
+ sections: Array<{
1385
+ id: string;
1386
+ label: string;
1387
+ displayLabel?: string;
1388
+ zoneId?: string;
1389
+ }>;
1390
+ gaAreas: GAAreaAvailability[];
1391
+ bestAvailableZones: Array<{
1392
+ id: string;
1393
+ label: string;
1394
+ }>;
1395
+ };
1396
+ map: {
1397
+ floors: Array<{
1398
+ id: string;
1399
+ name: string;
1400
+ }>;
1401
+ activeFloorId: string | null;
1402
+ /** `'all'` = the stacked whole-venue view; `'single'` = one floor. */
1403
+ floorMode: 'all' | 'single';
1404
+ /** How the stacked view marks each level. */
1405
+ floorLabelStyle: FloorLabelStyle;
1406
+ rung: LodRung;
1407
+ focusedSectionId: string | null;
1408
+ focusedSection: SectionSummary | null;
1409
+ viewMode: RendererViewMode;
1410
+ buyerView: SeatingChartBuyerView;
1411
+ view3dTargetSeatId: string | null;
1412
+ view3dNavigationMode: SeatingChartNavigationMode;
1413
+ colorblindSafe: boolean;
1414
+ /** Chrome the HOST draws over the map, in CSS px, echoed back so a native
1415
+ * shell can confirm what framing is honouring. Zero when none reported. */
1416
+ viewportInsets: {
1417
+ top: number;
1418
+ right: number;
1419
+ bottom: number;
1420
+ left: number;
1421
+ };
1422
+ categoryFilter: string[] | null;
1423
+ accessibilityFilter: string[] | null;
1424
+ hideLimitedView: boolean;
1425
+ };
1426
+ selection: {
1427
+ seats: SelectedSeat[];
1428
+ validity: PickerSelectionValidity | null;
1429
+ maxSelection: number;
1430
+ };
1431
+ access: {
1432
+ configured: boolean;
1433
+ status: 'public' | 'ready' | 'expired' | 'unavailable';
1434
+ reason?: string;
1435
+ };
1436
+ }
1437
+ interface SeatingChartOptions {
1438
+ /** CSS selector or an HTMLElement to render into. */
1439
+ container: string | HTMLElement;
1440
+ /** Event key, e.g. `ev_xxx`. */
1441
+ event: string;
1442
+ /** API origin. Defaults to https://api.seatlayer.io. */
1443
+ apiBase?: string;
1423
1444
  /**
1424
- * If the Designer never posts `ready` within this many milliseconds, the host
1425
- * transitions to the error card with a timeout message. Defaults to `20000`.
1426
- * Only used when `showLoadingState` is enabled.
1445
+ * Publishable account key for direct Public-sale bootstrap of a Platform/SDK
1446
+ * Event. It is never buyer identity and cannot request private inventory.
1447
+ * An explicit buyer-access provider/token below always takes precedence.
1427
1448
  */
1428
- loadingTimeoutMs?: number;
1449
+ publicKey?: string;
1429
1450
  /**
1430
- * How to size the iframe's height. The Designer is a full application (its
1431
- * shell is `position:fixed; height:100dvh`), not flowing content, so it should
1432
- * fill its box rather than be measured.
1433
- *
1434
- * - `'fill'` (default): container-aware. On mount the SDK probes whether the
1435
- * host gave the container a DEFINITE (bounded) height:
1436
- * - **Bounded container** (a fixed-height block, `height`/`max-height`,
1437
- * `flex:1; min-h:0`, a resolved `%`, etc.) → the iframe fills 100% of
1438
- * that block and tracks its size live via a `ResizeObserver`.
1439
- * - **Content-sized container** (the block collapses to whatever the iframe
1440
- * measures — typical full-page usage) → the iframe grows so its bottom
1441
- * edge reaches the bottom of the viewport (`window.innerHeight -
1442
- * iframe.top`), recomputed (rAF-throttled) on `resize` /
1443
- * `orientationchange` / `scroll`.
1444
- * Either way the result is clamped to `minHeight`. The verdict is cached but
1445
- * re-probed on `resize`/`orientationchange` so a responsive host layout can
1446
- * flip between the two. The legacy `seatlayer.designer.resize` message is
1447
- * ignored in `'fill'` mode: it is circular, because the fixed-position shell
1448
- * just echoes the iframe height.
1449
- * - a number: a fixed pixel height. In this mode the legacy resize message is
1450
- * still honoured (unless `autoResize` is `false`) so older hosts keep growing.
1451
+ * Buyer access session provider the recommended way to render private
1452
+ * channel inventory (Sales Channels guide §6).
1451
1453
  *
1452
- * All SDK-managed heights are written with `!important` priority so a host
1453
- * theme's `iframe { height: !important }` cannot override them.
1454
- */
1455
- height?: 'fill' | number;
1456
- /** Minimum height (px) that `'fill'` mode clamps to. Defaults to `480`. */
1457
- minHeight?: number;
1458
- /**
1459
- * Auto-grow the iframe to the height the Designer reports over the resize
1460
- * protocol (`seatlayer.designer.resize`). Only applies when `height` is a fixed
1461
- * number; ignored in `'fill'` mode. Defaults to `true`. Set `false` when the
1462
- * host sizes a fixed-height iframe itself.
1463
- */
1464
- autoResize?: boolean;
1465
- /**
1466
- * Called when the user presses "Try again" on the error card. Use it to mint a
1467
- * fresh Designer session and call `setDesignerUrl()` with the new URL, which
1468
- * recreates the iframe and returns to the loading state. When omitted, "Try
1469
- * again" reloads the current `designerUrl` in place.
1454
+ * Called with a `reason` whenever the SDK needs a bearer: first acquisition,
1455
+ * a near/actual expiry, a 401 `buyer_access_expired`, a realtime reconnect,
1456
+ * or `refreshAccess()`. It should POST to YOUR backend, which mints the
1457
+ * session with your secret key and returns `{ token, expiresAt }`.
1470
1458
  *
1471
- * When supplied, it also powers automatic session renewal see
1472
- * {@link EmbeddedDesignerOptions.autoRenewSession}.
1459
+ * The token lives in memory for the widget's lifetime and nowhere else: never
1460
+ * in storage, never in a URL, never in a log or an error message. Refresh
1461
+ * returns the same or a narrower scope — the SDK never widens to Public sale
1462
+ * on its own, and a failed refresh stops the scoped operation rather than
1463
+ * retrying it anonymously.
1473
1464
  */
1474
- onRequestRelaunch?: () => void;
1465
+ buyerAccessTokenProvider?: BuyerAccessTokenProvider;
1475
1466
  /**
1476
- * Keep long editing sessions alive without the user ever hitting the expiry
1477
- * wall. Designer sessions are short-lived security tokens; when the host wires
1478
- * `onRequestRelaunch` the SDK, with this enabled, will:
1479
- *
1480
- * - **Renew proactively.** From each `ready` message's `expiresAt` it schedules
1481
- * a silent relaunch shortly before the session lapses (~3 min ahead; for a
1482
- * TTL under 15 min it renews after 80% of the remaining life, and never
1483
- * sooner than 30s after `ready`). The host mints a fresh session and swaps
1484
- * `designerUrl`, so the editor keeps working with no error card.
1485
- * - **Recover on expiry.** If an expiry error still slips through (a slept
1486
- * laptop woke past the renewal window, say) it makes ONE automatic relaunch
1487
- * attempt before showing the "Try again" card, and only falls back to the
1488
- * card if that relaunch also fails.
1489
- *
1490
- * Defaults to `true` whenever `onRequestRelaunch` is provided; a no-op without
1491
- * it. Set `false` to keep the fully manual "Try again" behavior.
1467
+ * One-shot escape hatch for hosts that already own the session lifecycle.
1468
+ * Cannot be renewed when it lapses the widget reports `onAccessExpired`
1469
+ * and then `onAccessUnavailable`. Prefer `buyerAccessTokenProvider`.
1492
1470
  */
1493
- autoRenewSession?: boolean;
1494
- onReady?: (message: EmbeddedDesignerMessage) => void;
1495
- onSaved?: (message: EmbeddedDesignerMessage) => void;
1496
- onPublished?: (message: EmbeddedDesignerMessage) => void;
1497
- onClose?: (message: EmbeddedDesignerMessage) => void;
1498
- onError?: (message: EmbeddedDesignerMessage) => void;
1499
- }
1500
- /** Mount, replace, and destroy a scoped Designer iframe safely. */
1501
- declare class EmbeddedDesigner {
1502
- private options;
1503
- private frame;
1504
- private designerOrigin;
1505
- private overlay;
1506
- private timeoutTimer;
1507
- /** Proactive session-renewal timer; armed from each `ready`, cleared on re-mount. */
1508
- private renewTimer;
1509
- /** Last identity-checked session expiry, so a live policy change can re-arm. */
1510
- private sessionExpiresAt;
1471
+ buyerAccessToken?: string | BuyerAccessToken;
1472
+ /** Max seats selectable at once (default 10). */
1473
+ maxSelection?: number;
1474
+ /** Object ids or public labels selected after live availability is loaded. */
1475
+ selectedObjects?: string[];
1476
+ /** Object ids or public labels the buyer may select. Omit for all free objects. */
1477
+ selectableObjects?: string[] | null;
1478
+ /** Require exactly this many seated/table guest units before the selection is valid. */
1479
+ numberOfPlacesToSelect?: number;
1480
+ /** Optional local selection guards, enforced again before hold. */
1481
+ selectionValidators?: PickerSelectionValidator[];
1511
1482
  /**
1512
- * One automatic recovery relaunch is allowed per expiry. Reset ONLY when a fresh
1513
- * `ready` arrives deliberately not on re-mount so a session that keeps failing
1514
- * to load can't loop the host through endless silent relaunches.
1483
+ * BCP 47 language for the widget UI `'de'`, `'es-MX'`, etc. Falls back to
1484
+ * the browser language, then English. 37 languages ship. The German
1485
+ * bundle (etc.) is fetched on demand so unused languages cost nothing.
1515
1486
  */
1516
- private autoRecoverUsed;
1517
- private phase;
1487
+ locale?: string;
1518
1488
  /**
1519
- * Set only after an identity-checked `ready`. Unlike `phase`, this remains true
1520
- * if a later fatal error renders the error card, so no subsequent callback can
1521
- * shed the chart/workspace identity the live session already established.
1489
+ * Per-key string overrides layered over the active locale white-label copy
1490
+ * without shipping a whole bundle, e.g. `{ 'map.fromPrice': 'ab {price}' }`.
1522
1491
  */
1523
- private identityEstablished;
1524
- private restoreContainerPosition;
1525
- private pinned;
1526
- private frameStyleBeforeFs;
1527
- private docOverflowBeforeFs;
1528
- private bodyOverflowBeforeFs;
1529
- private fsKeyHandler;
1530
- /** Latest height (px string) the Designer reported; re-applied after unpin. */
1531
- private lastAutoHeight;
1532
- private fillRaf;
1533
- private reprobeRaf;
1534
- private fillListening;
1535
- /** Resolved container element (fill measurement + ResizeObserver target). */
1536
- private containerEl;
1537
- /** Cached fill verdict: 'container' = bounded block, 'viewport' = full page. */
1538
- private fillMode;
1539
- /** Live block-size tracking in container-fill mode; disconnected on destroy. */
1540
- private resizeObs;
1541
- constructor(options: EmbeddedDesignerOptions);
1542
- mount(): HTMLIFrameElement;
1543
- /** Replace the iframe instead of assigning a new fragment to an existing one. */
1544
- setDesignerUrl(designerUrl: string): HTMLIFrameElement;
1545
- getIframe(): HTMLIFrameElement | null;
1546
- /** Update iframe sizing without replacing the live Designer session. */
1547
- setSizing(height: 'fill' | number | undefined, minHeight: number | undefined): void;
1548
- /** Update renewal/expiry-recovery policy without replacing the iframe. */
1549
- setRelaunchPolicy(onRequestRelaunch: (() => void) | undefined, autoRenewSession: boolean | undefined): void;
1550
- destroy(): void;
1551
- private loadingStateEnabled;
1552
- private autoResizeEnabled;
1553
- /** Fill mode is the default; a numeric `height` opts into a fixed pixel box. */
1554
- private fillEnabled;
1555
- /** Write an SDK-managed height with `!important` so a host theme can't win. */
1556
- private setFrameHeight;
1492
+ messages?: Record<string, string>;
1557
1493
  /**
1558
- * Decide whether the host gave the container a DEFINITE (bounded) height — a
1559
- * fixed block the embed should fill 100% of — versus a content-sized container
1560
- * that collapses to whatever the iframe measures (full-page usage).
1494
+ * Show an in-chart language switcher offering exactly these languages.
1561
1495
  *
1562
- * We drive the iframe to two extreme heights within a single synchronous task
1563
- * and watch whether the container follows: a bounded box barely moves, a
1564
- * content-sized one grows with the iframe. Because we restore the height before
1565
- * yielding, the browser only lays out it never paints the extremes, so there
1566
- * is no visible flash. Works for px, resolved `%`, and flex (`flex:1;min-h:0`)
1567
- * heights, and leaves a mere `min-height` floor classified as content-sized so
1568
- * full-page hosts keep the old viewport-fill behavior.
1569
- */
1570
- private detectFillMode;
1571
- /**
1572
- * Size the iframe for the current fill verdict, clamped to `minHeight`. In
1573
- * container mode it fills 100% of the bounded block; in viewport mode its
1574
- * bottom edge meets the bottom of the viewport (`window.innerHeight - top`).
1575
- * No-op while pinned fullscreen (the pin fills the viewport itself).
1496
+ * Opt-in and curated on purpose: the chart sits inside someone else's page,
1497
+ * a multilingual host already has its own switcher, and two that can
1498
+ * disagree is worse than either alone. A curated list also beats all 37 — a
1499
+ * German venue wants de/en/pl in the menu, not Welsh. Fewer than two
1500
+ * resolvable entries renders nothing.
1576
1501
  */
1577
- private applyFill;
1578
- /** rAF-throttled fill recompute, so a burst of scroll/RO ticks coalesces. */
1579
- private scheduleFill;
1502
+ languages?: string[];
1503
+ /** ISO 4217 currency for on-map prices (default USD). */
1504
+ currency?: string;
1580
1505
  /**
1581
- * rAF-throttled re-probe: a host layout change (responsive breakpoint, a block
1582
- * gaining/losing a definite height) can flip the verdict, so `resize` /
1583
- * `orientationchange` re-detect and swap the container observer accordingly.
1506
+ * Colorblind-safe rendering: category hues switch to an Okabe-Ito palette
1507
+ * and booked seats render hollow, so state never relies on hue alone.
1508
+ * Toggleable later with setColorblindSafe().
1584
1509
  */
1585
- private scheduleReprobe;
1586
- /** Attach/detach the container ResizeObserver to match the current verdict. */
1587
- private syncContainerObserver;
1588
- private startFill;
1589
- private stopFill;
1510
+ colorblindSafe?: boolean;
1511
+ /** Host-authoritative colors for the drawn canvas, separate from native chrome. */
1512
+ mapTheme?: PickerMapTheme;
1513
+ /** Light / dark / follow-the-reader for the DRAWN map — the embed has no
1514
+ * chrome to re-token, so a mode here means only the canvas ground, and
1515
+ * `mapTheme` still outranks it. Switchable with {@link SeatingChart.setThemeMode}. */
1516
+ theme?: {
1517
+ mode?: ThemeMode | null;
1518
+ };
1519
+ /** Initial canvas projection.
1520
+ * @deprecated `'isometric'` and `'perspective'` are retired in favour of the
1521
+ * real 3D venue view (`setBuyerView('venue3d')`); they remain accepted for
1522
+ * source compatibility and will be removed in the next major. Use `'flat'`. */
1523
+ initialView?: RendererViewMode;
1524
+ /** Offer the real, lazy-loaded interactive venue view. Default true. */
1525
+ enable3D?: boolean;
1526
+ /** Offer authored/generated view-from-seat inspection. Default true. */
1527
+ enableSeatView?: boolean;
1528
+ /** Optional device-specific ceiling for offering the venue WebGL scene. */
1529
+ max3DSeats?: number;
1590
1530
  /**
1591
- * Pin the iframe over the host page as a viewport-filling overlay. We save the
1592
- * iframe's inline style and the document scroll state so `unpinFullscreen`
1593
- * restores everything exactly. Escape (host-side) also exits.
1531
+ * Built-in seat tooltip on mouse hover (seat · category · price · status).
1532
+ * Rendered inside the widget so every host gets it; default true. Turn off
1533
+ * to draw your own popover from onSeatHover.
1594
1534
  */
1595
- private pinFullscreen;
1596
- /** Undo `pinFullscreen`: restore the iframe style + scroll lock. Idempotent. */
1597
- private unpinFullscreen;
1598
- private clearTimeoutTimer;
1535
+ seatTooltip?: boolean;
1536
+ /** Internal hosted-runtime control. Defaults true for direct web embeds. */
1537
+ showTestModeIndicator?: boolean;
1538
+ /** Internal hosted-runtime control. Defaults true, subject to white-label entitlement. */
1539
+ showAttribution?: boolean;
1599
1540
  /**
1600
- * Auto-renewal (proactive + one expiry recovery) is on when the host wired a
1601
- * relaunch hook and did not opt out. Without the hook there is nothing to call,
1602
- * so it is a no-op.
1541
+ * Seat hover with everything a popover needs (category label/color, resolved
1542
+ * tier-aware price, live status, currency); null on hover-out. Fires whether
1543
+ * or not the built-in tooltip is enabled.
1603
1544
  */
1604
- private autoRenewEnabled;
1605
- private clearRenewTimer;
1545
+ onSeatHover?: (details: SeatHoverDetails | null) => void;
1546
+ onSelectionChange?: (seats: SelectedSeat[]) => void;
1547
+ /** Selection-rule state after each selection change. */
1548
+ onSelectionValidityChange?: (state: PickerSelectionValidity) => void;
1549
+ /** The configured selection rules have just become valid. */
1550
+ onSelectionValid?: (seats: SelectedSeat[]) => void;
1551
+ /** The configured selection rules have just become invalid. */
1552
+ onSelectionInvalid?: (state: PickerSelectionValidity) => void;
1553
+ /** A manual or programmatic selection reached the active maximum. */
1554
+ onSelectionLimit?: (maxSelection: number) => void;
1555
+ onHold?: (result: HoldResult) => void;
1556
+ /** A prior active hold was restored with resumeHold(). */
1557
+ onHoldRestored?: (result: HoldResult) => void;
1558
+ onHoldExpired?: () => void;
1559
+ onGAClick?: (area: GAAreaAvailability) => void;
1606
1560
  /**
1607
- * Arm the proactive renewal timer from a `ready` message's `expiresAt` (epoch
1608
- * ms). We relaunch a comfortable lead before expiry so the host can mint a fresh
1609
- * session and swap `designerUrl` without the user ever seeing the expiry card:
1610
- *
1611
- * - normal TTL (≥ 15 min): renew {@link RENEW_LEAD_MS} (~3 min) before expiry;
1612
- * - short TTL (< 15 min): renew after {@link RENEW_SHORT_TTL_FRACTION} (80%) of
1613
- * the remaining life, so the lead can't overshoot the whole session;
1614
- * - either way, never sooner than {@link RENEW_MIN_DELAY_MS} (30s) after `ready`
1615
- * so a burst of `ready` messages can't spin the host.
1616
- *
1617
- * Re-armed on every `ready`; cleared on destroy / setDesignerUrl (via re-mount).
1618
- * A no-op when auto-renewal is off or `expiresAt` is missing/already past — the
1619
- * expiry-error path recovers a session that has already lapsed.
1561
+ * Mirror of {@link SeatPickerOptions.onGAPrompt}. The low-level chart draws no
1562
+ * quantity UI of its own, so this fires purely as the "the buyer wants to pick
1563
+ * a quantity here" signal, with the same pre-computed `min`/`max`/`tiers` the
1564
+ * full widget would have used; the return value is accepted and ignored.
1620
1565
  */
1621
- private scheduleRenewal;
1622
- private ensureContainerPositioned;
1623
- private restoreContainerStyle;
1624
- private removeOverlay;
1625
- private showError;
1626
- private handleTryAgain;
1566
+ onGAPrompt?: (prompt: GAPromptRequest) => boolean | void;
1627
1567
  /**
1628
- * Build (or rebuild) the overlay for the given phase. A single overlay element
1629
- * is reused so we never stack stale skeletons or cards.
1568
+ * The buyer access session lapsed. `refreshed` says whether the provider
1569
+ * already recovered it false means private inventory is now unavailable and
1570
+ * `onAccessUnavailable` follows. Distinct from `onError` on purpose: this is
1571
+ * never a network failure (guide §10).
1630
1572
  */
1631
- private renderOverlay;
1632
- private buildSkeleton;
1633
- private buildErrorCard;
1634
- private handleMessage;
1635
- }
1636
-
1637
- /**
1638
- * Buyer-safe ticket-offer availability shared by the canonical picker and the
1639
- * hosted event-page templates.
1640
- *
1641
- * This module intentionally owns the wire parser. A hosted page, iframe, popup
1642
- * and SDK mount must refuse or accept the same payload; duplicating this reader
1643
- * is how one surface eventually prints a price another surface will not charge.
1644
- */
1645
- type SaleState = 'on-sale' | 'low' | 'sold-out' | 'presale' | 'closed';
1646
- interface TicketOfferSummary {
1647
- /** Legacy ordering fields kept for older page templates and payloads. */
1648
- index: number;
1649
- count: number;
1650
- /** Units currently available at this offer price; null means unlimited. */
1651
- remaining: number | null;
1652
- /** Buyer-facing fields added by the offers UX. Absent on older workers. */
1653
- id?: string;
1654
- name?: string;
1655
- categoryKey?: string | null;
1656
- startsAt?: number | null;
1657
- endsAt?: number | null;
1658
- }
1659
- interface TicketOfferPrice {
1660
- categoryKey: string;
1661
- /** Major units. What a hold on this category is charged right now. */
1662
- price: number;
1663
- /** Major units, or null. Printed only when genuinely higher. */
1664
- previousPrice: number | null;
1665
- /** Offer provenance for the price row. Absent on older workers. */
1666
- offerId?: string;
1667
- offerName?: string;
1668
- remaining?: number | null;
1669
- startsAt?: number | null;
1670
- endsAt?: number | null;
1671
- }
1672
- interface ChannelPriceOverride {
1673
- categoryKey: string;
1674
- tierId: string | null;
1675
- price: number;
1676
- }
1677
- /** Pricing configuration projected only for the private channels in this grant. */
1678
- interface BuyerChannelPricing {
1679
- assignmentVersion: number;
1680
- channels: Array<{
1681
- channelId: string;
1682
- pricingVersion: number;
1683
- priceOverrides: ChannelPriceOverride[];
1684
- }>;
1685
- objects: Array<{
1686
- label: string;
1687
- channelId: string;
1688
- }>;
1689
- }
1690
- interface TicketOfferAvailability {
1691
- state: SaleState;
1692
- /** Currently advertised offer price, in minor units. */
1693
- fromPrice: number | null;
1694
- previousPrice: number | null;
1695
- currency: string | null;
1696
- /** The highest-priority active buy offer, when one exists. */
1697
- release: TicketOfferSummary | null;
1698
- /** The next scheduled price offer. It does not close ordinary ticket sales. */
1699
- upcoming: TicketOfferSummary | null;
1700
- /** Server-resolved active offer prices by category, in major units. */
1701
- prices: TicketOfferPrice[];
1702
- channelPricing: BuyerChannelPricing | null;
1703
- }
1704
- /** Parse a public offer payload without repairing a half-understood price. */
1705
- declare function parseTicketOfferAvailability(body: unknown): TicketOfferAvailability | null;
1706
- /** Translate the server-resolved category map into SeatPicker pricing. */
1707
- declare function ticketOfferPrices(availability: TicketOfferAvailability | null): Record<string, number>;
1708
-
1709
- /**
1710
- * pickerTypes — the SeatPicker public option/theme/pricing contract, plus the
1711
- * two internal shapes (`SectionLike`, `PriceBand`) the picker reads off a
1712
- * ChartDoc.
1713
- *
1714
- * Split out of SeatPicker.ts verbatim; these declarations are the widget's
1715
- * published API surface, so SeatPicker.ts re-exports every public name from
1716
- * here and `@seatlayer/js`'s entry (src/index.ts) is untouched. Types only —
1717
- * nothing in this file emits a byte of runtime JavaScript.
1718
- */
1719
-
1720
- /**
1721
- * Stable checkout-handoff contract (P4). Passed as the THIRD argument to
1722
- * `onCheckout(hold, seats, handoff)` — additive, so the legacy `(hold, seats)`
1723
- * shape used by DesiPass web-v2 (SDK 0.7.3+) is untouched. This is the object to
1724
- * build your order against: it is self-contained (holdId, expiry, currency, and
1725
- * per-line tier + price) and never changes shape across minor releases.
1726
- */
1727
- interface CheckoutLineItem {
1728
- /** Seat label (or GA synthetic-unit label) — the stable booking identity. */
1729
- label: string;
1573
+ onAccessExpired?: (event: BuyerAccessExpiredEvent) => void;
1730
1574
  /**
1731
- * Buyer-facing name (the designer's `displayLabel` override), when set.
1732
- * Show this in YOUR order summary; `label` stays the booking identity you
1733
- * pass to the book call. Absent = no override, fall back to `label`.
1575
+ * Private inventory is unavailable and refreshing will not fix it — revoked,
1576
+ * paused, wrong origin/event/mode, or the provider failed. Carries a reason,
1577
+ * never a channel name, id, colour or count.
1734
1578
  */
1735
- displayLabel?: string;
1579
+ onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
1736
1580
  /**
1737
- * Buyer-facing type word override (seats.io "Displayed type", e.g. "Table",
1738
- * "Bench", "Box"), when the designer set one. Absent = the default word.
1581
+ * Selected-but-unheld units stopped being selectable someone else took
1582
+ * them, or an allocation change moved them out of this buyer's scope. The
1583
+ * widget has already dropped them from the selection.
1739
1584
  */
1740
- displayType?: string;
1741
- /** Chart object id (row/booth/GA area) the unit belongs to. */
1742
- objectId: string;
1743
- objectType: 'seat' | 'booth' | 'ga' | 'table';
1744
- categoryKey: string;
1745
- /** Chosen ticket tier id (Adult/Child/…), or null when the category has no tiers. */
1746
- tierId: string | null;
1747
- /** Unit price in MAJOR currency units (e.g. 45 = 45.00). Server-authoritative. */
1748
- unitPrice: number;
1749
- /** ISO-4217, resolved server-side (per-event override → org → USD). */
1750
- currency: string;
1751
- quantity: number;
1752
- }
1753
- interface CheckoutHandoff {
1754
- /** Server hold id — pass this to YOUR book call. */
1755
- holdId: string;
1756
- /** Epoch ms the hold expires (after any extensions). */
1757
- expiresAt: number;
1758
- /** ISO-4217 currency for the whole order. */
1759
- currency: string;
1760
- /** Priced line items (tier + unit price + currency), server-authoritative. */
1761
- lineItems: CheckoutLineItem[];
1762
- /** Convenience total in major units (Σ unitPrice × quantity). */
1763
- total: number;
1764
- }
1765
- /** Host-authoritative pricing — see {@link SeatPickerOptions.pricing}. */
1766
- interface SeatPickerPricing {
1767
- /** Unit prices by category key: a flat number, or `{ base, tiers: { tierId: price } }`. */
1768
- prices?: Record<string, number | {
1769
- base?: number;
1770
- tiers?: Record<string, number>;
1771
- }>;
1772
- /** Custom money renderer (e.g. `(n) => n + '€'`). Defaults to Intl currency formatting. */
1773
- formatter?: (amount: number, currency: string) => string;
1774
- }
1775
- /** Optional constraints for the server-authoritative best-available pick. */
1776
- interface SeatPickerBestAvailableOptions {
1777
- /** Prefer a contiguous premium block, falling back to the best overall block. */
1778
- preferPremium?: boolean;
1779
- /** Restrict the search to one configured chart zone. */
1780
- zoneId?: string;
1781
- }
1782
- /** Buyer-facing surface shown by the full picker widget. */
1783
- type SeatPickerBuyerView = 'map' | 'venue3d';
1784
- /** Optional camera intent when switching the buyer-facing surface. */
1785
- interface SeatPickerBuyerViewOptions {
1786
- /** Enter (or remain in) 3D and fly the camera to this seat id. */
1787
- flyToSeatId?: string;
1788
- /** When already in 3D, return the camera to the venue overview. */
1789
- resetView?: boolean;
1790
- }
1791
- /** Host theme overrides — any subset; unset keys fall back to the org's chart theme, then defaults. */
1792
- interface SeatPickerTheme {
1585
+ onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
1586
+ onError?: (err: unknown) => void;
1793
1587
  /**
1794
- * Light, dark, or follow the reader's own `prefers-color-scheme`.
1588
+ * What the BUYER sees when the chart cannot load.
1795
1589
  *
1796
- * OPT-IN. Omit it and the widget renders exactly what it always did: the
1797
- * chart's authored ground, the organizer's colours, your own overrides. Set
1798
- * it and the built-in token set for that side takes the GROUND roles
1799
- * background, surface, ink, muted ink, hairlines, and the drawn map's own
1800
- * canvas while the organizer's accent, font, radius and logo, and every
1801
- * colour you state explicitly below, stay exactly as they are.
1590
+ * `'message'` (the default) renders a plain, styleable notice with a Try
1591
+ * again button. This used to be silent unconditionally: `render()` returned
1592
+ * with an EMPTY mounted div and only `onError` fired, so a host that had not
1593
+ * wired `onError` or had wired it to a logger — showed buyers a blank
1594
+ * rectangle where the seat map belongs, on the host's own domain, which
1595
+ * reads as a broken website rather than a temporary fault. `SeatPicker` has
1596
+ * always failed loud with a retry; this is the embed class catching up.
1802
1597
  *
1803
- * `'auto'` is re-read live: a buyer who flips their phone into dark mode
1804
- * mid-purchase keeps their seats and their hold.
1598
+ * `'none'` restores the silent behaviour for hosts that render their own
1599
+ * failure UI from `onError`.
1805
1600
  */
1806
- mode?: ThemeMode;
1807
- /** Brand accent (CTA, active chips, hold pill). */
1808
- accent?: string;
1809
- /** Ink on the accent (button labels). */
1810
- accentInk?: string;
1811
- /** Widget background. */
1812
- background?: string;
1813
- /** Panel/card surface color. */
1814
- surface?: string;
1815
- /** Primary text color. */
1816
- text?: string;
1817
- /** Secondary text color. */
1818
- muted?: string;
1819
- /** Hairline/border color. */
1820
- line?: string;
1821
- /** Font stack for all widget chrome. */
1822
- fontFamily?: string;
1823
- /** Corner radius base (px). */
1824
- radius?: number;
1825
- /** Header logo URL (falls back to the org logo from the chart theme, then a monogram). */
1826
- logoUrl?: string;
1827
- /** Brand/event fallback name for the monogram. */
1828
- brandName?: string;
1601
+ errorDisplay?: 'message' | 'none';
1602
+ /** Which pieces of the IMMERSIVE scene's own chrome this embed draws. The
1603
+ * 2D chrome each has its own option above; the 3D caption, seat stepper and
1604
+ * camera dock come from inside the lazy venue chunk, where an embedding
1605
+ * host had no way to reach them — so a native shell that already draws all
1606
+ * three drew them twice. Absent or `true` = drawn, exactly as before. */
1607
+ /** The 2D panorama's title/caption/badge join the 3D pieces here — the full
1608
+ * table is bridge/nativeChrome.ts. */
1609
+ chrome?: {
1610
+ view3dCaption?: boolean;
1611
+ view3dSeatStepper?: boolean;
1612
+ view3dControlDock?: boolean;
1613
+ seatViewTitle?: boolean;
1614
+ seatViewCaption?: boolean;
1615
+ seatViewBadge?: boolean;
1616
+ };
1617
+ /** The 2D panorama opened, or closed (`null`). Fires whether or not the web
1618
+ * drew its words, so a host that suppressed them has the exact strings. */
1619
+ onSeatViewChange?: (info: SeatingChartSeatViewInfo | null) => void;
1620
+ /** How the stacked "All floors" view marks each level. Default `'number'`. */
1621
+ floorLabelStyle?: FloorLabelStyle;
1622
+ /**
1623
+ * Multi-floor charts only: fires when the buyer taps a deck in the stacked
1624
+ * 3D view, after the picker switches to that floor — lets the host page sync
1625
+ * its own floor UI (tabs, labels) with the map.
1626
+ */
1627
+ onDeckTap?: (floorId: string) => void;
1628
+ /** Big-venue section focus state for host-owned navigation chrome. */
1629
+ onSectionFocus?: (summary: SectionSummary | null) => void;
1630
+ /** Availability changed after a live frame or explicit refresh. */
1631
+ onStatusChange?: () => void;
1632
+ /** The server closed sales for this event. */
1633
+ onSalesClosed?: () => void;
1634
+ /** A programmatic/native map-control action changed serializable map state. */
1635
+ onMapStateChange?: () => void;
1636
+ /** rAF-coalesced camera-frame signal for in-page overlays; not native state. */
1637
+ onViewChange?: () => void;
1638
+ /** Real buyer-view transitions and 3D camera targets. */
1639
+ onBuyerViewChange?: (state: {
1640
+ view: SeatingChartBuyerView;
1641
+ seatId?: string;
1642
+ }) => void;
1829
1643
  /**
1830
- * The DRAWN MAP, which the tokens above deliberately do not reach.
1831
- *
1832
- * Everything else on this interface is CSS: it re-inks panels, buttons and
1833
- * the sidebar. The seat map is a canvas, painted from the chart document's
1834
- * own `ChartTheme`, so a host could re-ink the whole widget and still be
1835
- * looking at somebody else's dark venue in the middle of it (which is exactly
1836
- * what SeatLayer's own light event-page palettes did, found 2026-08-07).
1837
- *
1838
- * Nested rather than flattened because `background` already means the
1839
- * WIDGET's background here and the canvas ground is a different surface —
1840
- * two things one word cannot carry.
1841
- *
1842
- * Set it only when you can vouch for the result: these colours are drawn
1843
- * behind and beside live seat statuses (held, sold, selected), and the map is
1844
- * the one part of this widget a buyer has to be able to read.
1644
+ * Non-blocking, localized selection advice currently the orphan-seat hint
1645
+ * (the selection would strand a single free seat between taken neighbors).
1646
+ * `null` clears it. Purely informational; nothing is ever prevented.
1845
1647
  */
1846
- map?: PickerMapTheme;
1648
+ onHint?: (message: string | null) => void;
1847
1649
  }
1848
- interface SeatPickerOptions {
1849
- /** CSS selector or element to mount into. Omit when using SeatPicker.open(). */
1850
- container?: string | HTMLElement;
1851
- /** Event key, e.g. `ev_xxx`. */
1852
- event: string;
1853
- /** API origin. Defaults to https://api.seatlayer.io. */
1854
- apiBase?: string;
1650
+ declare class SeatingChart {
1651
+ private readonly opts;
1652
+ private readonly controller;
1653
+ /** Publishable key used only by the public-only direct bootstrap path. */
1654
+ readonly publicKey?: string;
1655
+ private mount;
1656
+ private hostEl;
1855
1657
  /**
1856
- * Custom data transport. Defaults to the CORS-trivial PubApi against
1857
- * `apiBase`. Inject to run the widget against another backend adapter (the
1858
- * SeatLayer dashboard's own transport) or a fully local mock (demos).
1658
+ * Native hosts can render a Flutter/Swift/Kotlin decision surface above this
1659
+ * DOM tree. UIKit platform views still participate in hit testing beneath a
1660
+ * composited native overlay, so the runtime must be able to make its own DOM
1661
+ * inert rather than relying on the host's visual stacking alone.
1859
1662
  */
1860
- transport?: PickerTransport;
1663
+ private interactionEnabled_;
1664
+ /** The organizer's language for this event, learned when the chart resolves. */
1665
+ private eventLocale;
1666
+ private rendered;
1667
+ private mode_;
1668
+ private eventInfo;
1669
+ private maxSelection_;
1670
+ private colorblindSafe_;
1671
+ private readonly hostSurface;
1672
+ private categoryFilter_;
1673
+ private accessibilityFilter_;
1674
+ private hideLimitedView_;
1675
+ private focusedSection_;
1676
+ /** Last rung already exposed to native chrome; raw camera frames stay in-canvas. */
1677
+ private lastReportedViewRung_;
1678
+ private accessState_;
1679
+ private tipEl;
1680
+ private tipPos;
1681
+ private onTipMove;
1682
+ /** Null for the ordinary public chart — the tokenless path is untouched. */
1683
+ private readonly access;
1684
+ private readonly api;
1685
+ private readonly apiBase;
1686
+ private readonly buyerAssetUrls;
1687
+ private readonly immersive;
1688
+ private realtime;
1689
+ constructor(options: SeatingChartOptions);
1861
1690
  /**
1862
- * Publishable account key (`pk_live_…` / `pk_test_…`) for a public
1863
- * Platform/SDK Event. With no explicit buyer-access token/provider, the SDK
1864
- * uses it to bootstrap one origin-bound, Public-sale-only session directly
1865
- * from SeatLayer and receives chart + inventory in the same request.
1691
+ * Camera x/y/scale is renderer-local and deliberately absent from the native
1692
+ * picker snapshot. Keep pan and pinch frames on the canvas; only a semantic
1693
+ * rung transition needs to wake native chrome.
1694
+ */
1695
+ private handleViewChange;
1696
+ /** Record the current rung before publishing an explicit state mutation. */
1697
+ private notifyMapStateChange;
1698
+ /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
1699
+ render(): Promise<this>;
1700
+ /**
1701
+ * Attribution badge pinned to the embed's bottom-right, linking to
1702
+ * seatlayer.io. Rendered as an absolutely-positioned overlay with
1703
+ * self-contained inline styles — the SDK embed ships no widget CSS, and an
1704
+ * overlay keeps it out of the layout flow so it never disturbs the SDK v0.22
1705
+ * fill-height resize contract. Mirrors the full widget's mark + wordmark and
1706
+ * reuses the `picker.poweredBy` i18n string.
1707
+ */
1708
+ private buildBadge;
1709
+ private placeTooltip;
1710
+ private updateTooltip;
1711
+ /**
1712
+ * Whether the SERVED event is a live or a test event (`sk_test_` keys create
1713
+ * test events, which never book real inventory). `null` before render()
1714
+ * resolves — the mode comes from the server with the chart, not from options.
1866
1715
  *
1867
- * This is not buyer identity and cannot request private channels. An explicit
1868
- * `buyerAccessTokenProvider` or `buyerAccessToken` below always takes
1869
- * precedence for login, presale, partner or other scoped inventory.
1716
+ * The widget already surfaces this visually with the test-mode ribbon; this
1717
+ * getter is for hosts that draw their own chrome — notably a native WebView
1718
+ * wrapper, which must be able to tell an integrator that the build they are
1719
+ * about to ship is pointed at a test event.
1870
1720
  */
1871
- publicKey?: string;
1872
1721
  /**
1873
- * Buyer access session provider the recommended way to show private channel
1874
- * inventory (Sales Channels guide §6).
1722
+ * The opt-in language switcher, offering exactly the languages the host
1723
+ * named. Absent unless `languages` lists at least two — a menu with one
1724
+ * entry is furniture, and an unrequested one duplicates the switcher a
1725
+ * multilingual host site already has.
1875
1726
  *
1876
- * Called with a `reason` whenever the widget needs a bearer: first
1877
- * acquisition, a near/actual expiry, a 401 `buyer_access_expired`, a realtime
1878
- * reconnect, or `refreshAccess()`. It should POST to YOUR backend, which
1879
- * mints the session with your secret key and returns `{ token, expiresAt }`.
1727
+ * Each language names itself (`LOCALE_NAMES`): a reader scanning for their
1728
+ * own language finds "Deutsch", never "German". An English list is
1729
+ * unreadable to exactly the people who need it.
1730
+ */
1731
+ private buildLanguageSwitcher;
1732
+ /**
1733
+ * Change the language of a LIVE chart, keeping the buyer's selection.
1880
1734
  *
1881
- * The token lives in memory for the widget's lifetime and nowhere else: never
1882
- * in storage, never in a URL, never in a log or an error message. Refresh
1883
- * returns the same or a narrower scope; the widget never widens to Public
1884
- * sale on its own, and a failed refresh stops the scoped operation rather
1885
- * than retrying it anonymously. Any held seats stay held — a hold is
1886
- * relinquished by its own opaque capability, not by channel access, so
1887
- * losing access never strands inventory (guide §9).
1735
+ * `locale` is otherwise read once at render, which is fine for a page that
1736
+ * knows its language up front and useless for one with a language switcher.
1737
+ * The competing embed API can only `rerender()`, and that clears the
1738
+ * selection losing someone's seats because they changed language is not an
1739
+ * acceptable trade, so this rebuilds the map through `refreshMapCopy()`,
1740
+ * which restores the selection on the far side.
1888
1741
  *
1889
- * Ignored when a custom `transport` is supplied: that host owns its own
1890
- * credentials.
1742
+ * Returns the locale that actually became active: an unsupported or
1743
+ * not-yet-translated tag resolves to English rather than throwing, exactly as
1744
+ * it does at first render.
1891
1745
  */
1892
- buyerAccessTokenProvider?: BuyerAccessTokenProvider;
1746
+ setLocale(next: string | null | undefined): Promise<string>;
1747
+ getMode(): 'live' | 'test' | null;
1748
+ /** Atomic, token-free display state for native-owned picker chrome. */
1749
+ getPickerState(): SeatingChartPickerState;
1750
+ getCategoryAvailability(): Record<string, number>;
1751
+ getBestAvailableZones(): Array<{
1752
+ id: string;
1753
+ label: string;
1754
+ }>;
1755
+ getActiveFloorId(): string | null;
1756
+ getRung(): LodRung;
1757
+ getFocusedSection(): string | null;
1758
+ focusSection(sectionId: string): void;
1759
+ overview(): void;
1760
+ setRung(rung: LodRung): void;
1761
+ setCategoryFilter(categoryKeys: string[] | null, focus?: boolean): void;
1762
+ setAccessibilityFilter(types: string[] | null): void;
1763
+ setCommercialLimitedFilter(on: boolean): void;
1764
+ /** Current selection with prices resolved from the chart categories. */
1765
+ getSelection(): SelectedSeat[];
1766
+ /** Select free objects by engine id or public label. Returns newly selected objects. */
1767
+ selectObjects(objects: string[]): SelectedSeat[];
1768
+ /** Deselect objects by engine id or public label. */
1769
+ deselectObjects(objects: string[]): void;
1770
+ /** Clear every unheld selection. */
1771
+ clearSelection(): void;
1772
+ /** Select every selectable object in the named categories. */
1773
+ selectCategories(categoryKeys: string[]): SelectedSeat[];
1774
+ /** Deselect every selected object in the named categories. */
1775
+ deselectCategories(categoryKeys: string[]): void;
1776
+ /** Replace the buyer-selectable object allow-list without remounting. */
1777
+ setSelectableObjects(objects: string[] | null): void;
1778
+ /** Change the selection cap without remounting. */
1779
+ setMaxSelection(maxSelection: number): void;
1780
+ /** Current selection-rule validity, or null when no rules were configured. */
1781
+ getSelectionValidity(): PickerSelectionValidity | null;
1782
+ /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */
1783
+ hold(options?: {
1784
+ ttlMs?: number;
1785
+ }): Promise<HoldResult | null>;
1893
1786
  /**
1894
- * One-shot escape hatch for hosts that already own the session lifecycle.
1895
- * Cannot be renewed when it lapses the widget reports `onAccessExpired`
1896
- * and then `onAccessUnavailable`. Prefer `buyerAccessTokenProvider`.
1787
+ * @internal Like {@link hold} but RE-THROWS the structured API error (409
1788
+ * `reason`/`code` + `conflicts`) instead of swallowing it into `onError` +
1789
+ * `null`. The native WebView host adapter needs the throw so it can answer the
1790
+ * originating command with a correlated error carrying the SPECIFIC reason
1791
+ * (`sold_out` vs `not_enough_together`); the public method above keeps the
1792
+ * catch-and-onError contract that direct web consumers rely on. Not a stable
1793
+ * part of the embed API.
1897
1794
  */
1898
- buyerAccessToken?: string | BuyerAccessToken;
1899
- /** Max seats selectable at once (default 10). */
1900
- maxSelection?: number;
1901
- /** Object ids or public labels selected after live availability is loaded. */
1902
- selectedObjects?: string[];
1903
- /** Object ids or public labels the buyer may select. Omit for all free objects. */
1904
- selectableObjects?: string[] | null;
1905
- /** Require exactly this many total seated/table/GA guest units before checkout. */
1906
- numberOfPlacesToSelect?: number;
1907
- /** Optional sale guards evaluated after every change and enforced before a
1908
- * hold: minimum quantity, consecutive seats, and/or no stranded singles. */
1909
- selectionValidators?: PickerSelectionValidator[];
1795
+ holdOrThrow(options?: {
1796
+ ttlMs?: number;
1797
+ }): Promise<HoldResult | null>;
1798
+ /** Restore an active hold by its opaque id without extending its expiry. */
1799
+ resumeHold(holdId: string): Promise<HoldResult | null>;
1800
+ /** @internal Throwing variant of {@link resumeHold} for the native host adapter. See {@link holdOrThrow}. */
1801
+ resumeHoldOrThrow(holdId: string): Promise<HoldResult | null>;
1802
+ /**
1803
+ * Push the OPEN hold's expiry out ("need more time?"). Resolves the refreshed
1804
+ * hold, or `null` when there is nothing held or the server refused (the hold
1805
+ * is gone, already expired, or at its renewal cap) — refusal is a normal
1806
+ * outcome, not an error, so the host decides the copy. The client-side expiry
1807
+ * timer is re-armed to match, so `onHoldExpired` won't fire early.
1808
+ */
1809
+ extendHold(ttlMs?: number): Promise<HoldResult | null>;
1810
+ /** Current active hold known to this chart, if any. */
1811
+ getCurrentHold(): HoldResult | null;
1812
+ getGAAreas(): GAAreaAvailability[];
1813
+ holdGA(areaId: string, qty: number, options?: {
1814
+ tierId?: string | null;
1815
+ ttlMs?: number;
1816
+ }): Promise<HoldResult | null>;
1817
+ /** @internal Throwing variant of {@link holdGA} for the native host adapter. See {@link holdOrThrow}. */
1818
+ holdGAOrThrow(areaId: string, qty: number, options?: {
1819
+ tierId?: string | null;
1820
+ ttlMs?: number;
1821
+ }): Promise<HoldResult | null>;
1822
+ /**
1823
+ * Ask the server for the `qty` best free seats and hold them atomically.
1824
+ * `options.ttlMs` sets the checkout window exactly like {@link hold}; omit it
1825
+ * and the server falls back to the event setting, then its own default.
1826
+ */
1827
+ bestAvailable(qty: number, categoryKey?: string, options?: {
1828
+ zoneId?: string;
1829
+ preferPremium?: boolean;
1830
+ ttlMs?: number;
1831
+ }): Promise<BestAvailableResult | null>;
1832
+ /** @internal Throwing variant of {@link bestAvailable} for the native host adapter. See {@link holdOrThrow}. */
1833
+ bestAvailableOrThrow(qty: number, categoryKey?: string, options?: {
1834
+ zoneId?: string;
1835
+ preferPremium?: boolean;
1836
+ ttlMs?: number;
1837
+ }): Promise<BestAvailableResult | null>;
1910
1838
  /**
1911
- * BCP 47 language for the widget UI. 37 languages ship; omit this and each
1912
- * buyer gets their own `navigator.languages`, falling back to English.
1913
- * Read once at render there is no way to change it on a live chart.
1839
+ * Choose a ticket tier for a selected seat (e.g. Adult Child). The seat's
1840
+ * available `tiers` are on each `SelectedSeat` from `getSelection()` /
1841
+ * `onSelectionChange`. Re-emits the selection with the new tier + price, and
1842
+ * the tier rides along in the next `hold()` / `onHold` per seat. `tierId=null`
1843
+ * reverts to the default tier.
1914
1844
  */
1915
- locale?: string;
1916
- /** Per-key string overrides layered over the active locale. */
1917
- messages?: Record<string, string>;
1845
+ setSeatTier(seatId: string, tierId: string | null): void;
1846
+ /** Confirm/update an unheld variable table's guest quantity locally. */
1847
+ setTableQuantity(label: string, quantity: number): boolean;
1918
1848
  /**
1919
- * Show an in-picker language switcher offering exactly these languages.
1920
- *
1921
- * Opt-in and curated on purpose. Omit it and there is no control: the widget
1922
- * sits inside someone else's page, and a multilingual host site already has
1923
- * its own switcher — two that can disagree is worse than either alone. A
1924
- * curated list also beats offering all 37: a German venue wants de/en/pl in
1925
- * the menu, not Welsh.
1926
- *
1927
- * Switching keeps the buyer's selection; it does not re-render the chart.
1849
+ * Atomically replace an active variable-table hold with a new guest count.
1850
+ * Returns null for an inapplicable/invalid quantity or a refused replacement.
1928
1851
  */
1929
- languages?: string[];
1930
- /** ISO 4217 currency fallback (the org/event currency on the chart wins). */
1931
- currency?: string;
1932
- /** Colorblind-safe rendering (Okabe-Ito palette, hollow booked seats). */
1933
- colorblindSafe?: boolean;
1852
+ replaceTableQuantity(label: string, quantity: number, ttlMs?: number): Promise<HoldResult | null>;
1853
+ /** @internal Throwing variant used by the correlated native bridge. */
1854
+ replaceTableQuantityOrThrow(label: string, quantity: number, ttlMs?: number): Promise<HoldResult | null>;
1934
1855
  /**
1935
- * How a big chart's seats arrive: 'progressive' (default) shows the venue's
1936
- * sections as soon as the chart lands and fills the seats in a section at a
1937
- * time, nearest the viewport first; 'plain' builds the whole scene in one
1938
- * step. Small charts are built in one step either way.
1856
+ * Floors of a multi-floor chart `[{ id, name }]` (single-floor charts
1857
+ * return one entry; empty before render()). Pair with setFloor() to build a
1858
+ * host-side floor switcher.
1939
1859
  */
1940
- loadingReveal?: 'progressive' | 'plain';
1941
- /** Initial map projection. Buyers now toggle **Map (flat 2D) + 3D** only; the
1942
- * legacy `perspective` (2.5D) value is still ACCEPTED for source compatibility
1943
- * but is deprecated — it is coerced to `flat` with a one-time console warning.
1944
- * The 3D venue view is entered from the Map/3D control, not this option. */
1945
- initialView?: RendererViewMode;
1946
- /**
1947
- * Offer the interactive 3D venue view (Map | 3D toggle + a "See it in 3D"
1948
- * action on the seat-confirm card). Default true. The 3D button is shown only
1949
- * when this is not false AND the browser exposes WebGL2; there are ZERO GL
1950
- * bytes on the wire until the buyer actually opens 3D (the OGL chunk is
1951
- * dynamically imported on first use). Set false for embed hosts that must
1952
- * stay strictly 2D. */
1953
- enable3D?: boolean;
1860
+ getFloors(): {
1861
+ id: string;
1862
+ name: string;
1863
+ }[];
1864
+ /** Switch the shown floor (2D), or `'all'` for the stacked whole-venue view.
1865
+ * One control, not two: a strip whose first chip is "All floors" would
1866
+ * otherwise need a separate command for that one chip. */
1867
+ setFloor(floorId: string): void;
1868
+ /** How the stacked view marks each level: a `G/1/2` badge, or floor names. */
1869
+ setFloorLabelStyle(style: FloorLabelStyle): void;
1870
+ /** Toggle colorblind-safe rendering at runtime (see options.colorblindSafe). */
1871
+ setColorblindSafe(on: boolean): void;
1872
+ /** Switch the 2D canvas projection.
1873
+ * @deprecated `'isometric'` and `'perspective'` are retired in favour of the
1874
+ * real 3D venue view (`setBuyerView('venue3d')`); accepted for source
1875
+ * compatibility until the next major. */
1876
+ setViewMode(mode: RendererViewMode): void;
1877
+ /** Current canvas projection. */
1878
+ getViewMode(): RendererViewMode;
1879
+ /** Zoom in one step (same increment as the wheel/pinch gesture). */
1880
+ zoomIn(): void;
1881
+ /** Zoom out one step. */
1882
+ zoomOut(): void;
1883
+ /** Reset the camera so the whole chart fits the container. */
1884
+ zoomToFit(): void;
1954
1885
  /**
1955
- * Seat-count ceiling above which 3D is not offered. Default: 60,000 seats on
1956
- * desktop, reduced to 30,000 on a device that reports itself as small/low-core.
1886
+ * Light / dark / follow-the-reader, applied to a LIVE map.
1957
1887
  *
1958
- * The 53,018-seat evidence is desktop-only; that scale remains unmeasured on
1959
- * phones, which is why the small-device default stays conservative. The 3D
1960
- * scene holds every seat resident until a streaming rung exists. */
1961
- max3DSeats?: number;
1962
- /**
1963
- * Fires when the buyer enters/leaves 3D or targets a seat there. Hosts can
1964
- * mirror this small, non-sensitive state into a shareable URL.
1888
+ * This is a repaint, not a remount. It re-inks the canvas through the
1889
+ * controller's in-place rebuild, which keeps the buyer's selection, the
1890
+ * section they had opened and the exact camera pose a native shell
1891
+ * flipping the OS appearance must not cost the buyer their seats or their
1892
+ * place in the venue. `null` hands the ground back to the chart's own
1893
+ * colours; an explicit `mapTheme` still outranks any mode.
1965
1894
  */
1966
- onBuyerViewChange?: (state: {
1967
- view: SeatPickerBuyerView;
1968
- seatId?: string;
1969
- }) => void;
1970
- /**
1971
- * Optional analytics sink for the widget's journey and performance events.
1972
- * Emits `chart_rendered` once per successful load with total time, step
1973
- * timings and anonymous chart-size counts, plus the 3D venue-view journey
1974
- * (`3d_opened`, `3d_orbit_engaged`, `3d_seat_picked`,
1975
- * `3d_cinematic_played`/`_skipped`/`_cancelled`, panorama outcomes, and WebGL
1976
- * context loss/recovery)
1977
- * with `{ surface: 'buyer' }` merged into the props. A throwing sink never
1978
- * breaks the widget. Route it to your product analytics (e.g. PostHog). */
1979
- onAnalytics?: (event: string, props: Record<string, unknown>) => void;
1895
+ /** A repaint, not a remount: the buyer keeps their selection, their focused
1896
+ * section and their camera. See ./seatingChartHostSurface. */
1897
+ setThemeMode(mode: ThemeMode | null, options?: {
1898
+ mapTheme?: PickerMapTheme | null;
1899
+ }): void;
1900
+ /** The mode the host asked for `'auto'`, not what `auto` resolved to. */
1901
+ getThemeMode(): ThemeMode | null;
1902
+ /** Report the chrome the HOST draws over the map, in CSS px, so framing
1903
+ * aims at what the buyer can see. See ./seatingChartHostSurface. */
1904
+ setViewportInsets(insets: {
1905
+ top?: number;
1906
+ right?: number;
1907
+ bottom?: number;
1908
+ left?: number;
1909
+ } | null): void;
1910
+ /** The insets in force; all zero when the host reported none. */
1911
+ getViewportInsets(): {
1912
+ top: number;
1913
+ right: number;
1914
+ bottom: number;
1915
+ left: number;
1916
+ };
1917
+ /** Enter or leave the same real lazy-loaded venue scene as SeatPicker. */
1918
+ setBuyerView(view: SeatingChartBuyerView, options?: {
1919
+ flyToSeatId?: string;
1920
+ resetView?: boolean;
1921
+ }): Promise<void>;
1922
+ getBuyerView(): SeatingChartBuyerView;
1923
+ /** Open an authored or chart-derived 360° view for one physical seat. */
1924
+ openSeatView(seatId: string): Promise<void>;
1925
+ /** Explicit Rotate / Move control for native and custom picker chrome. */
1926
+ setVenue3DNavigationMode(mode: SeatingChartNavigationMode): void;
1980
1927
  /**
1981
- * Every seat of a progressively revealed chart is now built and painted.
1928
+ * Enable or suppress all buyer input inside this chart without unmounting it.
1982
1929
  *
1983
- * `render()` resolves with the section shells and the nearest seats — the
1984
- * rest arrive in idle slices. A host that needs "the whole venue is on
1985
- * screen" waits for this rather than for `render()`. Fires exactly once per
1986
- * chart, and never for a chart small enough to build in one step (there is
1987
- * nothing to wait for: `render()` already was that moment).
1988
- */
1989
- onSeatsRevealed?: () => void;
1990
- /**
1991
- * Optional organiser-defined campaign/referral label (max 120 characters).
1992
- * On the public SeatLayer transport it is frozen onto the hold, then copied
1993
- * to the Managed order, organiser reporting and booking webhook. It grants no
1994
- * inventory access and is ignored by host-supplied transports.
1930
+ * Native SDKs use this while a native confirmation, quantity prompt, loading
1931
+ * surface or error surface owns the chart area. Disabling input in the DOM is
1932
+ * intentional: an iOS WKWebView can receive the same physical tap beneath a
1933
+ * Flutter overlay even when the Flutter widget is wrapped in IgnorePointer.
1934
+ * Programmatic bridge commands continue to work, so a prompt can still open
1935
+ * view-from-seat or 3D and then re-enable interaction after the handoff.
1995
1936
  */
1996
- referral?: string;
1937
+ setInteractionEnabled(enabled: boolean): void;
1938
+ private applyInteractionState;
1939
+ /** Release the current hold (if any). No-op when nothing is held. */
1940
+ release(): Promise<void>;
1941
+ /** Release selected labels from the current hold while keeping the remainder. */
1942
+ releaseLabels(labels: string[]): Promise<boolean>;
1943
+ /** Tear everything down: close the socket, stop timers, drop the canvas. */
1997
1944
  /**
1998
- * Hide the "Powered by SeatLayer" attribution badge in the side panel foot.
1999
- * The chart theme's own `hideBadge` flag (paid orgs) also hides it — the badge
2000
- * is shown only when BOTH this option and the theme flag are unset/false.
1945
+ * Realtime for an access-scoped chart.
1946
+ *
1947
+ * A tokenless chart never gets here: `access` is null, `PubApi.socketUrl()`
1948
+ * returns the URL it always has, and PickerController keeps its own socket
1949
+ * and its own legacy frames. Nothing about the public path changes.
2001
1950
  */
2002
- hideBadge?: boolean;
1951
+ private startRealtime;
1952
+ /** Re-read live inventory without remounting or dropping buyer state. */
1953
+ refreshAvailability(): Promise<void>;
2003
1954
  /**
2004
- * Hide the picker's event identity (logo, event name and venue/date metadata)
2005
- * when the host surface already presents the same event heading. The hold
2006
- * timer, sales status and modal close controls remain available. The identity
2007
- * is restored automatically while the picker is full screen so it never
2008
- * loses context on a small device or an expanded map. Default false.
2009
- *
2010
- * A mounted host can update this through `setEventDetailsHidden()` when its
2011
- * own event chrome arrives asynchronously.
1955
+ * Re-acquire the buyer access session call after your app has re-authorized
1956
+ * the buyer (a revoked session cannot be recovered any other way). Resolves
1957
+ * true when a fresh bearer is held; the realtime feed restarts with it.
2012
1958
  */
2013
- hideEventDetails?: boolean;
1959
+ refreshAccess(): Promise<boolean>;
2014
1960
  /**
2015
- * Start the wide-layout ticket panel collapsed so the map owns the full
2016
- * width (map-first hosts, small embeds). The buyer reopens it with the
2017
- * "Tickets" pill beside Map|3D, and it opens itself the moment a seat lands
2018
- * in the cart a collapsed panel must never hide a checkout. Narrow
2019
- * layouts ignore this (the bottom sheet is already the collapse). A mounted
2020
- * host can drive it later through `setPanelCollapsed()`. Default false.
1961
+ * The visible failure state. Deliberately inline-styled and dependency-free:
1962
+ * this renders on a stranger's website, where our stylesheet may not have
1963
+ * loaded (the chart fetch just failed) and where inheriting the host's own
1964
+ * styles is likelier to produce something unreadable than something on-brand.
2021
1965
  */
2022
- panelCollapsed?: boolean;
2023
- /** Host theme overrides — see SeatPickerTheme. */
2024
- theme?: SeatPickerTheme;
1966
+ private showLoadFailure;
1967
+ destroy(): void;
1968
+ }
1969
+
1970
+ /**
1971
+ * seatingChartBinding — the single source of truth the framework wrappers bind
1972
+ * against.
1973
+ *
1974
+ * `@seatlayer/react`, `@seatlayer/vue` and `@seatlayer/angular` are supposed to
1975
+ * be thin: mount a `SeatingChart`, forward the props, forward the callbacks,
1976
+ * expose the imperative handle. In practice each of them hand-wrote all three
1977
+ * lists, and they drifted — React grew `initialView` and `errorDisplay` props
1978
+ * that Vue and Angular never got, and nothing in any build caught it, because a
1979
+ * missing prop in a hand-written list is not a type error anywhere.
1980
+ *
1981
+ * So the lists live here, next to the class they describe, and the wrappers
1982
+ * iterate them. Adding a method to `SeatingChart` that buyers should be able to
1983
+ * call now means adding one entry to {@link SEATING_CHART_HANDLE_METHODS}; every
1984
+ * wrapper picks it up, and the wrapper tests fail until each one exposes it.
1985
+ *
1986
+ * Types only against `SeatingChart` — this module imports no runtime value from
1987
+ * it, so it costs a host nothing but the two arrays and the binder below.
1988
+ */
1989
+
1990
+ /**
1991
+ * Imperative handle exposed by every wrapper — call these to drive the picker
1992
+ * from your app.
1993
+ *
1994
+ * Angular is the one exception to the naming: its component surfaces `hold` as
1995
+ * `holdSelection`, because `hold` is already taken by its `@Output()` event.
1996
+ */
1997
+ interface SeatingChartHandle {
1998
+ /** Hold the current selection. Resolves the hold, or `null` on a 409 conflict. */
1999
+ hold(options?: {
2000
+ ttlMs?: number;
2001
+ }): Promise<HoldResult | null>;
2002
+ /** Restore an active hold by its opaque id. */
2003
+ resumeHold(holdId: string): Promise<HoldResult | null>;
2004
+ /** Current active hold known to the chart. */
2005
+ getCurrentHold(): HoldResult | null;
2006
+ /** GA areas with live remaining capacity. */
2007
+ getGAAreas(): GAAreaAvailability[];
2008
+ /** Atomically hold a quantity from one GA area. */
2009
+ holdGA(areaId: string, qty: number, options?: {
2010
+ tierId?: string | null;
2011
+ ttlMs?: number;
2012
+ }): Promise<HoldResult | null>;
2013
+ /** Ask the server for the `qty` best free seats and hold them atomically. */
2014
+ bestAvailable(qty: number, categoryKey?: string): Promise<BestAvailableResult | null>;
2015
+ /** Release the current hold (if any). */
2016
+ release(): Promise<void>;
2017
+ /** Release some held labels while keeping the remainder active. */
2018
+ releaseLabels(labels: string[]): Promise<boolean>;
2019
+ /** The current selection, with prices resolved from the chart categories. */
2020
+ getSelection(): SelectedSeat[];
2021
+ /** Select free objects by engine id or public label. */
2022
+ selectObjects(objects: string[]): SelectedSeat[];
2023
+ /** Deselect objects by engine id or public label. */
2024
+ deselectObjects(objects: string[]): void;
2025
+ /** Clear every unheld selection. */
2026
+ clearSelection(): void;
2027
+ /** Select every selectable object in the named categories. */
2028
+ selectCategories(categoryKeys: string[]): SelectedSeat[];
2029
+ /** Deselect every selected object in the named categories. */
2030
+ deselectCategories(categoryKeys: string[]): void;
2031
+ /** Replace the buyer-selectable object allow-list without rebuilding. */
2032
+ setSelectableObjects(objects: string[] | null): void;
2033
+ /** Change the selection cap without rebuilding. */
2034
+ setMaxSelection(maxSelection: number): void;
2035
+ /** Current exact-count validity, or null when no exact count was configured. */
2036
+ getSelectionValidity(): PickerSelectionValidity | null;
2025
2037
  /**
2026
- * Host-authoritative pricing. When your shop charges different prices than
2027
- * the chart's stored category prices, pass them here so the buyer sees the
2028
- * price they will actually pay — on the map tooltip, confirm popover, price
2029
- * panel, tray, totals, and in the checkout handoff's line items. Keyed by
2030
- * category key; per-tier overrides nest under `tiers`. Unlisted categories
2031
- * fall back to the chart price.
2038
+ * Choose a ticket tier for a selected seat (e.g. Adult → Child). Available
2039
+ * `tiers` are on each `SelectedSeat`; `tierId=null` reverts to the default.
2032
2040
  */
2033
- pricing?: SeatPickerPricing;
2041
+ setSeatTier(seatId: string, tierId: string | null): void;
2034
2042
  /**
2035
- * Fires when the server-resolved active offer changes. Hosted event pages use
2036
- * this to keep their headline, sticky bar and the canonical picker on the
2037
- * same live fact. The picker remains fully functional when it is omitted.
2043
+ * Floors of a multi-floor chart `[{ id, name }]` (single-floor charts
2044
+ * return one entry; empty before render()). Pair with setFloor().
2038
2045
  */
2039
- onOfferAvailabilityChange?: (availability: TicketOfferAvailability | null) => void;
2040
- /** Hold TTL in ms passed to hold(); server clamps to its own limits. */
2041
- holdTtlMs?: number;
2046
+ getFloors(): {
2047
+ id: string;
2048
+ name: string;
2049
+ }[];
2050
+ /** Switch the shown floor (2D). Warns + no-ops on single-floor charts. */
2051
+ setFloor(floorId: string): void;
2052
+ /** Toggle colorblind-safe rendering at runtime (see the `colorblindSafe` prop). */
2053
+ setColorblindSafe(on: boolean): void;
2054
+ /** Zoom in one step (same increment as the wheel/pinch gesture). */
2055
+ zoomIn(): void;
2056
+ /** Zoom out one step. */
2057
+ zoomOut(): void;
2058
+ /** Reset the camera so the whole chart fits the container. */
2059
+ zoomToFit(): void;
2042
2060
  /**
2043
- * An opaque hold id supplied by the host to restore after navigation. It is
2044
- * verified against the event and active server state before anything renders
2045
- * as owned by this buyer.
2061
+ * Re-acquire the buyer access session after your app re-authorizes the buyer
2062
+ * (Sales Channels). Resolves false when the chart is not access-scoped.
2046
2063
  */
2047
- initialHoldId?: string;
2064
+ refreshAccess(): Promise<boolean>;
2065
+ }
2066
+ /**
2067
+ * Every method a wrapper must forward. The wrappers build their handle by
2068
+ * iterating this, so the list IS the contract rather than a description of it.
2069
+ */
2070
+ declare const SEATING_CHART_HANDLE_METHODS: readonly ["hold", "resumeHold", "getCurrentHold", "getGAAreas", "holdGA", "bestAvailable", "release", "releaseLabels", "getSelection", "selectObjects", "deselectObjects", "clearSelection", "selectCategories", "deselectCategories", "setSelectableObjects", "setMaxSelection", "getSelectionValidity", "setSeatTier", "getFloors", "setFloor", "setColorblindSafe", "zoomIn", "zoomOut", "zoomToFit", "refreshAccess"];
2071
+ type SeatingChartHandleMethod = (typeof SEATING_CHART_HANDLE_METHODS)[number];
2072
+ /**
2073
+ * Build the forwarding object every wrapper exposes.
2074
+ *
2075
+ * `getInstance` is a function rather than the instance itself: a wrapper's
2076
+ * handle is created ONCE and must keep working across rebuilds, so it has to
2077
+ * read the current chart on each call instead of capturing one.
2078
+ */
2079
+ declare function bindSeatingChartHandle(getInstance: () => SeatingChart | null): SeatingChartHandle;
2080
+ /**
2081
+ * Props whose change means a DIFFERENT chart, so the wrapper tears the canvas
2082
+ * down and builds a new one. Everything else is read live: a parent re-render
2083
+ * must never destroy a canvas mid-selection.
2084
+ *
2085
+ * `initialView` and `errorDisplay` are here because they are read once at
2086
+ * construction and never re-applied — leaving them out does not make them
2087
+ * "live", it makes them silently ignored after mount, which is the worse of the
2088
+ * two behaviours. React already treated them this way; Vue and Angular did not
2089
+ * expose them at all.
2090
+ */
2091
+ declare const SEATING_CHART_IDENTITY_PROPS: readonly ["event", "apiBase", "maxSelection", "numberOfPlacesToSelect", "selectionValidators", "publicKey", "locale", "currency", "colorblindSafe", "initialView", "errorDisplay"];
2092
+ type SeatingChartIdentityProp = (typeof SEATING_CHART_IDENTITY_PROPS)[number];
2093
+ /** Non-callback options a wrapper accepts as props/inputs and passes straight through. */
2094
+ declare const SEATING_CHART_VALUE_PROPS: readonly ["event", "apiBase", "maxSelection", "numberOfPlacesToSelect", "selectionValidators", "publicKey", "locale", "currency", "colorblindSafe", "initialView", "errorDisplay", "selectedObjects", "selectableObjects", "messages", "seatTooltip", "buyerAccessTokenProvider", "buyerAccessToken"];
2095
+ type SeatingChartValueProp = (typeof SEATING_CHART_VALUE_PROPS)[number];
2096
+ /** Every callback option a wrapper wires to its own event mechanism. */
2097
+ declare const SEATING_CHART_CALLBACK_PROPS: readonly ["onSelectionChange", "onSelectionValidityChange", "onSelectionValid", "onSelectionInvalid", "onSelectionLimit", "onHold", "onHoldRestored", "onHoldExpired", "onGAClick", "onGAPrompt", "onError", "onDeckTap", "onHint", "onSeatHover", "onAccessExpired", "onAccessUnavailable", "onSelectedObjectUnavailable"];
2098
+ type SeatingChartCallbackProp = (typeof SEATING_CHART_CALLBACK_PROPS)[number];
2099
+ /** The value half of the options, as a wrapper holds it. */
2100
+ type SeatingChartValues = Pick<SeatingChartOptions, SeatingChartValueProp>;
2101
+ /** The callback half, already bound to the wrapper's event mechanism. */
2102
+ type SeatingChartCallbacks = Pick<SeatingChartOptions, SeatingChartCallbackProp>;
2103
+ /**
2104
+ * Assemble the options literal for `new SeatingChart(...)`.
2105
+ *
2106
+ * Picks by the lists above rather than spreading, so a wrapper cannot pass a
2107
+ * stray prop of its own (React's `className`/`style`, Vue's attrs) into the
2108
+ * SDK, and cannot forget one either.
2109
+ */
2110
+ declare function buildSeatingChartOptions(container: HTMLElement, values: Partial<SeatingChartValues>, callbacks: SeatingChartCallbacks): SeatingChartOptions;
2111
+
2112
+ /**
2113
+ * A secure, framework-neutral host for the SeatLayer chart Designer.
2114
+ *
2115
+ * The Designer remains an iframe so a platform never gives its SeatLayer secret
2116
+ * key to a browser. This class owns the iframe lifecycle and accepts messages
2117
+ * only from that iframe's exact origin.
2118
+ */
2119
+ type EmbeddedDesignerEventType = 'seatlayer.designer.ready' | 'seatlayer.designer.saved' | 'seatlayer.designer.published' | 'seatlayer.designer.close' | 'seatlayer.designer.error';
2120
+ interface EmbeddedDesignerMessage {
2121
+ type: EmbeddedDesignerEventType;
2122
+ chartId?: string;
2123
+ workspaceId?: string;
2124
+ expiresAt?: number;
2125
+ code?: string;
2126
+ message?: string;
2127
+ meta?: unknown;
2048
2128
  /**
2049
- * Automatically remember the active hold id in sessionStorage and restore it
2050
- * when this event's picker mounts again. Default true. Set false when the host
2051
- * owns hold persistence and supplies initialHoldId itself.
2129
+ * Set by the Designer on an error it raised while the editor was already
2130
+ * running (a failed autosave, thumbnail upload, reload…). Such an error is
2131
+ * about ONE operation, not about the session, so the SDK reports it to the
2132
+ * host and leaves the live editor mounted instead of replacing it with the
2133
+ * dead-end card. Absent on older Designer builds — see
2134
+ * {@link EmbeddedDesigner} for the phase-based fallback.
2052
2135
  */
2053
- restoreHold?: boolean;
2136
+ fatal?: boolean;
2137
+ /** The operation that failed, when `fatal` is `false` (e.g. `'save'`). */
2138
+ action?: string;
2139
+ }
2140
+ interface EmbeddedDesignerOptions {
2141
+ /** The short-lived URL returned by your backend's Designer-session call. */
2142
+ designerUrl: string;
2143
+ /** CSS selector or element where the iframe is mounted. */
2144
+ container: string | HTMLElement;
2054
2145
  /**
2055
- * Render the real chart and live seat statuses without allowing selection,
2056
- * holds or checkout. This is for venue previews and pre-sale Website pages;
2057
- * it is enforced by the widget even if the event later opens while mounted.
2058
- * Default false.
2146
+ * Verify the message belongs to the chart your backend opened. When set, the
2147
+ * id is required on `ready` and every runtime lifecycle message. Only an error
2148
+ * raised before the iframe resolves its session may omit it.
2059
2149
  */
2060
- readOnly?: boolean;
2150
+ expectedChartId?: string;
2061
2151
  /**
2062
- * Confirm mode: tapping a seat shows a confirmation card with section, row,
2063
- * seat, category, price and Select/Cancel before it enters the tray. Default
2064
- * true for the full buyer picker; set false only when the host supplies its
2065
- * own equivalent confirmation UI.
2152
+ * Verify the message belongs to the workspace your backend opened. Uses the
2153
+ * same ready/runtime requirement and pre-session error exception as
2154
+ * `expectedChartId`.
2066
2155
  */
2067
- confirmSelection?: boolean;
2156
+ expectedWorkspaceId?: string;
2157
+ title?: string;
2158
+ className?: string;
2159
+ style?: Partial<CSSStyleDeclaration>;
2160
+ allow?: string;
2161
+ referrerPolicy?: ReferrerPolicy;
2068
2162
  /**
2069
- * Offer a "View from seat" 360° preview (confirm popover + tray chips). The
2070
- * panorama is generated from the chart geometry, or the organizer's uploaded
2071
- * photo when a seat carries one. Default true; set false to hide the affordance.
2163
+ * Show the built-in branded loading skeleton and error/expiry card inside the
2164
+ * container while the Designer boots. Defaults to `true`. Set `false` when the
2165
+ * host renders its own loading and error chrome.
2072
2166
  */
2073
- seatView?: boolean;
2167
+ showLoadingState?: boolean;
2074
2168
  /**
2075
- * WHERE the buyer goes once their seats are held. Default `'handoff'`.
2076
- *
2077
- * 'handoff' (default, and every integration that has ever existed) the
2078
- * widget fires {@link onCheckout} with a holdId and priced line
2079
- * items, and YOUR server takes the money. Nothing about this path
2080
- * changes, and no payment code is even downloaded.
2081
- * 'hosted' the widget takes the money through the gateway the ORGANIZER
2082
- * connected, on their account the "sell tickets with no
2083
- * backend" path. Requires the org to be on hosted checkout and
2084
- * the event to have a gateway assigned; when it does not, this
2085
- * falls back to `'handoff'` for that buyer rather than dead-ending
2086
- * them, and reports why through {@link onCheckoutUnavailable}.
2087
- *
2088
- * Named for the destination rather than as a boolean flag because there is a
2089
- * real third answer coming and `hostedCheckout: true` would have no room for
2090
- * it; spelling the default out also makes a host's intent legible in their own
2091
- * source instead of hiding it in an absent option.
2169
+ * If the Designer never posts `ready` within this many milliseconds, the host
2170
+ * transitions to the error card with a timeout message. Defaults to `20000`.
2171
+ * Only used when `showLoadingState` is enabled.
2172
+ */
2173
+ loadingTimeoutMs?: number;
2174
+ /**
2175
+ * How to size the iframe's height. The Designer is a full application (its
2176
+ * shell is `position:fixed; height:100dvh`), not flowing content, so it should
2177
+ * fill its box rather than be measured.
2092
2178
  *
2093
- * TWO THINGS ARE WORTH KNOWING BEFORE YOU SWITCH THIS ON:
2179
+ * - `'fill'` (default): container-aware. On mount the SDK probes whether the
2180
+ * host gave the container a DEFINITE (bounded) height:
2181
+ * - **Bounded container** (a fixed-height block, `height`/`max-height`,
2182
+ * `flex:1; min-h:0`, a resolved `%`, etc.) → the iframe fills 100% of
2183
+ * that block and tracks its size live via a `ResizeObserver`.
2184
+ * - **Content-sized container** (the block collapses to whatever the iframe
2185
+ * measures — typical full-page usage) → the iframe grows so its bottom
2186
+ * edge reaches the bottom of the viewport (`window.innerHeight -
2187
+ * iframe.top`), recomputed (rAF-throttled) on `resize` /
2188
+ * `orientationchange` / `scroll`.
2189
+ * Either way the result is clamped to `minHeight`. The verdict is cached but
2190
+ * re-probed on `resize`/`orientationchange` so a responsive host layout can
2191
+ * flip between the two. The legacy `seatlayer.designer.resize` message is
2192
+ * ignored in `'fill'` mode: it is circular, because the fixed-position shell
2193
+ * just echoes the iframe height.
2194
+ * - a number: a fixed pixel height. In this mode the legacy resize message is
2195
+ * still honoured (unless `autoResize` is `false`) so older hosts keep growing.
2094
2196
  *
2095
- * 1. It needs the widget's own transport. A host-supplied `transport` owns its
2096
- * credentials and its backend, so hosted checkout stays off there (with one
2097
- * console warning) rather than reaching past it to api.seatlayer.io.
2098
- * 2. WHERE A HOSTED GATEWAY RETURNS THE BUYER is settled by {@link returnUrl}
2099
- * and by the organizer. Without one — or from an origin the organizer has
2100
- * not declared — the buyer comes back to SeatLayer's own buyer page and is
2101
- * confirmed THERE, not in this widget. Declare the embedding site under
2102
- * Embed domains in the dashboard and pass `returnUrl`, and the buyer
2103
- * returns to your page instead. In-page gateways never navigate away at
2104
- * all, so they are unaffected either way.
2197
+ * All SDK-managed heights are written with `!important` priority so a host
2198
+ * theme's `iframe { height: !important }` cannot override them.
2105
2199
  */
2106
- checkout?: 'handoff' | 'hosted';
2200
+ height?: 'fill' | number;
2201
+ /** Minimum height (px) that `'fill'` mode clamps to. Defaults to `480`. */
2202
+ minHeight?: number;
2107
2203
  /**
2108
- * Where a redirecting gateway should send the buyer back to, for
2109
- * `checkout: 'hosted'`.
2110
- *
2111
- * The server keeps this URL's path and query and adds only a non-secret
2112
- * `seatlayer_checkout=success|cancelled` outcome. The SDK keeps the order id
2113
- * in this tab's sessionStorage before leaving for the gateway. Point this at
2114
- * a page on the same origin (often `window.location.href`) and mount a picker
2115
- * there so it can resume without putting the order capability in a URL.
2204
+ * Auto-grow the iframe to the height the Designer reports over the resize
2205
+ * protocol (`seatlayer.designer.resize`). Only applies when `height` is a fixed
2206
+ * number; ignored in `'fill'` mode. Defaults to `true`. Set `false` when the
2207
+ * host sizes a fixed-height iframe itself.
2208
+ */
2209
+ autoResize?: boolean;
2210
+ /**
2211
+ * Called when the user presses "Try again" on the error card. Use it to mint a
2212
+ * fresh Designer session and call `setDesignerUrl()` with the new URL, which
2213
+ * recreates the iframe and returns to the loading state. When omitted, "Try
2214
+ * again" reloads the current `designerUrl` in place.
2116
2215
  *
2117
- * It is validated, not trusted: the organizer declares their embed origins
2118
- * in the dashboard, and an undeclared origin is ignored rather than
2119
- * refused — the sale still completes, the buyer just finishes on
2120
- * SeatLayer's page. Supplying a URL therefore cannot authorize it, which is
2121
- * what stops a copied snippet from redirecting a paid buyer anywhere it
2122
- * likes.
2123
- * As with every script embed, code already running on the host origin can
2124
- * read that origin's sessionStorage; iframe embedding provides SOP isolation.
2216
+ * When supplied, it also powers automatic session renewal see
2217
+ * {@link EmbeddedDesignerOptions.autoRenewSession}.
2125
2218
  */
2126
- returnUrl?: string;
2219
+ onRequestRelaunch?: () => void;
2127
2220
  /**
2128
- * Buyer pressed the CTA and the hold succeeded hand off to YOUR checkout.
2129
- * `hold` and `seats` are the legacy args (unchanged since 0.6). `handoff` (P4)
2130
- * is the stable, self-contained {@link CheckoutHandoff} to build your order
2131
- * against — holdId, expiry, currency and priced line items. Prefer it.
2221
+ * Keep long editing sessions alive without the user ever hitting the expiry
2222
+ * wall. Designer sessions are short-lived security tokens; when the host wires
2223
+ * `onRequestRelaunch` the SDK, with this enabled, will:
2132
2224
  *
2133
- * Under `checkout: 'hosted'` this fires ONLY when hosted checkout cannot run
2134
- * for this event, so a host can keep one code path for both. It never fires
2135
- * alongside a payment the widget is taking itself.
2225
+ * - **Renew proactively.** From each `ready` message's `expiresAt` it schedules
2226
+ * a silent relaunch shortly before the session lapses (~3 min ahead; for a
2227
+ * TTL under 15 min it renews after 80% of the remaining life, and never
2228
+ * sooner than 30s after `ready`). The host mints a fresh session and swaps
2229
+ * `designerUrl`, so the editor keeps working with no error card.
2230
+ * - **Recover on expiry.** If an expiry error still slips through (a slept
2231
+ * laptop woke past the renewal window, say) it makes ONE automatic relaunch
2232
+ * attempt before showing the "Try again" card, and only falls back to the
2233
+ * card if that relaunch also fails.
2234
+ *
2235
+ * Defaults to `true` whenever `onRequestRelaunch` is provided; a no-op without
2236
+ * it. Set `false` to keep the fully manual "Try again" behavior.
2136
2237
  */
2137
- onCheckout?: (hold: HoldResult, seats: PickerSeat[], handoff: CheckoutHandoff) => void;
2238
+ autoRenewSession?: boolean;
2239
+ onReady?: (message: EmbeddedDesignerMessage) => void;
2240
+ onSaved?: (message: EmbeddedDesignerMessage) => void;
2241
+ onPublished?: (message: EmbeddedDesignerMessage) => void;
2242
+ onClose?: (message: EmbeddedDesignerMessage) => void;
2243
+ onError?: (message: EmbeddedDesignerMessage) => void;
2244
+ }
2245
+ /** Mount, replace, and destroy a scoped Designer iframe safely. */
2246
+ declare class EmbeddedDesigner {
2247
+ private options;
2248
+ private frame;
2249
+ private designerOrigin;
2250
+ private overlay;
2251
+ private timeoutTimer;
2252
+ /** Proactive session-renewal timer; armed from each `ready`, cleared on re-mount. */
2253
+ private renewTimer;
2254
+ /** Last identity-checked session expiry, so a live policy change can re-arm. */
2255
+ private sessionExpiresAt;
2138
2256
  /**
2139
- * `checkout: 'hosted'` was asked for and this event cannot take money.
2140
- * The seats ARE held the buyer is mid-journey — so this is a routing
2141
- * decision, not an error, and it is never collapsed into {@link onError}.
2142
- *
2143
- * `reason` carries the server's three-way answer verbatim, because two of the
2144
- * three give opposite advice: `payments_off_for_event` means the organizer
2145
- * deliberately does not sell this event online (nothing is wrong), while
2146
- * `unavailable_for_event` means they switched it on and it is broken. Anything
2147
- * unreadable — a failed lookup, an older worker — reads as `not_configured`,
2148
- * which asserts the least about them.
2149
- *
2150
- * `onCheckout` fires immediately after this with the same hold. Supply either
2151
- * (or both) and you own the next screen; supply NEITHER and the widget shows
2152
- * the buyer an honest card of its own rather than swallowing the press.
2257
+ * One automatic recovery relaunch is allowed per expiry. Reset ONLY when a fresh
2258
+ * `ready` arrivesdeliberately not on re-mount — so a session that keeps failing
2259
+ * to load can't loop the host through endless silent relaunches.
2153
2260
  */
2154
- onCheckoutUnavailable?: (event: {
2155
- reason: PaymentOptionsReason;
2156
- handoff: CheckoutHandoff;
2157
- }) => void;
2261
+ private autoRecoverUsed;
2262
+ private phase;
2158
2263
  /**
2159
- * `checkout: 'hosted'` only the gateway's webhook landed and the order is
2160
- * PAID. The one signal a host with no backend actually needs, and the only
2161
- * place a receipt can come from on a page that has no server of its own.
2162
- *
2163
- * Distinct from {@link onBooked}, which reports the same sale seen from the
2164
- * seat map over the realtime channel and cannot fire at all for a buyer whose
2165
- * widget was torn down by a redirect to the gateway.
2264
+ * Set only after an identity-checked `ready`. Unlike `phase`, this remains true
2265
+ * if a later fatal error renders the error card, so no subsequent callback can
2266
+ * shed the chart/workspace identity the live session already established.
2166
2267
  */
2167
- onOrderConfirmed?: (order: OrderStatusResult) => void;
2268
+ private identityEstablished;
2269
+ private restoreContainerPosition;
2270
+ private pinned;
2271
+ private frameStyleBeforeFs;
2272
+ private docOverflowBeforeFs;
2273
+ private bodyOverflowBeforeFs;
2274
+ private fsKeyHandler;
2275
+ /** Latest height (px string) the Designer reported; re-applied after unpin. */
2276
+ private lastAutoHeight;
2277
+ private fillRaf;
2278
+ private reprobeRaf;
2279
+ private fillListening;
2280
+ /** Resolved container element (fill measurement + ResizeObserver target). */
2281
+ private containerEl;
2282
+ /** Cached fill verdict: 'container' = bounded block, 'viewport' = full page. */
2283
+ private fillMode;
2284
+ /** Live block-size tracking in container-fill mode; disconnected on destroy. */
2285
+ private resizeObs;
2286
+ constructor(options: EmbeddedDesignerOptions);
2287
+ mount(): HTMLIFrameElement;
2288
+ /** Replace the iframe instead of assigning a new fragment to an existing one. */
2289
+ setDesignerUrl(designerUrl: string): HTMLIFrameElement;
2290
+ getIframe(): HTMLIFrameElement | null;
2291
+ /** Update iframe sizing without replacing the live Designer session. */
2292
+ setSizing(height: 'fill' | number | undefined, minHeight: number | undefined): void;
2293
+ /** Update renewal/expiry-recovery policy without replacing the iframe. */
2294
+ setRelaunchPolicy(onRequestRelaunch: (() => void) | undefined, autoRenewSession: boolean | undefined): void;
2295
+ destroy(): void;
2296
+ private loadingStateEnabled;
2297
+ private autoResizeEnabled;
2298
+ /** Fill mode is the default; a numeric `height` opts into a fixed pixel box. */
2299
+ private fillEnabled;
2300
+ /** Write an SDK-managed height with `!important` so a host theme can't win. */
2301
+ private setFrameHeight;
2168
2302
  /**
2169
- * The held seats were BOOKED (P4) your server completed payment and the
2170
- * booking landed over the realtime channel while the widget was still open.
2171
- * The widget shows a success state; use this to advance your own UI (receipt,
2172
- * redirect). Fires once per hold.
2303
+ * Decide whether the host gave the container a DEFINITE (bounded) height a
2304
+ * fixed block the embed should fill 100% of versus a content-sized container
2305
+ * that collapses to whatever the iframe measures (full-page usage).
2306
+ *
2307
+ * We drive the iframe to two extreme heights within a single synchronous task
2308
+ * and watch whether the container follows: a bounded box barely moves, a
2309
+ * content-sized one grows with the iframe. Because we restore the height before
2310
+ * yielding, the browser only lays out — it never paints the extremes, so there
2311
+ * is no visible flash. Works for px, resolved `%`, and flex (`flex:1;min-h:0`)
2312
+ * heights, and leaves a mere `min-height` floor classified as content-sized so
2313
+ * full-page hosts keep the old viewport-fill behavior.
2173
2314
  */
2174
- onBooked?: (handoff: CheckoutHandoff) => void;
2175
- /** Selection changed (tap or best-available). */
2176
- onSelectionChange?: (seats: PickerSeat[]) => void;
2177
- /** Selection-rule state after each selection change. */
2178
- onSelectionValidityChange?: (state: PickerSelectionValidity) => void;
2179
- /** The configured selection rules have just become valid. */
2180
- onSelectionValid?: (seats: PickerSeat[]) => void;
2181
- /** The configured selection rules have just become invalid. */
2182
- onSelectionInvalid?: (state: PickerSelectionValidity) => void;
2183
- /** A selection attempt reached the active maximum. */
2184
- onSelectionLimit?: (maxSelection: number) => void;
2315
+ private detectFillMode;
2185
2316
  /**
2186
- * Active hold changed because it was created, restored, extended, partially
2187
- * released, or fully released. Hosts should persist this state for route
2188
- * navigation and clear their checkout cart when `hold` becomes null.
2317
+ * Size the iframe for the current fill verdict, clamped to `minHeight`. In
2318
+ * container mode it fills 100% of the bounded block; in viewport mode its
2319
+ * bottom edge meets the bottom of the viewport (`window.innerHeight - top`).
2320
+ * No-op while pinned fullscreen (the pin fills the viewport itself).
2189
2321
  */
2190
- onHoldChange?: (hold: HoldResult | null, seats: PickerSeat[], handoff: CheckoutHandoff | null) => void;
2191
- /** The open hold expired server-side (widget already reset itself). */
2192
- onHoldExpired?: () => void;
2193
- /** A prior active hold was verified and restored into the tray. */
2194
- onHoldRestored?: (hold: HoldResult, seats: PickerSeat[], handoff: CheckoutHandoff) => void;
2195
- /** Modal only: the buyer closed the picker (ESC / scrim / ✕). */
2196
- onClose?: () => void;
2322
+ private applyFill;
2323
+ /** rAF-throttled fill recompute, so a burst of scroll/RO ticks coalesces. */
2324
+ private scheduleFill;
2197
2325
  /**
2198
- * The buyer access session lapsed. `refreshed` says whether the provider
2199
- * already recovered it false means private inventory is now unavailable and
2200
- * `onAccessUnavailable` follows. Never collapsed into `onError`: an expiry is
2201
- * a recoverable, buyer-explainable state, not a network failure (guide §10).
2326
+ * rAF-throttled re-probe: a host layout change (responsive breakpoint, a block
2327
+ * gaining/losing a definite height) can flip the verdict, so `resize` /
2328
+ * `orientationchange` re-detect and swap the container observer accordingly.
2202
2329
  */
2203
- onAccessExpired?: (event: BuyerAccessExpiredEvent) => void;
2330
+ private scheduleReprobe;
2331
+ /** Attach/detach the container ResizeObserver to match the current verdict. */
2332
+ private syncContainerObserver;
2333
+ private startFill;
2334
+ private stopFill;
2204
2335
  /**
2205
- * Private inventory is unavailable and refreshing will not fix it revoked,
2206
- * paused, wrong origin/event/mode, or the provider failed. Carries a reason,
2207
- * never a channel name, id, colour or count. The widget shows its own
2208
- * explanatory panel; return nothing to keep it, or handle the state yourself.
2336
+ * Pin the iframe over the host page as a viewport-filling overlay. We save the
2337
+ * iframe's inline style and the document scroll state so `unpinFullscreen`
2338
+ * restores everything exactly. Escape (host-side) also exits.
2209
2339
  */
2210
- onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
2340
+ private pinFullscreen;
2341
+ /** Undo `pinFullscreen`: restore the iframe style + scroll lock. Idempotent. */
2342
+ private unpinFullscreen;
2343
+ private clearTimeoutTimer;
2211
2344
  /**
2212
- * Selected-but-unheld units stopped being selectable someone else took
2213
- * them, or an allocation change moved them out of this buyer's scope. The
2214
- * widget has already dropped them from the tray.
2345
+ * Auto-renewal (proactive + one expiry recovery) is on when the host wired a
2346
+ * relaunch hook and did not opt out. Without the hook there is nothing to call,
2347
+ * so it is a no-op.
2215
2348
  */
2216
- onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
2349
+ private autoRenewEnabled;
2350
+ private clearRenewTimer;
2217
2351
  /**
2218
- * A buyer tapped a general-admission area (on the map, or in the tray's
2219
- * `Areas` list) and the widget is about to open its own quantity prompt.
2352
+ * Arm the proactive renewal timer from a `ready` message's `expiresAt` (epoch
2353
+ * ms). We relaunch a comfortable lead before expiry so the host can mint a fresh
2354
+ * session and swap `designerUrl` without the user ever seeing the expiry card:
2220
2355
  *
2221
- * Return `true` to take the interaction over completely the built-in
2222
- * popover/sheet is suppressed, and the host drives the choice with whatever
2223
- * UI it likes, landing the result through `prompt.confirm(quantity)` (a plain
2224
- * number for a single-tier area, or `{ [tierId]: quantity }`) or dropping it
2225
- * with `prompt.cancel()`. Return anything else (or nothing) and the built-in
2226
- * prompt opens as usual, so this is also usable as a plain notification.
2356
+ * - normal TTL (≥ 15 min): renew {@link RENEW_LEAD_MS} (~3 min) before expiry;
2357
+ * - short TTL (< 15 min): renew after {@link RENEW_SHORT_TTL_FRACTION} (80%) of
2358
+ * the remaining life, so the lead can't overshoot the whole session;
2359
+ * - either way, never sooner than {@link RENEW_MIN_DELAY_MS} (30s) after `ready`
2360
+ * so a burst of `ready` messages can't spin the host.
2227
2361
  *
2228
- * `prompt.max` already folds in the area's live availability AND the
2229
- * order-wide ticket cap minus everything chosen elsewhere; a `confirm` above
2230
- * it is clamped rather than rejected.
2362
+ * Re-armed on every `ready`; cleared on destroy / setDesignerUrl (via re-mount).
2363
+ * A no-op when auto-renewal is off or `expiresAt` is missing/already past — the
2364
+ * expiry-error path recovers a session that has already lapsed.
2231
2365
  */
2232
- onGAPrompt?: (prompt: GAPromptRequest) => boolean | void;
2233
- onError?: (err: unknown) => void;
2366
+ private scheduleRenewal;
2367
+ private ensureContainerPositioned;
2368
+ private restoreContainerStyle;
2369
+ private removeOverlay;
2370
+ private showError;
2371
+ private handleTryAgain;
2372
+ /**
2373
+ * Build (or rebuild) the overlay for the given phase. A single overlay element
2374
+ * is reused so we never stack stale skeletons or cards.
2375
+ */
2376
+ private renderOverlay;
2377
+ private buildSkeleton;
2378
+ private buildErrorCard;
2379
+ private handleMessage;
2234
2380
  }
2235
2381
 
2236
2382
  /** What the saved-seat comparison chip needs from the widget around it. */
@@ -2261,6 +2407,143 @@ declare class View3dCompareChip {
2261
2407
  mainButton(): HTMLElement | null;
2262
2408
  }
2263
2409
 
2410
+ /**
2411
+ * The cart's arithmetic, in ONE place.
2412
+ *
2413
+ * Held server lines, freshly-picked seats and pending GA quantities, all priced
2414
+ * through the picker's own `paidPrice`. The tray renders from this and the
2415
+ * WebMCP `get_selection` tool reads it, so a buyer looking at the sheet and an
2416
+ * agent asking what is selected can never be told two different totals — which
2417
+ * is exactly what a second copy of these six reduces would eventually produce.
2418
+ *
2419
+ * It lives here rather than on SeatPicker because that file is pinned at its
2420
+ * size in the app's file-size ratchet and may not grow, and because a pure
2421
+ * function over a named slice is easier to check than a method with fifty
2422
+ * neighbours.
2423
+ */
2424
+
2425
+ /** A chart category as far as pricing is concerned. */
2426
+ interface PricedCategory {
2427
+ key?: string;
2428
+ price?: number;
2429
+ tiers?: {
2430
+ id?: string;
2431
+ price: number;
2432
+ }[];
2433
+ }
2434
+
2435
+ /**
2436
+ * WebMCP tools for the buyer picker (`webMcp` option, off by default).
2437
+ *
2438
+ * An AI agent running in the buyer's browser drives the REAL widget through the
2439
+ * same functions a tap does — search, select, and (only when the integrator
2440
+ * opts in) hold — instead of guessing at pixels. Nothing here runs unless the
2441
+ * host asked for it, and nothing here may break a render: a runtime without the
2442
+ * API, or one that rejects a registration, costs one console.warn. The module
2443
+ * talks to a narrow `WebMcpHost` rather than to `SeatPicker`, so every tool is
2444
+ * testable without mounting a widget.
2445
+ */
2446
+ /** Event facts the picker learned when the chart loaded. */
2447
+ interface WebMcpEventFacts {
2448
+ name: string;
2449
+ venue: string | null;
2450
+ startsAt: number | null;
2451
+ timezone: string | null;
2452
+ currency: string;
2453
+ salesClosed: boolean;
2454
+ }
2455
+ interface WebMcpCategoryFacts {
2456
+ key: string;
2457
+ label: string;
2458
+ price?: number;
2459
+ priceRange?: {
2460
+ min: number;
2461
+ max: number;
2462
+ };
2463
+ available: number;
2464
+ }
2465
+ /** One bookable seat, flattened for search. Ids/labels stay the picker's. */
2466
+ interface WebMcpSeatFacts {
2467
+ id: string;
2468
+ label: string;
2469
+ /** Contiguity owner: the logical row a run may not bridge. */
2470
+ rowKey: string;
2471
+ /** Section owner, used by the `together: false` relaxation. */
2472
+ sectionKey: string;
2473
+ /** Seat order inside the row; a missing integer marks an aisle. */
2474
+ index: number;
2475
+ categoryKey: string;
2476
+ /** The price the buyer would actually pay (channel/host overrides applied). */
2477
+ price: number;
2478
+ zoneId?: string;
2479
+ available: boolean;
2480
+ }
2481
+ /** Buyer-facing words for one seat — the SAME short row/seat the widget's own
2482
+ * seat card prints, never the raw row label ("Stalls A A"), which repeats the
2483
+ * section and reads to an agent as a bug. */
2484
+ interface WebMcpSeatWords {
2485
+ section: string;
2486
+ row: string;
2487
+ seat: string;
2488
+ category: string;
2489
+ }
2490
+ interface WebMcpSelectionState {
2491
+ seats: Array<WebMcpSeatWords & {
2492
+ id: string;
2493
+ label: string;
2494
+ price: number;
2495
+ }>;
2496
+ count: number;
2497
+ total: number;
2498
+ currency: string;
2499
+ hold: {
2500
+ id: string;
2501
+ expiresAt: number;
2502
+ } | null;
2503
+ validity: unknown;
2504
+ }
2505
+ type WebMcpHoldOutcome = {
2506
+ ok: true;
2507
+ hold: {
2508
+ id: string;
2509
+ expiresAt: number;
2510
+ };
2511
+ handoff?: unknown;
2512
+ } | {
2513
+ ok: false;
2514
+ reason: string;
2515
+ };
2516
+ /** Everything the tools need from the widget, and nothing else. */
2517
+ interface WebMcpHost {
2518
+ event(): WebMcpEventFacts | null;
2519
+ selectionLimits(): {
2520
+ max: number;
2521
+ required: number | null;
2522
+ };
2523
+ categories(): WebMcpCategoryFacts[];
2524
+ zones(): Array<{
2525
+ id: string;
2526
+ label: string;
2527
+ }>;
2528
+ hasPremiumSeats(): boolean;
2529
+ seats(): WebMcpSeatFacts[];
2530
+ /** Buyer-facing spatial words for one seat, resolved lazily per result. */
2531
+ describeSeat(id: string): WebMcpSeatWords | null;
2532
+ selection(): WebMcpSelectionState;
2533
+ clearSelection(): void;
2534
+ selectObjects(ids: string[]): void;
2535
+ hold(): Promise<WebMcpHoldOutcome>;
2536
+ }
2537
+ interface WebMcpRegistration {
2538
+ /** Tool names actually registered, in registration order. */
2539
+ names: string[];
2540
+ /** Abort the registration signal; unregister by name where supported. */
2541
+ dispose: () => void;
2542
+ }
2543
+
2544
+ /** What `PickerController.render()` reports back about the loaded event. */
2545
+ type PickerRenderInfo = NonNullable<Awaited<ReturnType<PickerController['render']>>>;
2546
+
2264
2547
  declare class SeatPicker implements GaPromptPicker {
2265
2548
  /** @internal Read by pickerGaPrompt for the `onGAPrompt` host hook. */
2266
2549
  readonly opts: SeatPickerOptions;
@@ -2277,7 +2560,7 @@ declare class SeatPicker implements GaPromptPicker {
2277
2560
  /** @internal */ readonly controller: PickerController;
2278
2561
  /** @internal */ maxTickets: number;
2279
2562
  /** Exact ticket count required before checkout; null keeps ordinary 1..max behavior. */
2280
- private readonly exactTickets;
2563
+ /** @internal */ readonly exactTickets: number | null;
2281
2564
  private lastSelectionValidity;
2282
2565
  /** Original host pricing, kept separate from live server offer overrides. */
2283
2566
  private readonly hostPricing;
@@ -2287,7 +2570,11 @@ declare class SeatPicker implements GaPromptPicker {
2287
2570
  /** @internal */ root: HTMLDivElement | null;
2288
2571
  private mapHost;
2289
2572
  private rendered;
2290
- private destroyed;
2573
+ /** @internal */ destroyed: boolean;
2574
+ /** @internal Chart-load facts; WebMCP's describe_event answers from them. */
2575
+ renderInfo: PickerRenderInfo | null;
2576
+ /** @internal Live WebMCP registration, when the host opted in. */
2577
+ webMcp: WebMcpRegistration | null;
2291
2578
  /** Bumped on every locale REQUEST, so a load that resolves late can tell it
2292
2579
  * is no longer the one asked for. See {@link applyActiveLocale}. */
2293
2580
  private localeGeneration;
@@ -2304,15 +2591,15 @@ declare class SeatPicker implements GaPromptPicker {
2304
2591
  private offerVisibilityHandler;
2305
2592
  /** Short-lived UI motion timers; all are cancelled on destroy. */
2306
2593
  private motionTimers;
2307
- private currency;
2594
+ /** @internal */ currency: string;
2308
2595
  private eventTimezone;
2309
2596
  /** The event's formatted date-time (event-zone), reused by the closed-state note. */
2310
2597
  private eventWhenText;
2311
2598
  private offerAvailability;
2312
2599
  /** Server-authoritative channel rules, projected only for this buyer scope. */
2313
2600
  private readonly channelByObject;
2314
- private readonly pricesByChannel;
2315
- private hold;
2601
+ /** @internal */ readonly pricesByChannel: Map<string, ChannelPriceOverride[]>;
2602
+ /** @internal */ hold: HoldResult | null;
2316
2603
  /** Latest server expiry for the open hold (moves on extend). */
2317
2604
  private holdExpiresAt;
2318
2605
  /** True once we handed off to checkout — arms booked-confirmation detection. */
@@ -2539,6 +2826,9 @@ declare class SeatPicker implements GaPromptPicker {
2539
2826
  /** Controller callbacks are wired once, before the first async chart load. */
2540
2827
  private wireController;
2541
2828
  render(): Promise<this>;
2829
+ /** @internal Both hand off to pickerWebMcpBridge, which owns the mapping. */
2830
+ registerAgentTools(): Promise<void>;
2831
+ /** @internal */ webMcpHost(holds: boolean): WebMcpHost;
2542
2832
  /** Wire host chrome after the skeleton exists and before the renderer owns its map host. */
2543
2833
  private wireBuyerShell;
2544
2834
  /**
@@ -2656,7 +2946,7 @@ declare class SeatPicker implements GaPromptPicker {
2656
2946
  /** A completed hold gets one short map ripple per concrete seat. */
2657
2947
  private flashHeldSeats;
2658
2948
  /** Update only the action affordance; selection callbacks must not refire. */
2659
- private committedSelection;
2949
+ /** @internal */ committedSelection(): PickerSeat[];
2660
2950
  private pendingSelectionCount;
2661
2951
  private heldTicketCount;
2662
2952
  /** @internal */ totalTicketCount(): number;
@@ -2676,10 +2966,11 @@ declare class SeatPicker implements GaPromptPicker {
2676
2966
  /** Attach the F3 overview minimap into the bottom-left chrome region. */
2677
2967
  private buildMinimap;
2678
2968
  /** Effective category summary price. Object-specific channel prices are added to the range below. */
2679
- private catPrice;
2680
- /** Complete buyer-visible price range, including every ticket tier and
2681
- * tier-specific host override. */
2682
- private catPriceRange;
2969
+ /** @internal */ catPrice(c: PricedCategory): number | undefined;
2970
+ /** @internal */ catPriceRange(c: PricedCategory): {
2971
+ min: number;
2972
+ max: number;
2973
+ } | undefined;
2683
2974
  /** Build the compact price selector in the panel header. Choosing a band both
2684
2975
  * filters availability and smoothly frames the matching seats on the map. */
2685
2976
  private buildPriceFilter;
@@ -2818,7 +3109,7 @@ declare class SeatPicker implements GaPromptPicker {
2818
3109
  */
2819
3110
  private removeHeldLabels;
2820
3111
  private handleChangeSeats;
2821
- private handleCta;
3112
+ /** @internal */ handleCta(): Promise<void>;
2822
3113
  private startHoldTimer;
2823
3114
  private stopHoldTimer;
2824
3115
  /** Show/refresh (or hide) the "Need more time?" prompt with the live seconds left. */
@@ -3747,4 +4038,4 @@ interface AttachPickerFrameOptions {
3747
4038
  */
3748
4039
  declare function attachPickerFrame(iframe: HTMLIFrameElement, opts?: AttachPickerFrameOptions): () => void;
3749
4040
 
3750
- export { ApiError, type AttachPickerFrameOptions, type BestAvailableResult, BuyerAccessContext, type BuyerAccessExpiredEvent, type BuyerAccessRefreshReason, type BuyerAccessToken, type BuyerAccessTokenProvider, BuyerAccessUnavailableError, type BuyerAccessUnavailableEvent, type BuyerAccessUnavailableReason, BuyerRealtimeClient, type BuyerRealtimeOptions, type CheckoutHandoff, type CheckoutLineItem, type CheckoutSessionResult, EmbeddedDesigner, type EmbeddedDesignerEventType, type EmbeddedDesignerMessage, type EmbeddedDesignerOptions, type GAAreaAvailability, type GAPromptRequest, type GaPromptTier, type HoldConflict, type HoldLineItem, type HoldResult, type OrderStatusResult, type PaymentOptionsReason, type PaymentOptionsResult, type PaymentProviderName, type PerformanceGroupAvailability, type PerformanceGroupCheckoutHandoff, type PerformanceGroupDescriptor, PerformanceGroupDestroyedError, type PerformanceGroupHold, type PerformanceGroupHoldAllocation, type PerformanceGroupOperationEvent, type PerformanceGroupOperationState, PerformanceGroupPicker, type PerformanceGroupPickerOptions, type PerformanceGroupRecoveryState, type PerformanceGroupSeatAllocation, type PerformanceGroupSelectionMode, type PerformanceGroupStatusEvent, type Projection, type PubApiOptions, type RealtimeSink, type ResumedHoldResult, SEATING_CHART_CALLBACK_PROPS, SEATING_CHART_HANDLE_METHODS, SEATING_CHART_IDENTITY_PROPS, SEATING_CHART_VALUE_PROPS, type SaleState, SeasonApiError, type SeasonAvailability, type SeasonCheckoutHandoff, type SeasonDescriptor, type SeasonOperation, type SeasonOperationState, SeasonPicker, type SeasonPickerOptions, SeasonRecoveryTimeoutError, type SeasonRenewalIntent, type SeasonStatusEvent, SeatPicker, type SeatPickerBestAvailableOptions, type SeatPickerBuyerView, type SeatPickerBuyerViewOptions, type SeatPickerOptions, type SeatPickerPricing, type SeatPickerTheme, SeatingChart, type SeatingChartCallbackProp, type SeatingChartCallbacks, type SeatingChartHandle, type SeatingChartHandleMethod, type SeatingChartIdentityProp, type SeatingChartOptions, type SeatingChartValueProp, type SeatingChartValues, type SelectedObjectUnavailableEvent, type SelectedSeat, type StatusChange, type SubscribeTicket, ThemeMode, type TicketOfferAvailability, type TicketOfferPrice, type TicketOfferSummary, attachPickerFrame, bindSeatingChartHandle, buildSeatingChartOptions, createBuyerAccessContext, createControllerSink, parseTicketOfferAvailability, shortOperationReference, ticketOfferPrices };
4041
+ export { ApiError, type AttachPickerFrameOptions, type BestAvailableResult, BuyerAccessContext, type BuyerAccessExpiredEvent, type BuyerAccessRefreshReason, type BuyerAccessToken, type BuyerAccessTokenProvider, BuyerAccessUnavailableError, type BuyerAccessUnavailableEvent, type BuyerAccessUnavailableReason, BuyerRealtimeClient, type BuyerRealtimeOptions, type CheckoutHandoff, type CheckoutLineItem, type CheckoutSessionResult, EmbeddedDesigner, type EmbeddedDesignerEventType, type EmbeddedDesignerMessage, type EmbeddedDesignerOptions, type GAAreaAvailability, type GAPromptRequest, type GaPromptTier, type HoldConflict, type HoldLineItem, type HoldResult, type OrderStatusResult, type PaymentOptionsReason, type PaymentOptionsResult, type PaymentProviderName, type PerformanceGroupAvailability, type PerformanceGroupCheckoutHandoff, type PerformanceGroupDescriptor, PerformanceGroupDestroyedError, type PerformanceGroupHold, type PerformanceGroupHoldAllocation, type PerformanceGroupOperationEvent, type PerformanceGroupOperationState, PerformanceGroupPicker, type PerformanceGroupPickerOptions, type PerformanceGroupRecoveryState, type PerformanceGroupSeatAllocation, type PerformanceGroupSelectionMode, type PerformanceGroupStatusEvent, type Projection, type PubApiOptions, type RealtimeSink, type ResumedHoldResult, SEATING_CHART_CALLBACK_PROPS, SEATING_CHART_HANDLE_METHODS, SEATING_CHART_IDENTITY_PROPS, SEATING_CHART_VALUE_PROPS, type SaleState, SeasonApiError, type SeasonAvailability, type SeasonCheckoutHandoff, type SeasonDescriptor, type SeasonOperation, type SeasonOperationState, SeasonPicker, type SeasonPickerOptions, SeasonRecoveryTimeoutError, type SeasonRenewalIntent, type SeasonStatusEvent, SeatPicker, type SeatPickerBestAvailableOptions, type SeatPickerBuyerView, type SeatPickerBuyerViewOptions, type SeatPickerOptions, type SeatPickerPricing, type SeatPickerTheme, type SeatPickerWebMcpOptions, SeatingChart, type SeatingChartCallbackProp, type SeatingChartCallbacks, type SeatingChartHandle, type SeatingChartHandleMethod, type SeatingChartIdentityProp, type SeatingChartOptions, type SeatingChartSeatViewInfo, type SeatingChartValueProp, type SeatingChartValues, type SelectedObjectUnavailableEvent, type SelectedSeat, type StatusChange, type SubscribeTicket, ThemeMode, type TicketOfferAvailability, type TicketOfferPrice, type TicketOfferSummary, attachPickerFrame, bindSeatingChartHandle, buildSeatingChartOptions, createBuyerAccessContext, createControllerSink, parseTicketOfferAvailability, shortOperationReference, ticketOfferPrices };