@seatlayer/js 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -157,6 +157,15 @@ declare class SeatingChart {
157
157
  constructor(options: SeatingChartOptions);
158
158
  /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
159
159
  render(): Promise<this>;
160
+ /**
161
+ * Attribution badge pinned to the embed's bottom-right, linking to
162
+ * seatlayer.io. Rendered as an absolutely-positioned overlay with
163
+ * self-contained inline styles — the SDK embed ships no widget CSS, and an
164
+ * overlay keeps it out of the layout flow so it never disturbs the SDK v0.22
165
+ * fill-height resize contract. Mirrors the full widget's mark + wordmark and
166
+ * reuses the `picker.poweredBy` i18n string.
167
+ */
168
+ private buildBadge;
160
169
  private placeTooltip;
161
170
  private updateTooltip;
162
171
  /** Current selection with prices resolved from the chart categories. */
@@ -257,15 +266,28 @@ interface EmbeddedDesignerOptions {
257
266
  /**
258
267
  * How to size the iframe's height. The Designer is a full application (its
259
268
  * shell is `position:fixed; height:100dvh`), not flowing content, so it should
260
- * fill the viewport rather than be measured.
269
+ * fill its box rather than be measured.
261
270
  *
262
- * - `'fill'` (default): grow the iframe so its bottom edge reaches the bottom
263
- * of the viewport `window.innerHeight - iframe.top`, clamped to `minHeight`
264
- * — recomputed (rAF-throttled) on `resize` / `orientationchange` / `scroll`.
265
- * The legacy `seatlayer.designer.resize` message is ignored in this mode: it
266
- * is circular, because the fixed-position shell just echoes the iframe height.
271
+ * - `'fill'` (default): container-aware. On mount the SDK probes whether the
272
+ * host gave the container a DEFINITE (bounded) height:
273
+ * - **Bounded container** (a fixed-height block, `height`/`max-height`,
274
+ * `flex:1; min-h:0`, a resolved `%`, etc.) the iframe fills 100% of
275
+ * that block and tracks its size live via a `ResizeObserver`.
276
+ * - **Content-sized container** (the block collapses to whatever the iframe
277
+ * measures — typical full-page usage) → the iframe grows so its bottom
278
+ * edge reaches the bottom of the viewport (`window.innerHeight -
279
+ * iframe.top`), recomputed (rAF-throttled) on `resize` /
280
+ * `orientationchange` / `scroll`.
281
+ * Either way the result is clamped to `minHeight`. The verdict is cached but
282
+ * re-probed on `resize`/`orientationchange` so a responsive host layout can
283
+ * flip between the two. The legacy `seatlayer.designer.resize` message is
284
+ * ignored in `'fill'` mode: it is circular, because the fixed-position shell
285
+ * just echoes the iframe height.
267
286
  * - a number: a fixed pixel height. In this mode the legacy resize message is
268
287
  * still honoured (unless `autoResize` is `false`) so older hosts keep growing.
288
+ *
289
+ * All SDK-managed heights are written with `!important` priority so a host
290
+ * theme's `iframe { height: … !important }` cannot override them.
269
291
  */
270
292
  height?: 'fill' | number;
271
293
  /** Minimum height (px) that `'fill'` mode clamps to. Defaults to `480`. */
@@ -282,8 +304,30 @@ interface EmbeddedDesignerOptions {
282
304
  * fresh Designer session and call `setDesignerUrl()` with the new URL, which
283
305
  * recreates the iframe and returns to the loading state. When omitted, "Try
284
306
  * again" reloads the current `designerUrl` in place.
307
+ *
308
+ * When supplied, it also powers automatic session renewal — see
309
+ * {@link EmbeddedDesignerOptions.autoRenewSession}.
285
310
  */
286
311
  onRequestRelaunch?: () => void;
312
+ /**
313
+ * Keep long editing sessions alive without the user ever hitting the expiry
314
+ * wall. Designer sessions are short-lived security tokens; when the host wires
315
+ * `onRequestRelaunch` the SDK, with this enabled, will:
316
+ *
317
+ * - **Renew proactively.** From each `ready` message's `expiresAt` it schedules
318
+ * a silent relaunch shortly before the session lapses (~3 min ahead; for a
319
+ * TTL under 15 min it renews after 80% of the remaining life, and never
320
+ * sooner than 30s after `ready`). The host mints a fresh session and swaps
321
+ * `designerUrl`, so the editor keeps working with no error card.
322
+ * - **Recover on expiry.** If an expiry error still slips through (a slept
323
+ * laptop woke past the renewal window, say) it makes ONE automatic relaunch
324
+ * attempt before showing the "Try again" card, and only falls back to the
325
+ * card if that relaunch also fails.
326
+ *
327
+ * Defaults to `true` whenever `onRequestRelaunch` is provided; a no-op without
328
+ * it. Set `false` to keep the fully manual "Try again" behavior.
329
+ */
330
+ autoRenewSession?: boolean;
287
331
  onReady?: (message: EmbeddedDesignerMessage) => void;
288
332
  onSaved?: (message: EmbeddedDesignerMessage) => void;
289
333
  onPublished?: (message: EmbeddedDesignerMessage) => void;
@@ -297,6 +341,14 @@ declare class EmbeddedDesigner {
297
341
  private designerOrigin;
298
342
  private overlay;
299
343
  private timeoutTimer;
344
+ /** Proactive session-renewal timer; armed from each `ready`, cleared on re-mount. */
345
+ private renewTimer;
346
+ /**
347
+ * One automatic recovery relaunch is allowed per expiry. Reset ONLY when a fresh
348
+ * `ready` arrives — deliberately not on re-mount — so a session that keeps failing
349
+ * to load can't loop the host through endless silent relaunches.
350
+ */
351
+ private autoRecoverUsed;
300
352
  private phase;
301
353
  private restoreContainerPosition;
302
354
  private pinned;
@@ -307,7 +359,14 @@ declare class EmbeddedDesigner {
307
359
  /** Latest height (px string) the Designer reported; re-applied after unpin. */
308
360
  private lastAutoHeight;
309
361
  private fillRaf;
362
+ private reprobeRaf;
310
363
  private fillListening;
364
+ /** Resolved container element (fill measurement + ResizeObserver target). */
365
+ private containerEl;
366
+ /** Cached fill verdict: 'container' = bounded block, 'viewport' = full page. */
367
+ private fillMode;
368
+ /** Live block-size tracking in container-fill mode; disconnected on destroy. */
369
+ private resizeObs;
311
370
  constructor(options: EmbeddedDesignerOptions);
312
371
  mount(): HTMLIFrameElement;
313
372
  /** Replace the iframe instead of assigning a new fragment to an existing one. */
@@ -318,14 +377,39 @@ declare class EmbeddedDesigner {
318
377
  private autoResizeEnabled;
319
378
  /** Fill mode is the default; a numeric `height` opts into a fixed pixel box. */
320
379
  private fillEnabled;
380
+ /** Write an SDK-managed height with `!important` so a host theme can't win. */
381
+ private setFrameHeight;
321
382
  /**
322
- * Size the iframe so its bottom edge meets the bottom of the viewport
323
- * (`window.innerHeight - top`), clamped to `minHeight`. No-op while pinned
324
- * fullscreen (the pin fills the viewport itself).
383
+ * Decide whether the host gave the container a DEFINITE (bounded) height a
384
+ * fixed block the embed should fill 100% of — versus a content-sized container
385
+ * that collapses to whatever the iframe measures (full-page usage).
386
+ *
387
+ * We drive the iframe to two extreme heights within a single synchronous task
388
+ * and watch whether the container follows: a bounded box barely moves, a
389
+ * content-sized one grows with the iframe. Because we restore the height before
390
+ * yielding, the browser only lays out — it never paints the extremes, so there
391
+ * is no visible flash. Works for px, resolved `%`, and flex (`flex:1;min-h:0`)
392
+ * heights, and leaves a mere `min-height` floor classified as content-sized so
393
+ * full-page hosts keep the old viewport-fill behavior.
394
+ */
395
+ private detectFillMode;
396
+ /**
397
+ * Size the iframe for the current fill verdict, clamped to `minHeight`. In
398
+ * container mode it fills 100% of the bounded block; in viewport mode its
399
+ * bottom edge meets the bottom of the viewport (`window.innerHeight - top`).
400
+ * No-op while pinned fullscreen (the pin fills the viewport itself).
325
401
  */
326
402
  private applyFill;
327
- /** rAF-throttled fill recompute, so a burst of scroll/resize ticks coalesces. */
403
+ /** rAF-throttled fill recompute, so a burst of scroll/RO ticks coalesces. */
328
404
  private scheduleFill;
405
+ /**
406
+ * rAF-throttled re-probe: a host layout change (responsive breakpoint, a block
407
+ * gaining/losing a definite height) can flip the verdict, so `resize` /
408
+ * `orientationchange` re-detect and swap the container observer accordingly.
409
+ */
410
+ private scheduleReprobe;
411
+ /** Attach/detach the container ResizeObserver to match the current verdict. */
412
+ private syncContainerObserver;
329
413
  private startFill;
330
414
  private stopFill;
331
415
  /**
@@ -337,6 +421,29 @@ declare class EmbeddedDesigner {
337
421
  /** Undo `pinFullscreen`: restore the iframe style + scroll lock. Idempotent. */
338
422
  private unpinFullscreen;
339
423
  private clearTimeoutTimer;
424
+ /**
425
+ * Auto-renewal (proactive + one expiry recovery) is on when the host wired a
426
+ * relaunch hook and did not opt out. Without the hook there is nothing to call,
427
+ * so it is a no-op.
428
+ */
429
+ private autoRenewEnabled;
430
+ private clearRenewTimer;
431
+ /**
432
+ * Arm the proactive renewal timer from a `ready` message's `expiresAt` (epoch
433
+ * ms). We relaunch a comfortable lead before expiry so the host can mint a fresh
434
+ * session and swap `designerUrl` without the user ever seeing the expiry card:
435
+ *
436
+ * - normal TTL (≥ 15 min): renew {@link RENEW_LEAD_MS} (~3 min) before expiry;
437
+ * - short TTL (< 15 min): renew after {@link RENEW_SHORT_TTL_FRACTION} (80%) of
438
+ * the remaining life, so the lead can't overshoot the whole session;
439
+ * - either way, never sooner than {@link RENEW_MIN_DELAY_MS} (30s) after `ready`
440
+ * so a burst of `ready` messages can't spin the host.
441
+ *
442
+ * Re-armed on every `ready`; cleared on destroy / setDesignerUrl (via re-mount).
443
+ * A no-op when auto-renewal is off or `expiresAt` is missing/already past — the
444
+ * expiry-error path recovers a session that has already lapsed.
445
+ */
446
+ private scheduleRenewal;
340
447
  private ensureContainerPositioned;
341
448
  private restoreContainerStyle;
342
449
  private removeOverlay;
package/dist/index.d.ts CHANGED
@@ -157,6 +157,15 @@ declare class SeatingChart {
157
157
  constructor(options: SeatingChartOptions);
158
158
  /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
159
159
  render(): Promise<this>;
160
+ /**
161
+ * Attribution badge pinned to the embed's bottom-right, linking to
162
+ * seatlayer.io. Rendered as an absolutely-positioned overlay with
163
+ * self-contained inline styles — the SDK embed ships no widget CSS, and an
164
+ * overlay keeps it out of the layout flow so it never disturbs the SDK v0.22
165
+ * fill-height resize contract. Mirrors the full widget's mark + wordmark and
166
+ * reuses the `picker.poweredBy` i18n string.
167
+ */
168
+ private buildBadge;
160
169
  private placeTooltip;
161
170
  private updateTooltip;
162
171
  /** Current selection with prices resolved from the chart categories. */
@@ -257,15 +266,28 @@ interface EmbeddedDesignerOptions {
257
266
  /**
258
267
  * How to size the iframe's height. The Designer is a full application (its
259
268
  * shell is `position:fixed; height:100dvh`), not flowing content, so it should
260
- * fill the viewport rather than be measured.
269
+ * fill its box rather than be measured.
261
270
  *
262
- * - `'fill'` (default): grow the iframe so its bottom edge reaches the bottom
263
- * of the viewport `window.innerHeight - iframe.top`, clamped to `minHeight`
264
- * — recomputed (rAF-throttled) on `resize` / `orientationchange` / `scroll`.
265
- * The legacy `seatlayer.designer.resize` message is ignored in this mode: it
266
- * is circular, because the fixed-position shell just echoes the iframe height.
271
+ * - `'fill'` (default): container-aware. On mount the SDK probes whether the
272
+ * host gave the container a DEFINITE (bounded) height:
273
+ * - **Bounded container** (a fixed-height block, `height`/`max-height`,
274
+ * `flex:1; min-h:0`, a resolved `%`, etc.) the iframe fills 100% of
275
+ * that block and tracks its size live via a `ResizeObserver`.
276
+ * - **Content-sized container** (the block collapses to whatever the iframe
277
+ * measures — typical full-page usage) → the iframe grows so its bottom
278
+ * edge reaches the bottom of the viewport (`window.innerHeight -
279
+ * iframe.top`), recomputed (rAF-throttled) on `resize` /
280
+ * `orientationchange` / `scroll`.
281
+ * Either way the result is clamped to `minHeight`. The verdict is cached but
282
+ * re-probed on `resize`/`orientationchange` so a responsive host layout can
283
+ * flip between the two. The legacy `seatlayer.designer.resize` message is
284
+ * ignored in `'fill'` mode: it is circular, because the fixed-position shell
285
+ * just echoes the iframe height.
267
286
  * - a number: a fixed pixel height. In this mode the legacy resize message is
268
287
  * still honoured (unless `autoResize` is `false`) so older hosts keep growing.
288
+ *
289
+ * All SDK-managed heights are written with `!important` priority so a host
290
+ * theme's `iframe { height: … !important }` cannot override them.
269
291
  */
270
292
  height?: 'fill' | number;
271
293
  /** Minimum height (px) that `'fill'` mode clamps to. Defaults to `480`. */
@@ -282,8 +304,30 @@ interface EmbeddedDesignerOptions {
282
304
  * fresh Designer session and call `setDesignerUrl()` with the new URL, which
283
305
  * recreates the iframe and returns to the loading state. When omitted, "Try
284
306
  * again" reloads the current `designerUrl` in place.
307
+ *
308
+ * When supplied, it also powers automatic session renewal — see
309
+ * {@link EmbeddedDesignerOptions.autoRenewSession}.
285
310
  */
286
311
  onRequestRelaunch?: () => void;
312
+ /**
313
+ * Keep long editing sessions alive without the user ever hitting the expiry
314
+ * wall. Designer sessions are short-lived security tokens; when the host wires
315
+ * `onRequestRelaunch` the SDK, with this enabled, will:
316
+ *
317
+ * - **Renew proactively.** From each `ready` message's `expiresAt` it schedules
318
+ * a silent relaunch shortly before the session lapses (~3 min ahead; for a
319
+ * TTL under 15 min it renews after 80% of the remaining life, and never
320
+ * sooner than 30s after `ready`). The host mints a fresh session and swaps
321
+ * `designerUrl`, so the editor keeps working with no error card.
322
+ * - **Recover on expiry.** If an expiry error still slips through (a slept
323
+ * laptop woke past the renewal window, say) it makes ONE automatic relaunch
324
+ * attempt before showing the "Try again" card, and only falls back to the
325
+ * card if that relaunch also fails.
326
+ *
327
+ * Defaults to `true` whenever `onRequestRelaunch` is provided; a no-op without
328
+ * it. Set `false` to keep the fully manual "Try again" behavior.
329
+ */
330
+ autoRenewSession?: boolean;
287
331
  onReady?: (message: EmbeddedDesignerMessage) => void;
288
332
  onSaved?: (message: EmbeddedDesignerMessage) => void;
289
333
  onPublished?: (message: EmbeddedDesignerMessage) => void;
@@ -297,6 +341,14 @@ declare class EmbeddedDesigner {
297
341
  private designerOrigin;
298
342
  private overlay;
299
343
  private timeoutTimer;
344
+ /** Proactive session-renewal timer; armed from each `ready`, cleared on re-mount. */
345
+ private renewTimer;
346
+ /**
347
+ * One automatic recovery relaunch is allowed per expiry. Reset ONLY when a fresh
348
+ * `ready` arrives — deliberately not on re-mount — so a session that keeps failing
349
+ * to load can't loop the host through endless silent relaunches.
350
+ */
351
+ private autoRecoverUsed;
300
352
  private phase;
301
353
  private restoreContainerPosition;
302
354
  private pinned;
@@ -307,7 +359,14 @@ declare class EmbeddedDesigner {
307
359
  /** Latest height (px string) the Designer reported; re-applied after unpin. */
308
360
  private lastAutoHeight;
309
361
  private fillRaf;
362
+ private reprobeRaf;
310
363
  private fillListening;
364
+ /** Resolved container element (fill measurement + ResizeObserver target). */
365
+ private containerEl;
366
+ /** Cached fill verdict: 'container' = bounded block, 'viewport' = full page. */
367
+ private fillMode;
368
+ /** Live block-size tracking in container-fill mode; disconnected on destroy. */
369
+ private resizeObs;
311
370
  constructor(options: EmbeddedDesignerOptions);
312
371
  mount(): HTMLIFrameElement;
313
372
  /** Replace the iframe instead of assigning a new fragment to an existing one. */
@@ -318,14 +377,39 @@ declare class EmbeddedDesigner {
318
377
  private autoResizeEnabled;
319
378
  /** Fill mode is the default; a numeric `height` opts into a fixed pixel box. */
320
379
  private fillEnabled;
380
+ /** Write an SDK-managed height with `!important` so a host theme can't win. */
381
+ private setFrameHeight;
321
382
  /**
322
- * Size the iframe so its bottom edge meets the bottom of the viewport
323
- * (`window.innerHeight - top`), clamped to `minHeight`. No-op while pinned
324
- * fullscreen (the pin fills the viewport itself).
383
+ * Decide whether the host gave the container a DEFINITE (bounded) height a
384
+ * fixed block the embed should fill 100% of — versus a content-sized container
385
+ * that collapses to whatever the iframe measures (full-page usage).
386
+ *
387
+ * We drive the iframe to two extreme heights within a single synchronous task
388
+ * and watch whether the container follows: a bounded box barely moves, a
389
+ * content-sized one grows with the iframe. Because we restore the height before
390
+ * yielding, the browser only lays out — it never paints the extremes, so there
391
+ * is no visible flash. Works for px, resolved `%`, and flex (`flex:1;min-h:0`)
392
+ * heights, and leaves a mere `min-height` floor classified as content-sized so
393
+ * full-page hosts keep the old viewport-fill behavior.
394
+ */
395
+ private detectFillMode;
396
+ /**
397
+ * Size the iframe for the current fill verdict, clamped to `minHeight`. In
398
+ * container mode it fills 100% of the bounded block; in viewport mode its
399
+ * bottom edge meets the bottom of the viewport (`window.innerHeight - top`).
400
+ * No-op while pinned fullscreen (the pin fills the viewport itself).
325
401
  */
326
402
  private applyFill;
327
- /** rAF-throttled fill recompute, so a burst of scroll/resize ticks coalesces. */
403
+ /** rAF-throttled fill recompute, so a burst of scroll/RO ticks coalesces. */
328
404
  private scheduleFill;
405
+ /**
406
+ * rAF-throttled re-probe: a host layout change (responsive breakpoint, a block
407
+ * gaining/losing a definite height) can flip the verdict, so `resize` /
408
+ * `orientationchange` re-detect and swap the container observer accordingly.
409
+ */
410
+ private scheduleReprobe;
411
+ /** Attach/detach the container ResizeObserver to match the current verdict. */
412
+ private syncContainerObserver;
329
413
  private startFill;
330
414
  private stopFill;
331
415
  /**
@@ -337,6 +421,29 @@ declare class EmbeddedDesigner {
337
421
  /** Undo `pinFullscreen`: restore the iframe style + scroll lock. Idempotent. */
338
422
  private unpinFullscreen;
339
423
  private clearTimeoutTimer;
424
+ /**
425
+ * Auto-renewal (proactive + one expiry recovery) is on when the host wired a
426
+ * relaunch hook and did not opt out. Without the hook there is nothing to call,
427
+ * so it is a no-op.
428
+ */
429
+ private autoRenewEnabled;
430
+ private clearRenewTimer;
431
+ /**
432
+ * Arm the proactive renewal timer from a `ready` message's `expiresAt` (epoch
433
+ * ms). We relaunch a comfortable lead before expiry so the host can mint a fresh
434
+ * session and swap `designerUrl` without the user ever seeing the expiry card:
435
+ *
436
+ * - normal TTL (≥ 15 min): renew {@link RENEW_LEAD_MS} (~3 min) before expiry;
437
+ * - short TTL (< 15 min): renew after {@link RENEW_SHORT_TTL_FRACTION} (80%) of
438
+ * the remaining life, so the lead can't overshoot the whole session;
439
+ * - either way, never sooner than {@link RENEW_MIN_DELAY_MS} (30s) after `ready`
440
+ * so a burst of `ready` messages can't spin the host.
441
+ *
442
+ * Re-armed on every `ready`; cleared on destroy / setDesignerUrl (via re-mount).
443
+ * A no-op when auto-renewal is off or `expiresAt` is missing/already past — the
444
+ * expiry-error path recovers a session that has already lapsed.
445
+ */
446
+ private scheduleRenewal;
340
447
  private ensureContainerPositioned;
341
448
  private restoreContainerStyle;
342
449
  private removeOverlay;