@seatlayer/js 0.71.2 → 0.71.3

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