SeatSquirrel
DeploymentSelf-Hosted Mode

Picker API Reference

Complete API reference for the SeatSquirrel Picker

Constructor Options

new SeatSquirrel.StatelessPicker(options)

Shared Callbacks (Both Modes)

These options work in both standalone and iframe SDK modes.

OptionTypeRequiredDescription
onReady() => voidNoCalled when picker is ready.
onLayoutLoaded() => voidNoCalled when loadLayout() succeeds. Pure confirmation signal — no payload.
onSelectionChanged(data: SelectionData) => voidNoCalled when selections change. data.lastSelection is the item just selected/modified, or null on deselect/clear.
onComplete(data: SelectionData) => voidNoImportant: Called when user clicks to finalise their selection.
This is the most important callback — where you handle the user's final selections and send them to your backend for processing.
onError(error: { message: string, code: string, details?: string }) => voidNoCalled when an error occurs.
requirePricingCategorybooleanNoWhen true, entities without a valid pricing category are treated as unavailable. Default: true. Can also be set via loadLayout options, setPricingCategories, or setAvailability by-pricing-category mode.

iFrame SDK Only

These options only apply when using the iframe SDK. They are not available in standalone mode.

OptionTypeRequiredDescription
containerstring | HTMLElementYesCSS selector or DOM element for the iframe
baseUrlstringNoLocation of the SeatSquirrel app (default: https://seatsquirrel.com)
pickerUrlstringNoFull URL override for the picker page. Takes precedence over baseUrl. Useful for servers requiring .html extensions (e.g., https://example.com/picker.html)

Methods

Summary

MethodParametersReturns
loadLayout(layoutData, options?)layoutData: LayoutOutput, options?Standalone: { success: boolean, availabilityErrors?: string[] }
iFrame: Promise<{ success: boolean, availabilityErrors?: string[] }>
isPickerReady(){ pickerReady: boolean }
setAvailability({ mode, ... }){ mode, ... }{ success: boolean }
setPricingCategories({ mode, ... }){ mode, pricingCategories }Standalone: { success: boolean }
iFrame: Promise<{ success: boolean }>
getPricingCategories()Standalone: { pricingCategories: PricingCategory[] }
iFrame: Promise<{ pricingCategories: PricingCategory[] }>
assignPricingCategories(input)AssignPricingCategoriesInputStandalone: { success, errors? }
iFrame: Promise<{ success, errors? }>
getSelections()SelectionData
getTotals(){ count: number, amount: number }
getLayout({ mode }){ mode: 'current' | 'last-saved' }Standalone: { layout: LayoutOutput | null }
iFrame: Promise<{ layout: LayoutOutput | null }>
setSelections(input, options?)SelectionData or SetSelectionsInput, { mode? }Standalone: SetSelectionsResult
iFrame: Promise<SetSelectionsResult>
clearSelections(){ success: boolean }
setAllowProceed(allow)booleanStandalone: { success: boolean }
iFrame: void
showToast(options)ShowToastOptionsStandalone: { success: boolean }
iFrame: Promise<{ actionClicked: boolean }>
showAlertDialog(options)ShowAlertDialogOptions{ success: boolean }
showNotice(options)ShowNoticeOptions{ success: boolean, noticeId: string }
removeNotice(noticeId)string{ success: boolean }
destroy()void (iFrame SDK only)

loadLayout(layoutData, options?)

Description

Load a layout into the picker for seat selection. This must be called before users can select seats.

Parameters

  • layoutData (LayoutOutput): The layout JSON from the designer or your database
  • options (Object, optional):
PropertyTypeRequiredDescription
requirePricingCategorybooleanNoWhen true, entities without a valid pricing category are treated as unavailable. Default: true. Pass false to allow uncategorized entities to remain selectable.
initialAvailabilitySetAvailabilityInputNoApply availability atomically with the layout load to prevent a flash of default (all-available) state. Accepts the same input as setAvailability() — any of the 5 modes. Applied after the layout is set but before onLayoutLoaded fires.

Returns

{ success: boolean, availabilityErrors?: string[] }success refers to the layout load itself. availabilityErrors is only present if initialAvailability was provided and had issues (the layout still loads and onLayoutLoaded still fires).

In standalone mode this is synchronous. In iframe SDK mode it returns a Promise.

Example

// Standalone mode (synchronous)
// By default, requirePricingCategory is true — entities without a valid pricing category are unavailable
const { success } = SeatSquirrel.picker.loadLayout(layoutData);

// Opt out of pricing enforcement — uncategorized entities remain selectable
const { success } = SeatSquirrel.picker.loadLayout(layoutData, {
  requirePricingCategory: false,
});

// Load with initial availability (prevents flash of default state)
const { success, availabilityErrors } = SeatSquirrel.picker.loadLayout(layoutData, {
  initialAvailability: {
    mode: 'by-id',
    rowSeats: { 'seat-1': true, 'seat-2': false },
    areas: { 'area-1': 50 },
  },
});
if (availabilityErrors) {
  console.warn('Availability issues:', availabilityErrors);
}

// Load with initial availability using pricing categories
const result = SeatSquirrel.picker.loadLayout(layoutData, {
  initialAvailability: {
    mode: 'by-pricing-category',
    available: ['category-vip', 'category-standard'],
  },
});
// Iframe SDK mode (async)
const { success } = await picker.loadLayout(layoutData);

// Opt out of pricing enforcement
const { success } = await picker.loadLayout(layoutData, { requirePricingCategory: false });

// Load with initial availability (prevents flash)
const { success, availabilityErrors } = await picker.loadLayout(layoutData, {
  initialAvailability: {
    mode: 'by-slug',
    rowSeats: { 'row-a/seat-1': true, 'row-a/seat-2': false },
  },
});

setAvailability({ mode, ... })

Description

Set which seats and areas are available. The mode parameter controls how items are identified:

  • 'by-id' — Use machine-generated IDs to identify seats, areas, table seats, and tables.
  • 'by-slug' — Use user-defined slugs. This is the recommended mode as slugs are stable, human-readable identifiers that you control.
  • 'dangerously-by-labels' — Use display labels. Use with caution — labels can clash if duplicated across sections. Prefer 'by-slug' for reliable lookups.
  • 'by-pricing-category' — Bulk-set availability based on pricing category IDs. Entities are resolved to available/unavailable based on their assigned pricing categories (including row→seat and table→tableSeat inheritance).
  • 'by-pricing-category-slug' — Same as 'by-pricing-category', but using pricing category slugs instead of IDs.

Parameters

  • options (Object):
PropertyTypeRequiredDescription
mode'by-id' | 'by-slug' | 'dangerously-by-labels' | 'by-pricing-category' | 'by-pricing-category-slug'YesHow to identify items

Additional properties for mode 'by-id':

PropertyTypeRequiredDescription
rowSeatsRecord<string, boolean>NoMap of seat ID to boolean (true = available)
areasRecord<string, number | boolean>NoMap of area ID to available count (number), or boolean (true = 1, false = 0)
tableSeatsRecord<string, boolean>NoMap of table seat ID to boolean (true = available)
tablesRecord<string, boolean>NoMap of table ID to boolean (true = available)

Additional properties for mode 'by-slug': (AvailabilityBySlug)

Object with rowSeats, areas, tableSeats, and tables maps keyed by slug.

Additional properties for mode 'dangerously-by-labels': (AvailabilityByLabels)

Object with rowSeats, areas, tableSeats, and tables arrays.

Additional properties for modes 'by-pricing-category' / 'by-pricing-category-slug':

PropertyTypeRequiredDescription
availablestring[]NoCategory IDs (or slugs) whose entities should be available. Unlisted categories → entities unavailable. This is a full reset.
unavailablestring[]NoCategory IDs (or slugs) whose entities should be unavailable. Only flips an entity to unavailable when all of its effective category IDs are in this list. Unlisted → unchanged. This is a delta.
requirePricingCategorybooleanNoWhen provided, updates the requirePricingCategory picker option. When true, uncategorized entities are treated as unavailable.

When both available and unavailable are provided, available is processed first (full reset), then unavailable overrides: entities whose entire set of effective category IDs are in the unavailable list are flipped to unavailable.

Uncategorized entities (empty pricingCategoryIds) are always left unchanged by these modes, unless requirePricingCategory is also set to true (which makes them unavailable at the rendering level).

Returns

{ success: boolean, errors?: string[] }

Example

// By ID
picker.setAvailability({
  mode: 'by-id',
  rowSeats: {
    'row-1234567890_abc123-seat-0_1234567890': true,
    'row-1234567890_abc123-seat-1_1234567890': false,
  },
  areas: {
    'area-1234567890_abc123': 50, // number for count
    'area-9876543210_def456': false, // boolean for single-customer areas (false=0, true=1)
  },
  tableSeats: {
    'table-1234567890_abc123-seat-0_1234567890': true,
  },
  tables: {
    'table-1234567890_abc123': false,
    'table-1234567891_def456': true,
  },
});

// By slug (recommended)
picker.setAvailability({
  mode: 'by-slug',
  rowSeats: {
    'row-a-seat-1': false,
    'row-a-seat-2': true,
  },
  areas: {
    'standing-room': 50,
    'vip-lounge': 0, // number: 0 = sold out
    'private-booth': false, // boolean: false = unavailable (same as 0)
  },
  tableSeats: {
    'table-1-seat-1': false,
  },
  tables: {
    'vip-table-1': false,
    'table-5': true,
  },
});

// By pricing category — only "standard" category entities are available (full reset)
picker.setAvailability({
  mode: 'by-pricing-category',
  available: ['price_abc123'], // Standard category ID
});

// By pricing category — make "vip" entities unavailable (delta, others unchanged)
picker.setAvailability({
  mode: 'by-pricing-category',
  unavailable: ['price_def456'], // VIP category ID
});

// By pricing category — combined: available sets baseline, unavailable overrides
picker.setAvailability({
  mode: 'by-pricing-category',
  available: ['price_abc123', 'price_def456'], // Standard + VIP available
  unavailable: ['price_abc123'], // Then override: Standard-only entities → unavailable
});

// By pricing category slug — same as above but using slugs
picker.setAvailability({
  mode: 'by-pricing-category-slug',
  available: ['standard', 'vip'],
});

// With requirePricingCategory — uncategorized entities also become unavailable
picker.setAvailability({
  mode: 'by-pricing-category',
  available: ['price_abc123'],
  requirePricingCategory: true,
});

// Dangerously by labels (use with caution)
picker.setAvailability({
  mode: 'dangerously-by-labels',
  rowSeats: [
    { rowLabel: 'A', seatLabel: '1', isAvailable: false },
    { rowLabel: 'A', seatLabel: '2', isAvailable: true },
  ],
  areas: [
    { areaLabel: 'Standing Room', availableCount: 50 },
    { areaLabel: 'Private Booth', availableCount: false }, // boolean: false = 0
  ],
  tableSeats: [
    { tableLabel: 'Table 1', seatLabel: '1', isAvailable: false },
    { tableLabel: 'Table 1', seatLabel: '2', isAvailable: true },
  ],
  tables: [
    { tableLabel: 'VIP Table', isAvailable: false },
    { tableLabel: 'Table 5', isAvailable: true },
  ],
});

setPricingCategories({ mode, ... })

Description

Set or update pricing categories in the picker. The mode parameter controls how the categories are applied:

  • 'replace' — Replace all pricing categories with new data. This mode replaces all category properties (id, label, slug, price, color, customerNotes). Existing seat-to-category assignments are preserved. Entities assigned to omitted categories automatically render as unavailable and are blocked from selection (the "rendering safety net" — no additional setAvailability call needed). This relies on the requirePricingCategory setting (default true from loadLayout).
  • 'update-by-id' — Partially update existing categories by ID. Merges provided fields into matching categories while leaving unmentioned categories untouched. All-or-nothing: if any ID doesn't match an existing category, the entire call fails with no changes applied. This is also the way to do price-only updates — just pass { id, price }.
  • 'update-by-slug' — Partially update existing categories by slug. Merges provided fields into matching categories while leaving unmentioned categories untouched. All-or-nothing: if any slug doesn't match an existing category, the entire call fails with no changes applied. This is also the way to do price-only updates — just pass { slug, price }.

Note: When using 'update-by-slug' mode, the slug field is used only for matching — it is not merged into the category. To change a category's slug, use 'update-by-id' mode and pass slug as an update field.

Parameters

  • options (Object):
PropertyTypeRequiredDescription
mode'replace' | 'update-by-id' | 'update-by-slug'YesHow to apply the pricing categories
pricingCategoriesArrayYesArray of pricing category objects (shape depends on mode)
requirePricingCategorybooleanNoOverride whether entities must have a valid pricing category to be selectable. If omitted, inherits the current setting (default true from loadLayout).

Category Object Properties (mode: 'replace'):

PropertyTypeRequiredDescription
idstringYesUnique identifier
labelstringYesDisplay label
slugstringYesURL-friendly identifier
pricenumberYesPrice in cents
colorstringYesHex color code
customerNotesstringNoOptional notes visible to customers

Update Object Properties (mode: 'update-by-id'):

PropertyTypeRequiredDescription
idstringYesID of the category to update (must match existing)
labelstringNoNew display label
slugstringNoNew slug
pricenumberNoNew price in cents
colorstringNoNew hex color
customerNotesstringNoNew customer notes

Update Object Properties (mode: 'update-by-slug'):

PropertyTypeRequiredDescription
slugstringYesSlug of the category to match (used for lookup only, not merged)
labelstringNoNew display label
pricenumberNoNew price in cents
colorstringNoNew hex color
customerNotesstringNoNew customer notes

Returns

{ success: boolean } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise<{ success: boolean }>.

Example

// Replace all categories (standalone, synchronous)
const { success } = SeatSquirrel.picker.setPricingCategories({
  mode: 'replace',
  pricingCategories: [
    { id: 'cat-1', label: 'Standard', slug: 'standard', price: 5000, color: '#3B82F6' },
    { id: 'cat-2', label: 'VIP', slug: 'vip', price: 10000, color: '#EF4444', customerNotes: 'Includes drinks' },
  ],
});

// Replace all categories (iframe SDK, async)
const { success } = await picker.setPricingCategories({
  mode: 'replace',
  pricingCategories: [
    { id: 'cat-1', label: 'Standard', slug: 'standard', price: 5000, color: '#3B82F6' },
    { id: 'cat-2', label: 'VIP', slug: 'vip', price: 10000, color: '#EF4444', customerNotes: 'Includes drinks' },
  ],
});

// Partially update by ID
const { success } = SeatSquirrel.picker.setPricingCategories({
  mode: 'update-by-id',
  pricingCategories: [
    { id: 'price_abc123', price: 7500, label: 'Standard (updated)' },
    { id: 'price_def456', color: '#10B981' },
  ],
});

// Partially update by slug
const { success } = await picker.setPricingCategories({
  mode: 'update-by-slug',
  pricingCategories: [
    { slug: 'standard', price: 7500, label: 'Standard (updated)' },
    { slug: 'vip', color: '#10B981' },
  ],
});

// Price-only update (pass only the price field — other properties are preserved)
SeatSquirrel.picker.setPricingCategories({
  mode: 'update-by-slug',
  pricingCategories: [
    { slug: 'standard', price: 4000 },
    { slug: 'vip', price: 8000 },
  ],
});

// Tier filtering: only offer VIP and Standard for this performance.
// Entities assigned to other categories (e.g., Budget) become unavailable
// because requirePricingCategory defaults to true via loadLayout.
SeatSquirrel.picker.setPricingCategories({
  mode: 'replace',
  pricingCategories: [
    { id: 'cat-1', label: 'Standard', slug: 'standard', price: 5000, color: '#3B82F6' },
    { id: 'cat-2', label: 'VIP', slug: 'vip', price: 10000, color: '#EF4444' },
  ],
});

getPricingCategories()

Description

Get the current pricing categories from the loaded layout. Returns the full category objects including id, label, slug, price, color, and customerNotes.

Returns

{ pricingCategories: PricingCategory[] } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise<{ pricingCategories: PricingCategory[] }>.

Example

// Standalone mode (synchronous)
const { pricingCategories } = SeatSquirrel.picker.getPricingCategories();

// Iframe SDK mode (async)
const { pricingCategories } = await picker.getPricingCategories();

pricingCategories.forEach(cat => {
  console.log(cat.label, cat.price, cat.color);
});

assignPricingCategories(input)

Description

Assign pricing categories to layout objects (rows, seats, areas, tables, table seats) programmatically. This is useful when pricing categories are managed externally and need to be mapped to layout objects after loading.

Atomic semantics: If any assignment fails validation, none are applied. All errors are returned in the errors array.

The mode parameter controls how objects and categories are identified:

  • 'by-id' — Reference objects and categories by their internal UUIDs.
  • 'by-slug' — Reference objects and categories by their user-defined slugs. This is the recommended mode.
  • 'dangerously-by-labels' — Reference objects by human-readable labels (can be ambiguous across sections). Categories are always referenced by slug in this mode.

Parameters

PropertyTypeRequiredDescription
mode'by-id' | 'by-slug' | 'dangerously-by-labels'YesHow to identify objects and categories

Additional properties for mode 'by-id':

PropertyTypeRequiredDescription
rowsRecord<string, string[]>NoMap of row ID to category IDs
rowSeatsRecord<string, string[]>NoMap of seat ID to category IDs
areasRecord<string, string[]>NoMap of area ID to category IDs
tablesRecord<string, string[]>NoMap of table ID to category IDs
tableSeatsRecord<string, string[]>NoMap of table seat ID to category IDs

Additional properties for mode 'by-slug':

Same shape as 'by-id', but keys are slugs (e.g., { 'row-a': ['vip', 'standard'] }).

Additional properties for mode 'dangerously-by-labels':

PropertyTypeRequiredDescription
rowsArray<{ sectionId?, rowLabel, pricingCategorySlugs }>NoRow assignments by label
rowSeatsArray<{ sectionId?, rowLabel, seatLabel, pricingCategorySlugs }>NoSeat assignments by label
areasArray<{ sectionId?, areaLabel, pricingCategorySlugs }>NoArea assignments by label
tablesArray<{ sectionId?, tableLabel, pricingCategorySlugs }>NoTable assignments by label
tableSeatsArray<{ sectionId?, tableLabel, seatLabel, pricingCategorySlugs }>NoTable seat assignments by label

Returns

AssignPricingCategoriesResult { success: boolean, errors?: string[] } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise.

Example

// Assign by slug (recommended)
const result = SeatSquirrel.picker.assignPricingCategories({
  mode: 'by-slug',
  rows: {
    'row-a': ['vip', 'standard'],
    'row-b': ['standard'],
  },
  areas: {
    'vip-lounge': ['vip'],
  },
});

// Assign by ID
const result = await picker.assignPricingCategories({
  mode: 'by-id',
  rows: {
    'row-uuid-1': ['cat-uuid-1', 'cat-uuid-2'],
    'row-uuid-2': ['cat-uuid-2'],
  },
});

// Dangerously by labels (use with caution)
const result = SeatSquirrel.picker.assignPricingCategories({
  mode: 'dangerously-by-labels',
  rows: [
    { rowLabel: 'A', pricingCategorySlugs: ['vip', 'standard'] },
    { sectionId: 'section-uuid', rowLabel: 'A', pricingCategorySlugs: ['economy'] },
  ],
});

if (!result.success) {
  console.error('Assignment errors:', result.errors);
}

setSelections(input, options?)

Description

Programmatically set selections in the picker. Use this to restore a previous selection state (e.g., from a saved cart) or to pre-select items on behalf of the user.

The engine validates each item against the loaded layout — checking that the entity exists, is available, and that the pricing category is valid when the entity has pricing categories assigned. Uncategorized bookable entities can be selected with categoryId: null / omitted categorySlug. Valid items are applied even when some items fail (best-effort semantics).

Two input modes:

  • by-id (default) — Pass full SelectionItem objects with machine UUIDs. Backward compatible with the legacy input shape.
  • by-slug — Pass simplified SlugSelectionItem objects with human-readable slugs. The engine resolves slugs to IDs and enriches the full SelectionItem internally. Recommended for integrations that don't track internal UUIDs.

The options.mode parameter controls how new selections interact with existing ones:

  • 'replace' (default) — Clears all existing selections before applying the new ones.
  • 'merge' — Adds to existing selections without clearing.

After selections are applied, onSelectionChanged fires automatically with the updated state.

Parameters

Input ModeShapeDescription
by-id (default){ selections: SelectionItem[] } or { mode: 'by-id', selections: SelectionItem[] }Full SelectionItem objects with machine IDs
by-slug{ mode: 'by-slug', selections: SlugSelectionItem[] }Simplified objects with human-readable slugs
  • options (Object, optional):
PropertyTypeRequiredDescription
mode'replace' | 'merge'NoHow to apply selections (default: 'replace')

Returns

SetSelectionsResult { success: boolean, applied: SelectionItem[], failed: SetSelectionsFailedItem[] } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise.

  • success is true only if all items were applied (no failures).
  • applied contains the items that were successfully selected.
  • failed contains each failed item with a reason explaining why.

Example — by-slug mode

// Standalone mode — pre-select items using slugs
const result = SeatSquirrel.picker.setSelections({
  mode: 'by-slug',
  selections: [
    { type: 'row-seat', slug: 'row-a-seat-1', categorySlug: 'standard' },
    { type: 'area', slug: 'vip-lounge', categorySlug: 'vip', quantity: 2 },
    { type: 'table-seat', slug: 'table-1-seat-3', categorySlug: 'premium' },
    { type: 'whole-table', tableSlug: 'table-5', categorySlug: 'standard', quantity: 4 },
  ],
});

if (!result.success) {
  result.failed.forEach(f => {
    console.warn(`Failed: ${f.selection.slug || f.selection.id} — ${f.reason}`);
  });
}

// Iframe SDK mode — by-slug (async)
const result = await picker.setSelections({
  mode: 'by-slug',
  selections: [
    { type: 'row-seat', slug: 'row-a-seat-1', categorySlug: 'standard' },
    { type: 'row-seat', slug: 'row-b-seat-5', categorySlug: null },
  ],
});

Example — by-id mode

// Standalone mode — restore a saved cart (replace mode, default)
const result = SeatSquirrel.picker.setSelections({
  selections: [
    { id: 'seat-id-1', type: 'row-seat', categoryId: 'cat-1' },
    { id: 'seat-id-2', type: 'row-seat', categoryId: null },
    { id: 'area-id-1', type: 'area', categoryId: 'cat-2', quantity: 2 },
  ],
});

if (!result.success) {
  result.failed.forEach(f => {
    console.warn(`Failed: ${f.selection.id} — ${f.reason}`);
  });
}

// Merge mode — add to existing selections
const mergeResult = SeatSquirrel.picker.setSelections(
  {
    selections: [{ id: 'seat-id-3', type: 'row-seat', categoryId: 'cat-1' /* ...other fields */ }],
  },
  { mode: 'merge' }
);

// Iframe SDK mode (async)
const result = await picker.setSelections({
  selections: savedCart.selections,
  lastSelection: null,
  totals: { count: 0, amount: 0 },
});
console.log('Applied:', result.applied.length, 'Failed:', result.failed.length);

clearSelections()

Description

Clear all selected seats and areas.

Returns

{ success: boolean }

Example

picker.clearSelections();

setAllowProceed(allow)

Description

Enable or disable the proceed button. When disabled, the button is greyed out regardless of whether items are selected. Useful for enforcing external validation (e.g., terms acceptance) before allowing the user to proceed.

The proceed button is enabled by default.

Parameters

  • allow (boolean): true to enable the proceed button, false to disable it.

Returns

Standalone: { success: boolean } — iFrame: void (fire-and-forget).

Example

// Disable the proceed button
picker.setAllowProceed(false);

// Re-enable it after external validation passes
picker.setAllowProceed(true);

showToast(options)

Description

Show a toast notification inside the picker UI.

Parameters

  • options (Object):
PropertyTypeRequiredDescription
titlestringYesMain toast message
variantstringNo'info', 'warning', 'error', or 'success' (default: 'info')
descriptionstringNoSecondary description text
actionObjectNoAction button: { label: string, onClick?: function }. Per-toast onClick is called when the action button is clicked

Returns

  • Standalone mode: { success: boolean }
  • iFrame SDK mode: Promise<{ actionClicked: boolean }> — resolves when the toast is dismissed or the action button is clicked

Example

picker.showToast({ title: 'Seats reserved!', variant: 'success' });

// Per-toast action callback (works in both modes)
const { actionClicked } = await picker.showToast({
  title: 'Availability changed',
  variant: 'info',
  description: 'Some seats are no longer available.',
  action: {
    label: 'Refresh',
    onClick: function () {
      fetchLatestAvailability().then(a => picker.setAvailability({ mode: 'by-slug', ...a }));
    },
  },
});
console.log('User clicked action:', actionClicked);

Action handling by mode:

  • Standalone modeaction.onClick runs directly when the button is clicked.
  • iFrame SDK modeaction.onClick is called via correlation IDs when the action button is clicked. The returned Promise resolves with { actionClicked: true } if the action was clicked, or { actionClicked: false } if the toast was dismissed.

showAlertDialog(options)

Description

Show a modal alert dialog inside the picker UI. The dialog blocks interaction and requires the user to explicitly confirm or cancel.

Note: Only one alert dialog can be shown at a time. Calling showAlertDialog() while a dialog is open cancels the previous one. In both modes, onConfirm and onCancel work the same way. In iframe mode, the SDK matches callbacks via internal correlation IDs transparently.

Parameters

  • options (Object):
PropertyTypeRequiredDescription
titlestringYesDialog title text
descriptionstringNoDialog body/description text
confirmTextstringNoConfirm button label (default: 'OK')
cancelTextstringNoCancel button label (default: 'Cancel')
variantstringNo'info', 'warning', 'error', or 'success' — each with distinct icon and color (default: 'info')
modestringNo'alert' (OK button only) or 'confirm' (OK/Cancel buttons) (default: 'alert')
onConfirmfunctionNoCalled when the user clicks the confirm button
onCancelfunctionNoCalled when the user clicks the cancel button

Returns

{ success: boolean }

Example

picker.showAlertDialog({
  title: 'Seat unavailable',
  description: 'This seat was just booked by another customer.',
  mode: 'alert',
  variant: 'error',
  onConfirm: function () {
    fetchLatestAvailability().then(a => picker.setAvailability({ mode: 'by-slug', ...a }));
  },
});

picker.showAlertDialog({
  title: 'Session expired',
  description: 'Your reservation has expired. Please start again.',
  mode: 'alert',
  variant: 'error',
  onConfirm: function () {
    window.location.reload();
  },
});

showNotice(options)

Description

Show a persistent, non-blocking notice banner in the picker UI. Notices remain visible until dismissed by the user or removed via removeNotice(). One notice per position — calling showNotice() with the same position replaces the existing notice (the old notice's onDismiss is not called on replacement).

Parameters

ParameterTypeRequiredDefaultDescription
options.positionstringYes'stage-top', 'cart-top', or 'cart-before-proceed'
options.variantstringNo'info''info', 'warning', 'error', or 'success'
options.titlestringYesMain notice message
options.descriptionstringNoOptional secondary text
options.dismissablebooleanNotrueWhether the user can dismiss via X button
options.actionobjectNo{ label: string, onClick?: () => void } — optional action button
options.onDismissfunctionNoCalled when user dismisses (not called on API removal or replacement)

Positions:

  • stage-top — Banner overlaying the top of the canvas stage. Use for high-visibility alerts (e.g. "Booking closes in 5 min").
  • cart-top — Top of the selection panel, below the header. Use for selection-context notices (e.g. "Max 4 seats per booking").
  • cart-before-proceed — In the cart footer, above the Proceed button. Use for checkout-blocking warnings (e.g. "Select at least 2 seats").

Returns

{ success: boolean, noticeId: string }noticeId can be passed to removeNotice().

Example

// Show a warning over the stage
const { noticeId } = picker.showNotice({
  position: 'stage-top',
  variant: 'warning',
  title: 'Booking closes in 5 minutes',
  dismissable: false,
});

// Show an info notice at the top of the cart
picker.showNotice({
  position: 'cart-top',
  variant: 'info',
  title: 'Maximum 4 seats per booking',
});

// Show an error with an action button before the Proceed button
picker.showNotice({
  position: 'cart-before-proceed',
  variant: 'error',
  title: 'Please select at least 2 seats',
  action: {
    label: 'Help',
    onClick: function () {
      console.log('Help clicked');
    },
  },
});

// Replace the stage-top notice (same position replaces)
picker.showNotice({
  position: 'stage-top',
  variant: 'success',
  title: 'Seats reserved!',
});

removeNotice(noticeId)

Description

Remove a notice by its ID. The notice's onDismiss callback is not called when removed via API — only when the user clicks the X button.

Parameters

ParameterTypeRequiredDescription
noticeIdstringYesThe noticeId returned by showNotice()

Returns

{ success: boolean }true if the notice was found and removed.

Example

const { noticeId } = picker.showNotice({
  position: 'stage-top',
  variant: 'info',
  title: 'Loading availability...',
  dismissable: false,
});

// Later, remove it programmatically
picker.removeNotice(noticeId);

getSelections()

Description

Get the current selections synchronously from cached state.

Returns

(SelectionData) { selections, totals } — current selections and totals

Example

const { selections, totals } = picker.getSelections();
console.log(`${totals.count} items selected, $${totals.amount / 100}`);

getTotals()

Description

Get the last known totals without triggering a callback.

Returns

(Object) { count: number, amount: number }

Example

const totals = picker.getTotals();
console.log(`${totals.count} items, $${totals.amount / 100}`);

getLayout({ mode })

Description

Get the layout by mode. The mode parameter controls what is returned:

  • 'current' — Returns the real-time layout state with current availability and user selections reflected on all seats, areas, tables, and table seats.
  • 'last-saved' — Returns the layout snapshot from when loadLayout() was called (before any user selections). Returns null if no layout has been loaded.

Explicit isAvailable on every seat

In 'current' mode, every seat and table seat has an explicit isAvailable boolean — always true or false, never undefined or omitted.

Parameters

  • input (Object):
PropertyTypeRequiredDescription
mode'current' | 'last-saved'YesWhich layout snapshot to return

Returns

{ layout: LayoutOutput | null } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise<{ layout: LayoutOutput | null }>.

Example

// Standalone mode (synchronous)
const { layout } = SeatSquirrel.picker.getLayout({ mode: 'current' });

// Iframe SDK mode (async)
const { layout } = await picker.getLayout({ mode: 'current' });
if (layout) {
  layout.layout.rows.forEach(row => {
    row.rowSeats.forEach(seat => {
      console.log(seat.label, seat.isAvailable);
    });
  });
}

// Get the last loaded snapshot
const { layout: savedLayout } = picker.getLayout({ mode: 'last-saved' });
if (savedLayout) {
  console.log('Last loaded:', savedLayout);
}

isPickerReady()

Description

Check if the picker is ready to receive commands.

Returns

{ pickerReady: boolean } — Whether the picker is ready


destroy()

iFrame SDK only. Not available in standalone mode — standalone cleanup is handled automatically by the page lifecycle.

Description

Destroy the picker instance. Removes the iframe from the DOM, detaches the PostMessage event listener, and clears all internal state (pending requests, selections, callback maps). Call this when you no longer need the picker — for example, when a modal closes or the user navigates away in a SPA.

Returns

void

Example

picker.destroy();

Next Steps