Booking Client (Renderer) API Changes - v1.70.0
Release: v1.70.0
Package: @seatmap.pro/renderer
Date: 2026-08-02
MetaTitle: Renderer API Changes 1.70.0 - Seatmap.pro
MetaDescription: New renderer APIs in 1.70.0: admin flat section view, seat block selection, configurable hotkeys, per-source outline styles and branding settings.
Summary
Backward Compatible: YES
New Features: 5
Deprecations: 0
Bug Fixes: 3
Architecture Improvements: Keyboard input in the admin renderer moved behind a hotkey controller with configurable bindings.
Migration Required: NO - every new API is additive and optional. One behavioural change is worth reviewing: a numeric price reported to your callbacks now preserves decimals instead of truncating them.
New Features
Flat section view (SEAT-1099)
Status: New
Description: Flattens a single section onto its seating grid in the admin renderer, matching what the schema editor shows. The rest of the map is dimmed behind a backdrop, and row and seat labels are drawn over the flattened grid.
API:
interface IAdminRenderer extends IRenderer {
setFlatSectionView: (sectionId: number | null) => Promise<boolean>;
}
interface IAdminRendererSettings {
onFlatSectionViewChange?: (sectionId: number | null) => void;
}
Example:
const renderer = new SeatmapAdminRenderer(document.getElementById('renderer-container'), {
publicKey: 'your-public-key',
onFlatSectionViewChange: (sectionId) => {
console.log(sectionId === null ? 'venue view' : `flat: ${sectionId}`);
},
});
await renderer.loadEvent('your-event-id');
const flattened = await renderer.setFlatSectionView(sectionId);
await renderer.setFlatSectionView(null);
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sectionId |
number | null |
Yes | - | Section to flatten, or null to exit the view |
Returns: Promise<boolean> - true when the view is flat for that section, or when null was passed; false when the section cannot be flattened.
Behavior:
- A section that is general admission, a table layout, an unknown id, or has no grid resolves
falseand leaves the view unchanged. - Requesting the section that is already flat resolves
trueand does nothing. The call never toggles the view off. - Exiting always resolves
true, whether or not anything was flat. onFlatSectionViewChangefires on every entry and exit, including the forced exit on renderer teardown or a schema reload.onFlatSectionViewChangemust be supplied at construction time. The renderer closes over the constructor settings object andContextkeeps a shallow copy, so assigning the callback afterwards has no effect.
Performance Impact: LOW - the flattened grid is built once per entry.
Related Files:
products/booking-client/src/admin-renderer/flatten/SectionFlattenController.ts- NEW: entry, exit and teardown handlingproducts/booking-client/src/admin-renderer/flatten/RowSeatLabelOverlay.ts- NEW: row and seat labels over the flattened grid
Seat block selection (SEAT-1048)
Status: New
Description: Selects the rectangular block of seats spanning the section-local grid coordinates between an anchor seat and a focus seat. It works on the section’s own row and seat grid rather than on screen geometry, so the block follows the seating layout even when the section is curved or rotated.
API:
selectSeatBlock(
anchorSeatId: number,
focusSeatId: number,
mode?: RendererSelectMode,
): Promise<boolean>
Example:
import { RendererSelectMode } from '@seatmap.pro/renderer';
await renderer.selectSeatBlock(anchorSeatId, focusSeatId);
await renderer.selectSeatBlock(anchorSeatId, focusSeatId, RendererSelectMode.ADD);
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
anchorSeatId |
number |
Yes | - | Seat where the block starts |
focusSeatId |
number |
Yes | - | Seat where the block ends |
mode |
RendererSelectMode |
No | RendererSelectMode.REPLACE |
Selection algebra applied to the block |
Returns: Promise<boolean> - false when either seat is unknown, the two seats belong to different sections, or grid coordinates are unavailable for that section; true otherwise.
Behavior:
REPLACEswaps the current selection for the block,ADDunions with it,SUBTRACTremoves the block from it.- Grid coordinates are fetched for the anchor’s section on demand, so the first call for a section may await a request.
Backend dependency: untransformed grid coordinates come from dedicated per-section endpoints (event/grid/ and event/schema/grid/), which the admin renderer calls for itself and caches per section. No integration change is required.
Configurable hotkeys (SEAT-1047)
Status: New
Description: Keyboard input in the admin renderer is routed through a hotkey controller with configurable bindings. Keystrokes are handled while the pointer is over the stage, and ignored when the event target is an input, textarea, select, or any editable element.
API:
export type HotkeysSetting = false | Partial<Record<string, string | null>>;
export type AdminHotkeyAction =
| 'pan'
| 'clearSelection'
| 'selectAll'
| 'zoomIn'
| 'zoomOut'
| 'zoomToFit'
| 'flatSection';
interface IRendererSettings {
hotkeys?: HotkeysSetting;
}
Default bindings:
| Action id | Combo | Type |
|---|---|---|
pan |
Space |
hold |
clearSelection |
Escape |
press |
selectAll |
Mod+A |
press |
zoomIn |
Mod+= |
press |
zoomOut |
Mod+- |
press |
zoomToFit |
Mod+0 |
press |
flatSection |
F |
hold |
Example:
const renderer = new SeatmapAdminRenderer(document.getElementById('renderer-container'), {
publicKey: 'your-public-key',
hotkeys: {
selectAll: 'Mod+E',
flatSection: null,
},
});
Behavior:
- A string value replaces that action’s default combo;
nulldisables the action; omitted actions keep their defaults. hotkeys: falsedisables keyboard handling entirely, for host applications that own the keyboard.- A combo is an optional
Mod+prefix plus a single key: a letter, a digit,=,-, or a key code such asSpaceorEscape.Modmaps to Cmd on macOS and Ctrl elsewhere. - A
holdbinding is active only while the key is held.Spacepans temporarily and restores the previous mode on release;Fflattens the hovered section for the duration of the hold. - Held keys are released on window blur, so a key does not stay stuck when focus leaves the page.
hotkeyslives on the sharedIRendererSettings, but only the admin renderer registers bindings. Setting it on a booking renderer has no effect.
Selection methods on the admin renderer (SEAT-1047)
Status: New
Description: The two selection actions behind the clearSelection and selectAll hotkeys are also public methods, so a host application can drive them from its own UI without binding a key.
API:
clearSelection(): void;
selectAllSeats(): void;
Behavior:
clearSelectionclears the seat selection, resets the selected outline state, and clears section selection, firingonSectionsSelectionChangewith an empty array when sections were selected.selectAllSeatsselects every seat that is not filtered out of the current view.
Per-source section outline styling (SEAT-985)
Status: New
Description: Section outline styles previously applied uniformly to every outline, so a hover style meant for user-drawn zones also changed the auto-generated outlines around seat sections. A bySource map now overrides the flat styles for one outline source only.
API:
export interface ISvgSectionStateStyles {
default?: Pick<ISvgSectionStyle, 'sectionName' | 'stroke' | 'cursor' | 'bgColor'>;
unavailable?: ISvgSectionStyle;
filtered?: ISvgSectionStyle;
hovered?: ISvgSectionStyle;
selected?: ISvgSectionStyle;
}
export interface IRendererSvgSectionStylesSetting extends ISvgSectionStateStyles {
bySource?: Partial<Record<'svg' | 'shape' | 'auto' | 'fallback', ISvgSectionStateStyles>>;
}
Example:
const renderer = new SeatmapBookingRenderer(container, {
publicKey: 'your-public-key',
theme: {
svgSectionStyles: {
hovered: {
stroke: { color: '#2196F3', width: '3px' },
},
bySource: {
fallback: {
hovered: { stroke: { color: 'transparent', width: '0' } },
},
auto: {
hovered: { stroke: { color: 'transparent', width: '0' } },
},
},
},
},
});
Outline sources:
| Source | Origin |
|---|---|
svg |
Zones drawn in the schema editor |
shape |
Shape objects |
auto |
Editor-generated outlines from seat groups |
fallback |
Runtime-generated from seat positions |
Behavior:
- The flat configuration is unchanged and existing themes keep working.
bySourceis applied on top, per source. bySourcegoverns the SVG outline styling. In WebGL overlay mode the section hover ring colour still comes from the flathovered.stroke.color.- The state keys inside a
bySourceentry are the same as the flat ones, so a source entry can override a single state and inherit the rest.
Related Files:
products/booking-client/src/models.ts- NEW:ISvgSectionStateStyles,bySourceonIRendererSvgSectionStylesSetting
Branding overlay settings (SEAT-1072)
Status: New
Description: The booking service resolves a branding level per render from the organization that owns the public key, and the renderer applies it as an overlay above the canvas in both the WebGL and Canvas2D paths. An active organization on a paid plan renders with no branding; an active organization without a paid plan gets a small clickable “Powered by Seatmap.pro” badge; a suspended organization gets a blocking overlay that disables seat selection. These settings control the presentation, not the level.
API:
export interface IWatermarkSettings {
position?: 'bottom-left' | 'bottom-right';
interruptionTitle?: string;
interruptionSubtitle?: string;
}
interface IRendererSettings {
watermark?: IWatermarkSettings;
}
Example:
const renderer = new SeatmapBookingRenderer(container, {
publicKey: 'your-public-key',
watermark: {
position: 'bottom-left',
interruptionTitle: 'Booking is temporarily unavailable',
interruptionSubtitle: 'Please contact support@example.com',
},
});
Behavior:
- Without
position, the badge sits bottom-right, and moves to bottom-left when the minimap occupies bottom-right so the two never overlap. An explicitpositionalways wins. interruptionTitleandinterruptionSubtitlereplace the default copy on the blocking overlay. Each falls back to the default when omitted; the overlay blocks selection regardless of the text.- Branding resolution fails open. Any error resolving the organization renders with no branding, so it cannot break a booking flow.
- Self-hosted deployments can force branding off with the
SEATMAP_WATERMARK_DISABLEDenvironment variable on the booking service, or thebooking.config.seatmap.watermark.disabledHelm value.
Modified Features
Numeric prices preserve decimals (SEAT-1056)
Status: Warning - Modified
What Changed: The renderer derives the numeric price it reports from a price’s display label. That derivation now preserves decimals.
Before (v1.69.x):
// price label "12.50"
onSeatsSelect: (seats) => {
console.log(seats[0].price); // 12
};
After (v1.70.0):
// price label "12.50"
onSeatsSelect: (seats) => {
console.log(seats[0].price); // 12.5
};
Affected values: price on section-click and section-selection callbacks, and price on cart seats and general-admission entries (onSeatsSelect, getCart).
Backward Compatibility: The type is unchanged - a reported price is still either a finite number or undefined. A non-numeric label such as Gold still reports undefined, and a 0 label still reports 0. Integrations that assumed integer-only prices, or that re-parsed the label themselves, should review these values.
Related correctness fixes in the same area:
- Cart price-id backfill matches a cart price to its price entry numerically, so decimal and suffixed labels (for example
12.50,42 EUR) are matched correctly. - A general-admission entry whose stored price is not a finite number can once again be removed from the cart.
- Restoring a seat from a saved cart key resolves a section by name, so a section whose name happens to be numeric is no longer confused with a different section that has that numeric id.
- Loading a single section trims the price list to that section, so seat and group price assignments from other sections are no longer carried into the isolated view.
Animation completion reports whether it finished (SEAT-1055)
Status: Warning - Modified
What Changed: IRendererAnimation.onComplete now receives a flag telling you whether the animation ran to its natural end.
Before (v1.69.x):
onComplete?: () => void;
After (v1.70.0):
onComplete?: (completed: boolean) => void;
Backward Compatibility: YES. A zero-argument callback remains assignable to the new signature in TypeScript and ignores the extra argument at runtime, so existing code compiles and behaves as before.
Behavior: every transform animation now signals completion on every exit path - natural finish, cancel, or preemption - with completed set to false when it was interrupted. An awaited zoom sequence interrupted by a gesture stops cleanly instead of leaving a pending promise or skipping its snapback.
Bug Fixes
Label styling was silently ignored (SEAT-1069)
Issue: A schema’s labelStyle arrives on the wire as a JSON string and was never parsed, so every setting it carries - hiding the general-admission label, hiding the price dot, section label styling - had no effect.
Impact: Every integration relying on labelStyle.
Root Cause: the section DTO aliased the internal model, so the string-typed wire field was read as if it were already an object.
Fix: labelStyle is parsed at schema ingest.
Behavior Change:
- Before:
labelStylesettings were dropped. - After:
labelStylesettings apply.
User Action Required: NO - but a schema that carried a labelStyle will now render as that style specifies, which may differ from what you saw before.
Zoom to a section jerked or froze (SEAT-1077)
Issue: During a zoom, two independent draw drivers painted the WebGL stage at different transforms, which showed as a two-step jerk, and a detail-crop upload running mid-animation blocked the main thread.
Fix: the self-driven redraw loop is gated while a transform animation runs, so the animator is the sole painter, and a pending viewport settle is cancelled when an animation starts. Concurrent zoom-in and zoom-out requests during an in-progress zoom are queued rather than interleaved.
User Action Required: NO.
Zoom and section transitions settled incorrectly (SEAT-1055)
Issue: Programmatic zoom-to-destination committed the raw target rather than the pan-limited position, so a zoom into a point near the venue edge rested past the bounds. Section-view transitions used a fixed timer instead of waiting for the zoom-to-fit animation, so the rotate-in started early or late on slower devices.
Fix:
- Zoom-to-destination, including minimap clicks and 2D zoom-to-section, commits the pan-limited position. A pinch-to-zoom that ends out of bounds snaps back to the limit.
- Entering and exiting a section view waits for the zoom-to-fit animation to finish.
- A section whose geometry produces an undefined rotation angle logs a warning and resets cleanly instead of animating a
NaNtransform. Overlapping section rotations no longer corrupt each other’s state. - A zero-duration zoom commits in a single step with no blank or
NaNframe, and the pan-limit snapback runs even while a zoom animation is in flight.
User Action Required: NO.
Type Definitions
New exported types
export type { HotkeysSetting } from './HotkeyController';
export type { AdminHotkeyAction } from './admin-renderer/hotkeys';
export type { IAdminRenderer } from './admin-renderer';
export interface IWatermarkSettings { ... }
export interface ISvgSectionStateStyles { ... }
IAdminRenderer was previously internal and is now exported alongside IAdminRendererSettings, so a host application can type a reference to the admin renderer by its interface.
Modified types
export interface IRendererSettings {
hotkeys?: HotkeysSetting; // Added
watermark?: IWatermarkSettings; // Added
}
export interface IRendererSvgSectionStylesSetting extends ISvgSectionStateStyles {
bySource?: Partial<Record<OutlineSource, ISvgSectionStateStyles>>; // Added
}
export interface IRendererAnimation {
onComplete?: (completed: boolean) => void; // Changed from () => void
}
The outline source union is 'svg' | 'shape' | 'auto' | 'fallback'. It is reachable structurally through bySource, so a TypeScript object literal with those keys type-checks without importing the alias.
Performance Changes
No renderer method signatures, configuration options, or callback values changed for performance work. Two areas improved:
Large venues (SEAT-1078): data operations that re-scanned every seat or section now use internal indexes. Seat filtering, locking and state changes no longer rebuild the spatial hit-test index; minimap cart pins are drawn from the cart rather than by scanning all seats; key-based seat lookups resolve in constant time per key. Per frame, row geometry uploads only when it changes, the static background quad and per-seat selection and hover data are no longer re-sent each frame, and the “is any seat still loading” check runs in constant time. GPU shaders, textures and buffers are released more thoroughly on teardown, reducing memory across repeated map loads.
Backgrounds (SEAT-1081): the converter emits a chain of progressively smaller levels alongside the tile grid and encodes tiles as WebP. The renderer picks the smallest level that serves the requested region, fetches its tiles in parallel and caches them, and re-checks each level’s decoded width so a level that under-delivers steps up. On a 9500px stadium background the worst case drops from about 2.5 MB to about 400 KB per crop.
Migration Guide
Quick Migration
From v1.69.x to v1.70.0
-
Update Package
npm install @seatmap.pro/renderer@1.70.0 # or yarn add @seatmap.pro/renderer@1.70.0 -
Review numeric prices
If your integration reads
pricefrom a callback or from the cart and assumed whole numbers, confirm it handles decimals. Nothing else requires a code change.
Compatibility
- All existing code continues to work
- No TypeScript errors
- No configuration changes required
Examples
Admin tooling with the new APIs
import { SeatmapAdminRenderer, RendererSelectMode } from '@seatmap.pro/renderer';
const renderer = new SeatmapAdminRenderer(document.getElementById('seatmap'), {
publicKey: 'your-public-key',
hotkeys: {
selectAll: 'Mod+E',
},
onFlatSectionViewChange: (sectionId) => {
setBreadcrumb(sectionId === null ? 'Venue' : sectionLabel(sectionId));
},
onSectionsSelectionChange: (sections) => {
setSelectedSections(sections);
},
});
await renderer.loadEvent('event-123');
renderer.setMode('selectSections');
async function inspectSection(sectionId) {
const flattened = await renderer.setFlatSectionView(sectionId);
if (!flattened) {
showToast('That section cannot be flattened');
}
}
async function selectRange(anchorSeatId, focusSeatId, additive) {
await renderer.selectSeatBlock(
anchorSeatId,
focusSeatId,
additive ? RendererSelectMode.ADD : RendererSelectMode.REPLACE,
);
}
async function backToVenue() {
await renderer.setFlatSectionView(null);
}
Related Changes
- See Release Notes for the product-focused overview
Support
Questions or Issues?
- Documentation: https://seatmap.pro/knowledge-base/renderer/
- Support: support@seatmap.pro
Reporting Bugs
Include:
- Renderer version: 1.70.0
- Browser and version
- Minimal reproduction code
- Console errors
- Steps to reproduce