UNPKG

@seatmap.pro/renderer

Version:

Seatmap renderer library for booking and admin interfaces by Seatmap.pro

1,939 lines (1,930 loc) 97.9 kB
import KDBush from 'kdbush'; import { Machine, Service } from 'robot3'; /** * Base interface for seat properties. */ interface IBaseSeat { /** * The unique identifier of the seat. */ id: number; /** * The ID of the row this seat belongs to. */ rowId: number; /** * The ID of the sector this seat belongs to. */ sectorId: number; /** * The X coordinate of the seat within its section. */ x: number; /** * The Y coordinate of the seat within its section. */ y: number; /** * The name or number of the seat. */ name: string; /** * Whether the seat is accessible for people with disabilities. */ isAccessible?: boolean; /** * Whether the seat is marked with a special status. */ isMarked?: boolean; } /** * Base interface for sector properties. */ interface IBaseSector { /** * The unique identifier of the sector. */ id: number; /** * The globally unique identifier for the sector. */ guid?: string; /** * Whether this is a general admission (GA) sector. */ isGa: boolean; /** * The name of the sector. */ name: string; /** * The rotation angle of the sector in degrees. */ angle?: number | null; /** * The type of the sector. */ type?: string | null; /** * Whether the sector is disabled and cannot be interacted with. */ disabled?: boolean; /** * Whether the sector is filtered out by current filter criteria. */ filtered?: boolean; /** * Whether the sector is currently selected. */ selected?: boolean; /** * Shape metadata for the sector (GA section shapes with text, color, etc.) */ shapes?: ReadonlyArray<IShapeMetadata>; /** Label anchor X offset from section center (SEAT-624) */ labelOffsetX?: number; /** Label anchor Y offset from section center (SEAT-624) */ labelOffsetY?: number; /** Label style overrides (SEAT-624) */ labelStyle?: ILabelStyle; /** Whether the section name label should render (SEAT-895) */ labelVisible?: boolean; } /** * Label style overrides for GA section labels (SEAT-624). */ interface ILabelStyle { fontScale?: number; color?: string; opacity?: number; fontWeight?: 'normal' | 'bold'; background?: string; visible?: boolean; showPrice?: boolean; } /** * Shape metadata from the API response. */ interface IShapeMetadata { id?: string; text?: string; textColor?: string; textPosition?: string; fill?: string; width?: number; height?: number; top?: number; left?: number; angle?: number; order?: number; fontScale?: number; } /** * Interface representing the schema data transfer object from the API. * @hidden */ interface ISchemaDTO { plainSeats?: IPlainSeatsDTO; seats: ISeatDTO[]; rows: IRowDTO[]; sectors: ISectorDTO[]; background: ISVGBackgroundDTO; requestTime?: number; responseSize?: number; configuration?: IConfigurationDTO; instanceId?: string; componentVersion?: string; } /** * Interface representing the venue data transfer object from the API. * @hidden */ interface IVenueDTO { guid: string; name: string; gaCapacity?: number; seatsCapacity?: number; seatmap: ISchemaDTO; schema: ISchemaDTO; } /** * Interface representing the plain seats data transfer object from the API. * @hidden */ interface IPlainSeatsDTO { ids: number[]; x: number[]; y: number[]; rowIds: []; sectorIds: []; names: string[]; } /** * Interface representing the base seat data transfer object from the API. * Contains the core properties of a seat as received from the backend. * @hidden */ type ISeatDTO = IBaseSeat; /** * Interface representing a row in the venue. * @hidden */ interface IRowDTO { id: number; rowNumber: number; sectorId: number; name: string; seatName: string; } /** * Interface representing the base sector data transfer object from the API. * Contains the core properties of a sector as received from the backend. * @hidden */ type ISectorDTO = IBaseSector; /** * Interface representing the SVG background data transfer object from the API. * @hidden */ interface ISVGBackgroundDTO { svg?: Nullable<string>; viewBox: { x: number; y: number; width: number; height: number; }; svgLink?: string; images?: IPngBackgroundDTO; outlineSvg?: Nullable<string>; } /** * Interface representing the configuration data transfer object from the API. * @hidden */ interface IConfigurationDTO { accessible: number[]; marked: number[]; eventName?: string; } /** * Interface representing a special price option for seats, sections, or sectors. * Contains information about a named price point with its identifier. */ interface ISpecialPrice { /** * The name or label of the special price option. */ name: string; /** * The unique identifier for this price option. */ priceId: number; } /** * Interface representing special state information for seats, sections, or sectors. * Contains additional properties that affect rendering or behavior. */ interface ISpecialState { /** * Special state flag 1, used for custom state indicators. */ s1?: number; /** * Array of special prices associated with this item. */ prices?: ISpecialPrice[]; /** * Category identifier for grouping items with similar special states. */ category?: number; } /** * Interface representing a price list data transfer object from the API. * @hidden */ interface IPriceListDTO { seats: [number, IPriceId, ISpecialState?][]; groupOfSeats: [number, number, number, ISpecialState?][]; prices: IPriceDTO[]; requestTime?: number; responseSize?: number; } /** * Interface representing a price data transfer object from the API. * @hidden */ interface IPriceDTO { id: IPriceId; name: string; } /** * Type representing blurred image data. * @hidden */ type IBlurred = { data: string; shrink_factor: number; size: number; status: string; }; /** * Type representing a PNG image loaded from a URL. * @hidden */ type IPngFromUrl = { height: number; size: number; width: number; path: string; status: string; }; /** * Interface representing PNG background images in different resolutions. * @hidden */ interface IPngBackgroundDTO { blurred: IBlurred; preview: IPngFromUrl; full: IPngFromUrl; } /** * Settings for the BookingApiClient. * @hidden */ interface IBookingApiClientSettings { baseUrl: string; publicKey: string; debug?: boolean; forceSvg?: boolean; } /** * Metrics for API requests. * @hidden */ interface RequestMetrics { data?: string; requestTime: number; responseSize: number; } /** * Error thrown when the booking API returns a non-2xx response. * Preserves the HTTP status and the backend error code (e.g. EVENT_ARCHIVED, EVENT_NOT_PUBLISHED). */ declare class ApiError extends Error { readonly status: number; readonly errorCode: string | undefined; constructor(status: number, statusText: string, errorCode?: string); } /** * API client for fetching schema and price data from the booking API. * @hidden */ declare class BookingApiClient { private settings; constructor(settings: IBookingApiClientSettings); /** * Returns schema data * @param schemaId Schema ID */ fetchSchema(schemaId: number): Promise<ISchemaDTO>; /** * Returns schema data * @param venueId Venue GUID */ fetchSchemaForVenue(venueId: string): Promise<Omit<IVenueDTO, 'seatmap'>>; /** * Returns schema data * @param eventId Event GUID */ fetchSchemaForEvent(eventId: string): Promise<ISchemaDTO>; private unpackSchemaDTO; /** * Return prices information * @param eventId Event GUID */ fetchPricesForEvent(eventId: string): Promise<IPriceListDTO>; /** * Return rows SVG information * @param eventId Event GUID */ fetchRowsSvgForEvent(eventId: string): Promise<RequestMetrics>; /** * Makes request to booking API * @param url Relative API endpoint URL, e.g. 'event/prices/?id=XXX' */ request<T>(url: string): Promise<T>; /** * Makes request to booking API * @param url Relative API endpoint URL, e.g. 'event/prices/?id=XXX' */ requestPlain<T extends RequestMetrics>(url: string): Promise<T>; private restoreSeats; private restoreIds; } interface IPoint { readonly x: number; readonly y: number; } type ById$1<T> = { [id: number]: T; }; interface IPrice { id: IPriceId; name: string; } interface IColoredPrice extends IPrice { color: string; } /** * @hidden */ type ColorSequenceSettings = string[][]; /** * @hidden */ declare const sortPrices: <T extends IPrice>(prices: T[]) => T[]; /** * @hidden */ declare const convertPricesToColored: (prices: IPrice[], colorSettings: ColorSequenceSettings) => IColoredPrice[]; /** * @hidden */ declare const convertPricesToColoredById: (prices: IPrice[], colorSettings: ColorSequenceSettings) => ById$1<IColoredPrice>; interface IVisibilityStatus { selectable: boolean; visible: boolean; } interface IVisibilityStatuses { outline: IVisibilityStatus; seat: IVisibilityStatus; marker: IVisibilityStatus; } /** * @hidden */ interface IRendererMachineContext { mode?: string; scale: number; isEagleView: boolean; events: DestEvent[]; hovered?: { targetType: RendererTargetType; id: number; }; visibility?: IVisibilityStatuses; enable3DView?: boolean; rotationZ?: number; perspectiveZ?: number; tiltX?: number; getWidth?: () => number; getHeight?: () => number; /** Zoom strategy configuration for eagle eye view interactions */ interactionZoomStrategy?: InteractionZoomStrategy; } /** * @hidden */ type RendererMachineReducer<T> = (ctx: IRendererMachineContext, src: T) => IRendererMachineContext; /** * @hidden */ type RendererMachine = Machine<any, IRendererMachineContext, any>; /** * @hidden */ type RendererMachineService = Service<RendererMachine>; /** * @hidden */ declare enum RendererTargetType { SEAT = "seat", SECTION = "section" } /** * @hidden */ declare enum RendererSelectMode { REPLACE = "replace", ADD = "add", SUBTRACT = "subtract" } /** * Source events */ /** * @hidden */ declare enum SrcEventType { DRAG_START = "srcDragStart", DRAG_MOVE = "srcDragMove", DRAG_END = "srcDragEnd", CLICK = "srcClick", MOUSE_MOVE = "srcMouseMove" } /** * @hidden */ type SrcEvent = IDragStartSrcEvent | IDragMoveSrcEvent | IDragEndSrcEvent | IClickSrcEvent | IMouseMoveSrcEvent; /** * @hidden */ interface IDragSrcEvent<T> { type: T; origin: { x: number; y: number; }; delta: { x: number; y: number; }; shiftKey?: boolean; altKey?: boolean; metaKey?: boolean; } /** * @hidden */ type IDragStartSrcEvent = IDragSrcEvent<SrcEventType.DRAG_START>; /** * @hidden */ type IDragMoveSrcEvent = IDragSrcEvent<SrcEventType.DRAG_MOVE>; /** * @hidden */ type IDragEndSrcEvent = IDragSrcEvent<SrcEventType.DRAG_END>; /** * @hidden */ interface IClickSrcEvent { type: SrcEventType.CLICK; target: HTMLElement; point: { x: number; y: number; }; section?: ISection; seat?: ISeat; shiftKey?: boolean; altKey?: boolean; metaKey?: boolean; } /** * @hidden */ interface IMouseMoveSrcEvent { type: SrcEventType.MOUSE_MOVE; target: HTMLElement; section?: ISection; seat?: ISeat; } /** * Output events */ /** * @hidden */ declare enum DestEventType { PAN = "destPan", RECT_SELECT = "destRectSelect", DESELECT = "destDeselect", PAN_ZOOM = "destPanZoom", SEAT_SELECT = "destSeatSelect", SEAT_CART_SWITCH = "destSeatCartSwitch", SECTION_CLICK = "destSectionClick", SEAT_MOUSE_ENTER = "destSeatMouseEnter", SECTION_MOUSE_ENTER = "destSectionMouseEnter", SEAT_MOUSE_LEAVE = "destSeatMouseLeave", SECTION_MOUSE_LEAVE = "destSectionMouseLeave" } /** * @hidden */ type DestEvent = IPanDestEvent | IRectSelectDestEvent | IPanZoomDestEvent | IDeselectDestEvent | ISeatSelectDestEvent | ISeatCartSwitchDestEvent | ISectionClickDestEvent | ISeatMouseEnterDestEvent | ISectionMouseEnterDestEvent | ISeatMouseLeaveDestEvent | ISectionMouseLeaveDestEvent; /** * @hidden */ interface IPanDestEvent { type: DestEventType.PAN; isStart?: boolean; isFinish?: boolean; delta: { x: number; y: number; }; } /** * @hidden */ interface IRectSelectDestEvent { type: DestEventType.RECT_SELECT; selectMode: RendererSelectMode; isStart?: boolean; isFinish?: boolean; isRowsMode?: boolean; isSectionsMode?: boolean; rect: { x: number; y: number; width: number; height: number; }; } /** * Pan-zoom destination event. * Triggered when clicking in eagle eye view to zoom into the venue. * @hidden */ interface IPanZoomDestEvent { type: DestEventType.PAN_ZOOM; /** Target zoom scale */ scale: number; /** Origin point in viewport coordinates */ origin?: { x: number; y: number; }; /** Section at the clicked point, if any */ section?: ISection; /** Zoom strategy to apply */ strategy?: InteractionZoomStrategy; } /** * @hidden */ interface ISeatMouseEnterDestEvent { type: DestEventType.SEAT_MOUSE_ENTER; isRowsMode?: boolean; seat: ISeat; } /** * @hidden */ interface ISectionMouseEnterDestEvent { type: DestEventType.SECTION_MOUSE_ENTER; section: ISection; } /** * @hidden */ interface ISeatMouseLeaveDestEvent { type: DestEventType.SEAT_MOUSE_LEAVE; } /** * @hidden */ interface ISectionMouseLeaveDestEvent { type: DestEventType.SECTION_MOUSE_LEAVE; } /** * @hidden */ interface IDeselectDestEvent { type: DestEventType.DESELECT; } /** * @hidden */ interface ISeatSelectDestEvent { type: DestEventType.SEAT_SELECT; selectMode: RendererSelectMode; point: { x: number; y: number; }; seat: ISeat; isRowsMode?: boolean; } /** * @hidden */ interface ISeatCartSwitchDestEvent { type: DestEventType.SEAT_CART_SWITCH; point: { x: number; y: number; }; seat: ISeat; } /** * @hidden */ interface ISectionClickDestEvent { type: DestEventType.SECTION_CLICK; point: { x: number; y: number; }; section: ISection; isSectionsMode?: boolean; } type GpuTier = 'high' | 'constrained'; interface GpuProbeResult { renderer: string; maxTextureSize: number; deviceMemory: number | undefined; /** Texture upload throughput benchmark score (ms). Lower = faster GPU. -1 if benchmark failed. */ benchmarkMs: number; } interface DeviceCapabilities { tier: GpuTier; maxCanvasDimension: number; gpu: GpuProbeResult | null; } /** * Label positioning and style data for a section outline (GA, seated, or background-bound). * @hidden */ interface ISectionLabelInfo { id: number; name: string; transform: TransformArray; isTable?: boolean; textColor?: string; /** Relative offset from section center for label positioning (SEAT-624) */ labelOffset?: { x: number; y: number; }; /** Style overrides for the label (SEAT-624) */ labelStyle?: { fontScale?: number; color?: string; opacity?: number; fontWeight?: 'normal' | 'bold'; background?: string; visible?: boolean; showPrice?: boolean; }; /** When true, label is centered on shape centroid (no vertical shift above seats) */ centroid?: boolean; hideText?: boolean; hasPrice?: boolean; fontScale?: number; } /** * @hidden */ declare class Context { private redrawHandler; element: HTMLElement; settings: IRendererSettings; seatImages: { [id: string]: HTMLImageElement; }; cart: ICart; gaCategories: { [id: number]: number; }; categoriesColor: { [id: number]: string | undefined; }; scale: number; translate: IPoint; isEagleView: boolean; visibility: IVisibilityStatuses; width: number; height: number; physicalWidth: number; physicalHeight: number; capabilities: DeviceCapabilities; enable3DView: boolean; rotationZ: number; perspectiveZ: number; tiltX: number; private _seats; seatsIndex: KDBush; seatsById: ById<ISeat>; seatsByRowId: { [rowId: number]: ISeat[]; }; rowsById: ById<IRowDTO>; sectionsById: ById<ISector>; pricesById: ById<IColoredPrice>; seatsKeysMissedOnPriceSet: string[]; seatsIdsMissedOnPriceSet: number[]; rowsPolylines?: Array<{ points: { x: number; y: number; }[]; widthPx?: number; color?: string; }>; rowsPolylinesVersion: number; /** * @hidden */ private _pricesDTO; private _selectedSeatIds; private _selectedGaId?; underlay: { svgString?: Nullable<string>; viewBox: { x: number; y: number; width: number; height: number; }; svgUrl?: string; outlineSvg?: Nullable<string>; pngBackground?: IPngBackgroundDTO; }; hoveredSeat?: ISeat; hoveredRow?: IRowDTO; sectionLabelInfo: ISectionLabelInfo[]; _selectedAt: { [seatId: number]: number; }; _deselectedAt: { [seatId: number]: number; }; constructor(element: HTMLElement, settings: IRendererSettings, redrawHandler: () => void); set selectedSeatIds(value: number[]); get selectedSeatIds(): number[]; set selectedGaId(value: number | undefined); get selectedGaId(): number | undefined; destroy(): void; /** * @hidden */ setPricesDTO(value: IPriceListDTO): void; /** * @hidden */ getPricesDTO(): IPriceListDTO; setHovered(seat: ISeat | undefined, isRowsMode?: boolean): void; private createSeatImages; initCart(cart: ICart): void; get seats(): ISeat[]; set seats(value: ISeat[]); getPositionByOffset: (offset: IPoint) => IPoint; getOffsetByPosition: (position: IPoint) => IPoint; addGaToCart(ga: ICartGa): void; removeGaFromCart(removedGa: IRemovedCartGa): void; /** * Clears the cart and records deselection timestamps for all seats. */ clearCart(): void; /** * Removes seats from the cart by internal seat IDs and records deselection times. */ removeSeatsFromCartByIds(ids: number[]): void; /** * Triggers a selection pulse animation for a seat and clears any pending deselection. */ triggerSeatSelectionPulse(seatId: number): void; addSeatsToCart(seats: ICartSeat[]): void; rectSelectSeats(rect: { x: number; y: number; width: number; height: number; }, mode: RendererSelectMode): void; selectSeats(seats: number[], mode: RendererSelectMode): void; rectSelectRows(rect: { x: number; y: number; width: number; height: number; }, mode: RendererSelectMode): void; repairSeat: (s: ICartSeat) => ICartSeat | undefined; afterCartUpdate: () => void; repairGa: (ga: ICartGa) => ICartGa | undefined; findSeatByKey(key: string): ISeat | undefined; getCart(): ICart; isSeatInCart(seatId: number): boolean; isSectionSelected(sectorId: number): boolean; addSeatToCart(seatId: number): void; removeSeatFromCart(seatId: number): void; getCartSeats(): ISeat[]; getSeatSelection(): ISeat[]; setSectionSelection(sections?: number[] | string[]): (number | undefined)[] | null; getSvgSectionBySelection(): ISectorDTO[]; getGaSectors(): ISector[]; seatToCartSeat: (seat: ISeat, origCartSeat?: ICartSeat) => ICartSeat; seatToExtendedSeat: (seat: ISeat) => IExtendedSeat; calculateAbsolutePoint(point: IPoint): IPoint; getSeatByOffset: (offset: IPoint) => ISeat | undefined; /** * Hit-test a seat using a viewport-relative point (CSS px) while accounting for 3D view. * When 3D is enabled, the point is unprojected back to the stage before querying KDBush. */ getSeatByViewportPoint: (viewportPoint: IPoint, viewportSize: { width: number; height: number; }) => ISeat | undefined; } type Nullable<T> = T | null | undefined; type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]>; } : T; /** * Type for mapping objects by their ID. */ type ById<T> = { [id: number]: T; }; type ColorById<T> = { [id: number | string]: T; }; /** * Type representing a price identifier, which can be either a number or a string. * Used to uniquely identify price points in the system. */ type IPriceId = number | string; /** * Type representing a transformation matrix as a 6-element array. */ type TransformArray = [number, number, number, number, number, number]; /** * Interface representing a seat in the venue with extended properties. * Contains all the base seat properties plus additional properties for rendering and interaction. */ interface ISeat extends IBaseSeat { /** * The price ID associated with this seat. */ priceId?: IPriceId; /** * Whether the seat is locked and cannot be selected. */ locked?: boolean; /** * Whether the seat is filtered out by current filter criteria. */ filtered?: boolean; /** * Special state information for the seat. */ special?: ISpecialState; /** * The state of the seat. */ state?: SeatInteractionState; } /** * Interface representing a sector in the venue with extended properties. * Contains all the base sector properties plus additional properties for rendering and interaction. */ interface ISector extends IBaseSector { /** * The price ID associated with this sector. */ priceId?: IPriceId; /** * Special state information for the sector. */ special?: ISpecialState; } /** * Interface representing a section in the venue. * A section is a logical grouping of seats or a general admission area. */ interface ISection { /** * The unique ID of the section. */ id?: number; /** * The name of the section. */ name: string; /** * Whether this is a general admission section. */ ga: boolean; /** * The rectangular bounds of the section. */ rect: ISectionRect; /** * The price for the section. */ price?: number; /** * Special state information for the section. */ special?: ISpecialState; /** * The type of the section. */ type?: string | null; /** * The total number of seats in the section. */ seatCount?: number; /** * Text color for the section label (e.g., GA section name). */ textColor?: string; } /** * Visual and interaction states that can be applied to entities */ interface IEntityStates { highlighted: boolean; selected: boolean; unavailable: boolean; filtered: boolean; } /** * Metadata for a section including visual states, bounds, and rendering info */ interface ISectionMetadata { id: number; name: string; guid?: string; type?: string; elementCount: number; seatCount?: number; priceId?: number | string; isGa?: boolean; disabled?: boolean; filtered?: boolean; bounds?: DOMRect | null; center?: { x: number; y: number; } | null; centerOutsideViewBox?: boolean; outlineSource?: 'svg' | 'shape' | 'auto' | 'fallback'; states: IEntityStates; } /** * Metadata for a seat including visual states, position, and availability */ interface ISeatMetadata { id: number; key: string; sectionId: number; sectionName?: string; row?: string; seat?: string; x: number; y: number; priceId?: number | string; available: boolean; locked?: boolean; filtered?: boolean; inCart: boolean; states: IEntityStates; } /** * Interface representing a section with additional coordinate information. * Extends ISection with absolute x and y coordinates. */ interface ISectionWithCoords { /** * The X coordinate of the section. */ x: number; /** * The Y coordinate of the section. */ y: number; /** * The unique ID of the section. */ id?: number | undefined; /** * The name of the section. */ name: string; /** * Whether this is a general admission section. */ ga: boolean; /** * The rectangular bounds of the section. */ rect: ISectionRect; /** * The price for the section. */ price?: number | undefined; /** * Special state information for the section. */ special?: ISpecialState | undefined; /** * The type of the section. */ type?: string | undefined | null; } /** * Interface representing the rectangular bounds of a section. */ interface ISectionRect { /** * The left coordinate of the rectangle. */ left: number; /** * The top coordinate of the rectangle. */ top: number; /** * The width of the rectangle. */ width: number; /** * The height of the rectangle. */ height: number; } /** * Interface representing a seat in the shopping cart. */ interface ICartSeat { /** * The unique identifier of the seat. */ id?: number; /** * The unique key for the seat, typically combining section, row, and seat information. */ key: string; /** * The price of the seat. */ price?: number; /** * Special state information for the seat. */ special?: ISpecialState; } /** * Extended interface for a seat with additional coordinate and descriptive information. * Extends ICartSeat with position and section details. */ interface IExtendedSeat extends ICartSeat { /** * The X coordinate of the seat. */ x: number; /** * The Y coordinate of the seat. */ y: number; /** * The absolute X coordinate of the seat. */ ax: number; /** * The absolute Y coordinate of the seat. */ ay: number; /** * The ID of the section containing this seat. */ sectionId: number; /** * The ID of the sector containing this seat. */ sectorId: number; /** * The name or number of the seat. */ seatName: string; /** * The row number of the seat. */ rowNumber: number; /** * The name of the section containing this seat. */ sectionName: string; /** * Whether the seat is accessible for people with disabilities. */ isAccessible?: boolean; } /** * Interface representing a general admission (GA) item in the shopping cart. */ interface ICartGa { /** * The ID of the sector for this GA item. */ sectorId?: number; /** * The unique key for the GA item. */ key: string; /** * The number of GA tickets in this item. */ count: number; /** * The price per GA ticket. */ price?: number; } /** * Interface representing a removed general admission (GA) item from the cart. */ interface IRemovedCartGa { /** * The ID of the sector for the removed GA item. */ sectorId: number; /** * The price of the removed GA item. */ price?: number; } /** * Interface representing the shopping cart containing selected seats and GA items. */ interface ICart { /** * Array of selected seats in the cart. */ seats: ICartSeat[]; /** * Array of general admission items in the cart. */ ga: ICartGa[]; } /** * Type definition for a function that filters seats based on custom criteria. * @param seat - The seat to evaluate against the filter criteria * @returns A boolean indicating whether the seat passes the filter (true) or not (false) */ type SeatFilter = (seat: ISeat) => boolean; /** * Type definition for a seat price scheme, which associates a price with a specific seat. */ type ISeatPriceScheme = { /** * Optional unique identifier for the price scheme. */ id?: number; /** * The unique key of the seat this price applies to. */ seatKey: string; /** * The price value for the seat. */ price: number; /** * The price ID reference for the seat. */ priceId: IPriceId; }; interface IZoomSettings { presets: number[]; maxZoomToFitScale: number; minPinchZoom: number; maxPinchZoom: number; } interface IRange { from?: number; to?: number; } interface IVisibleSetting { visible?: IRange; selectable?: IRange; } /** * Visibility settings for different elements based on zoom level. */ interface IVisibilitySettings { /** * Visibility configuration for seats. */ seats?: IVisibleSetting; /** * Visibility configuration for section outlines. */ outlines?: IVisibleSetting; /** * Visibility configuration for custom markers. * Controls when markers are visible and interactive based on zoom level. */ markers?: IVisibleSetting; } /** * Minimap position options. */ type MinimapPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; /** * Minimap configuration settings. */ interface IMinimapSettings { /** * Whether the minimap is enabled. */ enabled: boolean; /** * Position of the minimap on the screen. * @default 'bottom-right' */ position?: MinimapPosition; /** * Width of the minimap in pixels. Height is calculated automatically based on aspect ratio. * @default 200 */ width?: number; /** * Maximum minimap height as a percentage (0..100) of the seatmap container height. * When set and the computed minimap height exceeds this limit, minimap width is reduced proportionally * to preserve the venue aspect ratio. */ maxHeightPercent?: number; /** * Opacity of the minimap. * @default 0.8 */ opacity?: number; /** * Background color of the minimap. */ backgroundColor?: string; /** * Border color of the minimap. */ borderColor?: string; /** * Color of the viewport indicator rectangle. * @default '#FF5722' */ viewportColor?: string; /** * Border width in pixels. * @default 2 */ borderWidth?: number; /** * Margin from the edge of the screen in pixels. * @default 16 */ margin?: number; /** * Color of the dim overlay outside the viewport on the minimap. * @default '#000000' */ offscreenColor?: string; /** * Opacity of the dim overlay outside the viewport on the minimap (0..1). * @default 0.4 */ offscreenOpacity?: number; /** * Show cart pins on the minimap. * @default true */ showCartPins?: boolean; /** * Show custom markers on the minimap. * @default true */ showMarkers?: boolean; } type MarkerTarget = { type: 'seat'; seatId: number; } | { type: 'section'; sectionId: number; } | { type: 'point'; x: number; y: number; }; type MarkerAppearance = { type: 'pin'; color?: string; } | { type: 'circle'; radius?: number; color?: string; label?: string; } | { type: 'custom'; svg: string; width?: number; height?: number; }; interface IMarker { id: string; target: MarkerTarget; appearance?: MarkerAppearance; showOnCanvas?: boolean; showOnMinimap?: boolean; interactive?: boolean; data?: Record<string, unknown>; } /** * Individual zoom strategy option for eagle eye view interactions. * * @public */ type InteractionZoomStrategyType = 'default' | 'section' | 'next-scale'; /** * Zoom strategy configuration for eagle eye view interactions. * Can be a single strategy or an array of strategies for fallback behavior. * * When an array is provided, strategies are tried in order until one succeeds. * If all strategies fail, falls back to default zoom behavior (scale 1). * * The `'section'` strategy only works in eagle eye view. When already zoomed to a section, * it will fall through to the next strategy, enabling progressive zoom behavior. * * @example * ```typescript * // Single strategy * interactionZoomStrategy: 'section' * * // Progressive zoom with fallback * interactionZoomStrategy: ['section', 'next-scale'] * // First click (eagle view): zooms to section * // Second click (at section): zooms to next scale step * ``` * * @public */ type InteractionZoomStrategy = InteractionZoomStrategyType | InteractionZoomStrategyType[]; /** * Default settings for markers. */ interface IMarkerSettings { /** * Default appearance for markers when not specified per-marker. * @default { type: 'pin' } */ defaultAppearance?: MarkerAppearance; /** * Whether markers are shown on the main canvas by default. * @default true */ defaultShowOnCanvas?: boolean; /** * Whether markers are shown on the minimap by default. * @default true */ defaultShowOnMinimap?: boolean; /** * Whether markers are interactive (clickable/hoverable) by default. * @default false */ defaultInteractive?: boolean; } interface IResolvedMarker { marker: IMarker; x: number; y: number; } /** * Loading phase identifiers for progress tracking. */ type LoadingPhase = 'schema' | 'prices' | 'rows' | 'background-blurred' | 'background-preview' | 'background-full' | 'processing'; /** * Progress event data passed to onLoadProgress callback. */ interface ILoadProgressEvent { phase: LoadingPhase; progress: number; isIndeterminate: boolean; message: string; } /** * Background image loaded event data. * @hidden */ interface IBackgroundImageLoadedEvent { type: 'blurred' | 'preview' | 'full' | 'detail-crop'; loadTimeMs: number; sizeBytes?: number; /** Native image width in pixels */ width?: number; /** Native image height in pixels */ height?: number; /** Estimated uncompressed GPU VRAM usage in bytes (width * height * 4 for RGBA) */ gpuBytes?: number; } /** * Loader display style. */ type LoaderStyle = 'overlay' | 'top-bar'; /** * Loader theme configuration. */ interface ILoaderTheme { overlayColor?: string; overlayOpacity?: number; progressBarColor?: string; progressBarBackgroundColor?: string; progressBarHeight?: number; textColor?: string; fontSize?: number; } /** * Loader configuration settings. */ /** * Customizable error message with title and optional subtitle. */ interface IErrorMessage { title: string; subtitle?: string; } interface ILoaderSettings { enabled: boolean; style?: LoaderStyle; theme?: ILoaderTheme; showText?: boolean; texts?: { schema?: string; prices?: string; rows?: string; backgroundBlurred?: string; backgroundPreview?: string; backgroundFull?: string; processing?: string; }; /** * Customizable error messages shown when event loading fails. * Each key corresponds to a backend error code. */ errorTexts?: { /** Shown when the event has been archived (HTTP 410). */ eventArchived?: IErrorMessage; /** Shown when the event is not published (HTTP 422). */ eventNotPublished?: IErrorMessage; /** Shown when the event does not exist (HTTP 404). */ eventNotFound?: IErrorMessage; /** Shown for any other API error. */ genericError?: IErrorMessage; }; } /** * Configuration settings for the renderer. * Defines various options that control the behavior and appearance of the renderer. */ interface IRendererSettings { /** * Debug mode for the renderer. * @hidden */ debug?: boolean; /** * Environment setting that determines which API endpoint to use. * Can be 'local', 'stage', or 'production' (default). */ env?: string; /** * Base URL prefix for background images. * Used to resolve relative image paths to absolute URLs. * Absolute URLs (starting with http://, https://, or data:) are used as-is. */ backgroundImageBaseUrl?: string; /** * External price information for seats. */ seatsPrices?: ISeatPriceScheme[]; /** * Theme settings for the renderer. */ theme?: IRendererTheme; markers?: IMarkerSettings; /** * Delay in milliseconds for debounced events. */ debounceDelay?: number; /** * Number of seats to select as a group. */ groupSize?: number; /** * Fixed height for the renderer in pixels. */ height?: number; /** * Fixed width for the renderer in pixels. * If not provided, the renderer will use the container element's width. */ width?: number; /** * Initial padding around the venue when first loaded. */ initialPadding?: number; /** * Padding around the venue during normal operation. */ padding?: number; /** * Minimum zoom level required to enable seat selection. * * @deprecated * Use visibilitySettings instead */ seatSelectionMinZoom?: number; /** * Maximum number of seats that can be selected. */ selectionLimit?: number; /** * Duration of transform animations in milliseconds. */ transformAnimationDuration?: number; /** * Duration of zoom animations in milliseconds. */ zoomAnimationDuration?: number; /** * Zoom mode for zoomToSection method. * 'fit' - Adaptively fit section to screen with padding * 'scale' - Zoom to a specific scale value * @default 'fit' */ zoomToSectionMode?: 'fit' | 'scale'; /** * Default scale value when zoomToSectionMode is 'scale' and no custom scale provided. * @default 1.0 */ zoomToSectionDefaultScale?: number; /** * Padding percentage around section when fitting to screen (0.0 to 1.0). * 0.1 means 10% padding on all sides. * @default 0.1 */ zoomToSectionFitPadding?: number; /** * Threshold dimensions for adaptive zoom behavior. * Small sections get capped zoom, large sections get minimum zoom. * @default { small: 200, large: 1000 } */ zoomToSectionAdaptiveThreshold?: { small: number; large: number; }; /** * If true, disables zoom when clicking on empty space. */ disableZoomToEmptySpace?: boolean; /** * If true, disables cart changed when clicking on sections. */ disableCartInteractions?: boolean; /** * */ disableOutlinesInHelicopterView?: boolean; /** * If true, hides all seats from view. */ hideSeats?: boolean; /** * If true, shows the outline layer during animations. */ showOutlineLayerOnAnimation?: boolean; /** * If true, shows row labels. */ showRows?: boolean; /** * If true, shows outlines for unavailable seats. */ showUnavailableOutlines?: boolean; /** * If true, uses WebGL for rendering instead of Canvas. */ switchToWebGL?: boolean; /** * Force the GPU capability tier instead of auto-detecting from device hardware. * Since SEAT-990 the two-layer texture system (detail crop on zoom) runs on every * WebGL device regardless of tier; the flag now only caps physical canvas dimensions * to 2048px on 'constrained'. * Also settable via URL parameter `?gpuTier=constrained`. * @hidden */ gpuTier?: 'high' | 'constrained'; /** * If true, the renderer skips preview and full PNG background loads and uses * the SVG background instead. The blurred PNG (inline base64) is still loaded * as an instant low-quality placeholder until SVG rasterization completes. * Useful when the PNG storage is not CORS-configured. * Also settable via URL parameter `?forceSvg=true` and propagated to the * booking-service `/event/` request as `forceSvg=true`. */ forceSvg?: boolean; /** * If true, shows the debug overlay layer (diagnostic lines from sections to venue center). */ showDebugLayer?: boolean; /** * If true, enables 3D view mode for enhanced visualization. */ enable3DView?: boolean; /** * If true, highlights all section outlines with a border. */ highlightAllOutlines?: boolean; /** * Minimap configuration settings. */ minimap?: IMinimapSettings; /** * Loader configuration settings. * Controls the loading overlay with progress bar during event loading. */ loader?: ILoaderSettings; /** * Option to configure zoom settings */ zoomSettings?: IZoomSettings; /** * Option to configure visibility settings */ visibilitySettings?: IVisibilitySettings; /** * Zoom strategy when clicking on the canvas in eagle eye view. * Can be a single strategy or an array of strategies for fallback behavior. * * Available strategies: * - `'default'`: Zoom to scale 1.0 (current behavior) * - `'section'`: Zoom to the clicked section using adaptive fit (only works in eagle eye view) * - `'next-scale'`: Zoom to the next scale step in zoom presets * * When multiple strategies are provided, they are tried in order. * The first strategy that can be applied is executed. * If all strategies fail, falls back to default zoom behavior. * * **Note:** The `'section'` strategy only works when in eagle eye view (zoomed out). * If already zoomed to a section, clicking again will fall through to the next strategy, * allowing progressive zoom (e.g., section → next-scale → deeper zoom). * * @example Single strategy * ```typescript * interactionZoomStrategy: 'section' * ``` * * @example Multiple strategies with progressive zoom * ```typescript * interactionZoomStrategy: ['section', 'next-scale'] * // First click: zooms to section (from eagle view) * // Second click: zooms to next scale (already at section, falls through) * ``` * * @example Explicit fallback to default * ```typescript * interactionZoomStrategy: ['section', 'default'] * // Tries to zoom to section first, if no section clicked or not in eagle view, zooms to scale 1 * ``` * * @default 'default' */ interactionZoomStrategy?: InteractionZoomStrategy; /** * Rises when the mouse pointer moves above a seat. * * @remarks * * Seat is passed as a param to the handler (see IExtendedSeat). */ onSeatMouseEnter?: (seat: IExtendedSeat) => void; /** * Same as `onSeatMouseEnter` but with debounce. * * @remarks * * Seat is passed as a param to the handler (see IExtendedSeat). */ onSeatDebouncedEnter?: (seat: IExtendedSeat) => void; /** * Fires when the mouse pointer leaves a seat. */ onSeatMouseLeave?: () => void; /** * Fires when the user marks a seat as selected. * * @remarks * * Seat is passed as a param to the handler (see IExtendedSeat). * * To cancel seat selection you can return `false` or Promise resolving to `false`. */ onSeatSelect?: (seat: IExtendedSeat) => void | boolean | Promise<void | boolean>; /** * Fires when the user deselects a seat. * * @remarks * * Seat is passed as a param to the handler (see IExtendedSeat). * * To cancel seat deselection you can return `false` or Promise resolving to `false`. */ onSeatDeselect?: (seat: IExtendedSeat) => void | boolean | Promise<void | boolean>; /** * Fires when the user marks a seat or seats as selected. * * @remarks * * Seats are passed as a param to the handler (see ISeat). * */ onSeatsSelect?: (seats: ISeat[]) => void; /** * Fires when the user deselects a seat or seats. * * @remarks * * Seats are passed as a param to the handler (see ISeat). * */ onSeatsDeselect?: (seats: ISeat[]) => void; /** * Fires when the cart was modified. * * @remarks * * Cart state is passed as a param to the handler (see ICart). */ onCartChange?: (cart: ICart, prevCart?: ICart) => void; onMarkerClick?: (marker: IMarker, event: MouseEvent) => void; onMarkerMouseEnter?: (marker: IMarker) => void; onMarkerMouseLeave?: (marker: IMarker) => void; /** * Fires when the mouse pointer moves above a section. * * @remarks * * Section is passed as a param to the handler (see ISection). */ onSectorMouseEnter?: (section: ISection) => void; /** * Fires when the mouse pointer leaves a section. */ onSectorMouseLeave?: () => void; /** * Fires when the user clicks on a section. * * @deprecated * Use onSectionClick instead */ onSectorClick?: (section: ISection) => void; /** * Fires when the user clicks on a section. * * @remarks * * Section is passed as a param to the handler (see ISection). */ onSectionClick?: (section: ISection) => void; /** * Fires after section selection was updated in selectSections mode. * * @remarks * * Called with the full array of currently selected sections after any * selection change (single click toggle or rect drag selection). */ onSectionsSelectionChange?: (sections: ISection[]) => void; /** * Fires before the zoom animation started. */ onZoomStart?: (newZoom: number, oldZoom: number) => void; /** * Fires after the zoom animation ended. */ onZoomEnd?: (newZoom: number, oldZoom?: number) => void; /** * Fires when component full redrawing starts. */ onRedrawStart?: () => void; /** * Fires when component full redrawing ends. */ onRedrawEnd?: () => void; /** * Fires when schema data has been successfully loaded and processed. * * @remarks * * This event is triggered after the schema data is fully loaded and all internal * processing is complete. It can be used to perform actions that depend on the * schema being fully initialized. */ onSchemaDataLoaded?: () => void; /** * Fires during loading to report progress. * * @remarks * * Progress event contains phase, progress percentage (0-100), * whether the phase is indeterminate, and a status message. */ onLoadProgress?: (event: ILoadProgressEvent) => void; /** * Fires when a background image has been loaded. * * @remarks * * Provides the image type (blurred, preview, full), load time in ms, and size in bytes. * @hidden */ onBackgroundImageLoaded?: (event: IBackgroundImageLoadedEvent) => void; /** * Fires when the WebGL context is lost (e.g., GPU memory pressure on mobile). * Host apps can use this to show fallback UI. * @hidden */ onWebGLContextLost?: () => void; /** * Fires when the WebGL context is restored after a loss event. * @hidden */ onWebGLContextRestored?: () => void; /** * Fires while panning. */ onPan?: (delta: IPoint, isFinish?: boolean) => void; /** * Fires after seat selection was updated. */ onSeatSelectionChange?: () => void; /** * Fires after seats selection was updated. */ onSeatsSelectionChange?: (seats: ISeat[]) => void; /** * You can control seats' styling by returning custom style for each seat */ onBeforeSeatDraw?: (event: IBeforeSeatDrawEvent) => ISeatStyle; lockedSeatsFilter?: (seat: ISeat) => boolean; /** * Suppress console warnings for deprecated API methods. * Useful for gradual migration in production environments. * @default false */ suppressDeprecationWarnings?: boolean; /** * Control visibility of outline types based on source. * Each source can be set to 'always', 'eagle-only', or 'hidden'. * * Source meanings: * - svg: bound to the background svg * - fallback: fallback outline generated from seats * - shape: editor-made basic shapes * - auto: editor-generated outline */ outlineVisibility?: { auto?: 'always' | 'eagle-only' | 'hidden'; fallback?: 'always' | 'eagle-only' | 'hidden'; svg?: 'always' | 'eagle-only' | 'hidden'; shape?: 'always' | 'eagle-only' | 'hidden'; }; } /** * Represents the possible interaction states of a seat. * Used to determine how a seat should be rendered based on user interaction. */ type SeatInteractionState = 'default' | 'hovered' | 'selected' | 'unavailable' | 'loading' | 'error'; /** * Interface for the event data passed to the onBeforeSeatDraw callba