@seatlayer/js 0.23.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/README.md CHANGED
@@ -101,8 +101,19 @@ designer.setDesignerUrl(nextSession.designerUrl);
101
101
  designer.destroy();
102
102
  ```
103
103
 
104
- Give the container a height, for example `min-height: 760px`. Keep the default
105
- `referrerPolicy: 'origin'`; the Designer uses it to verify the parent origin.
104
+ The default `height: 'fill'` is **container-aware**. On mount the SDK probes the
105
+ container: if you gave it a definite height a fixed-height block, `height` /
106
+ `max-height`, a `flex:1; min-h-0` child, a resolved `%` — the iframe fills 100% of
107
+ that block and tracks its size live via a `ResizeObserver`. If the container is
108
+ content-sized (it collapses to whatever the iframe measures — typical full-page
109
+ usage), the iframe instead grows so its bottom edge meets the bottom of the
110
+ viewport. Either way the result is clamped to `minHeight` (default `480`), and the
111
+ verdict is re-probed on resize so a responsive layout can flip between the two. So
112
+ you can either drop the Designer into a sized block (it fills the block) or give it
113
+ `min-height: 760px` for full-page use (it fills the viewport). Every SDK-managed
114
+ height is written with `!important`, so a host theme's `iframe { height: … }` rule
115
+ can't override it. Keep the default `referrerPolicy: 'origin'`; the Designer uses
116
+ it to verify the parent origin.
106
117
 
107
118
  ### Built-in loading, error, and expiry states
108
119
 
@@ -117,11 +128,51 @@ CSS files or external assets.
117
128
  | --- | --- | --- | --- |
118
129
  | `showLoadingState` | `boolean` | `true` | Render the built-in skeleton and error card. Set `false` when you draw your own chrome. |
119
130
  | `loadingTimeoutMs` | `number` | `20000` | If `ready` never arrives within this window, show the error card with a timeout message. |
120
- | `onRequestRelaunch` | `() => void` | — | Called by **"Try again"**. Mint a fresh session and call `setDesignerUrl()`; the iframe recreates and returns to loading. When omitted, "Try again" reloads the current URL in place. |
131
+ | `onRequestRelaunch` | `() => void` | — | Called by **"Try again"** _and_ by automatic renewal (below). Mint a fresh session and call `setDesignerUrl()`; the iframe recreates and returns to loading. When omitted, "Try again" reloads the current URL in place. |
132
+ | `autoRenewSession` | `boolean` | `true`¹ | Silently renew the session before it expires and auto-recover once if an expiry error slips through. ¹Defaults `true` only when `onRequestRelaunch` is provided; a no-op without it. Set `false` for fully manual "Try again". |
121
133
 
122
134
  `setDesignerUrl()` always returns the host to the loading state, so a relaunch
123
135
  flow needs no extra bookkeeping.
124
136
 
137
+ ### Session lifecycle
138
+
139
+ Designer sessions are **short-lived by design**: your backend mints a `dse_`
140
+ token (default 1 hour, up to 4 hours via `expiresInSeconds`) and bakes it into
141
+ `designerUrl`. Pick a TTL that fits how long organizers actually edit — longer is
142
+ not automatically better; the renewal below keeps even a multi-hour session alive.
143
+
144
+ Provide `onRequestRelaunch` returning (or awaiting) a freshly minted session, and
145
+ the SDK turns expiry into a non-event:
146
+
147
+ - **Silent proactive renewal.** From each `ready` message's `expiresAt` the SDK
148
+ schedules an automatic relaunch shortly before the session lapses — ~3 minutes
149
+ ahead, or, for a TTL under 15 minutes, after 80% of the remaining life (never
150
+ sooner than 30s after `ready`). Your `onRequestRelaunch` mints a fresh session
151
+ and swaps `designerUrl`, so editing continues with no expiry card. The timer
152
+ re-arms from every `ready`.
153
+ - **Automatic expiry recovery.** If an expiry error still arrives (a laptop that
154
+ slept past the renewal window, say), the SDK makes **one** automatic relaunch
155
+ attempt before showing the "Try again" card, and only falls back to the card if
156
+ that attempt also fails.
157
+
158
+ Relaunching is safe: **in-progress work is autosaved server-side**, so a fresh
159
+ iframe restores the organizer's chart where they left off.
160
+
161
+ ```js
162
+ const designer = new EmbeddedDesigner({
163
+ container: '#venue-designer',
164
+ designerUrl: session.designerUrl,
165
+ expectedChartId: session.chartId,
166
+ // Mint a fresh session on renewal, expiry recovery, or "Try again":
167
+ onRequestRelaunch: async () => {
168
+ const next = await mintDesignerSession(session.chartId); // your backend, up to 4h TTL
169
+ designer.setDesignerUrl(next.designerUrl); // recreates the iframe
170
+ },
171
+ // autoRenewSession defaults to true because onRequestRelaunch is present.
172
+ });
173
+ designer.mount();
174
+ ```
175
+
125
176
  ## API
126
177
 
127
178
  `new SeatingChart(options)` — options: `container` (selector or element, required),
package/dist/index.cjs CHANGED
@@ -205,8 +205,28 @@ var SeatingChart = class {
205
205
  ribbon.style.cssText = "position:absolute;top:18px;right:-34px;z-index:6;transform:rotate(45deg);width:140px;text-align:center;padding:4px 0;background:#f4b740;color:#1a1200;font:800 10.5px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;letter-spacing:.12em;box-shadow:0 2px 8px rgba(0,0,0,.25);pointer-events:none;";
206
206
  host.appendChild(ribbon);
207
207
  }
208
+ this.buildBadge(host);
208
209
  return this;
209
210
  }
211
+ /**
212
+ * Attribution badge pinned to the embed's bottom-right, linking to
213
+ * seatlayer.io. Rendered as an absolutely-positioned overlay with
214
+ * self-contained inline styles — the SDK embed ships no widget CSS, and an
215
+ * overlay keeps it out of the layout flow so it never disturbs the SDK v0.22
216
+ * fill-height resize contract. Mirrors the full widget's mark + wordmark and
217
+ * reuses the `picker.poweredBy` i18n string.
218
+ */
219
+ buildBadge(host) {
220
+ if (this.controller.doc?.theme?.hideBadge) return;
221
+ const badge = document.createElement("a");
222
+ badge.href = "https://seatlayer.io";
223
+ badge.target = "_blank";
224
+ badge.rel = "noopener noreferrer";
225
+ badge.setAttribute("aria-label", (0, import_core.t)("picker.poweredBy"));
226
+ badge.style.cssText = 'position:absolute;bottom:10px;right:12px;z-index:5;display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border-radius:999px;background:rgba(255,255,255,.92);color:#4a5163;text-decoration:none;font:600 11px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:.02em;box-shadow:0 2px 8px rgba(0,0,0,.12);';
227
+ badge.innerHTML = `<span aria-hidden="true" style="width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;background:#f4b740;color:#1a1200"><svg viewBox="0 0 24 24" style="width:11px;height:11px;fill:currentColor"><path d="M4 15c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v3h-3v-2H7v2H4v-3Z"/><rect x="7" y="7" width="10" height="5" rx="1.6"/></svg></span><span>${(0, import_core.t)("picker.poweredBy")}</span>`;
228
+ host.appendChild(badge);
229
+ }
210
230
  placeTooltip() {
211
231
  if (!this.tipEl || !this.hostEl) return;
212
232
  const hw = this.hostEl.clientWidth;
@@ -361,6 +381,13 @@ var TYPES = /* @__PURE__ */ new Set([
361
381
  ]);
362
382
  var DEFAULT_LOADING_TIMEOUT_MS = 2e4;
363
383
  var DEFAULT_MIN_FILL_HEIGHT = 480;
384
+ var RENEW_LEAD_MS = 3 * 60 * 1e3;
385
+ var RENEW_SHORT_TTL_MS = 15 * 60 * 1e3;
386
+ var RENEW_SHORT_TTL_FRACTION = 0.8;
387
+ var RENEW_MIN_DELAY_MS = 30 * 1e3;
388
+ var FILL_PROBE_HEIGHT_PX = 1e5;
389
+ var FILL_PROBE_TRACK_EPSILON_PX = 4;
390
+ var FILL_MIN_DEFINITE_HEIGHT_PX = 50;
364
391
  function resolveContainer2(container) {
365
392
  if (typeof container !== "string") return container;
366
393
  const element = document.querySelector(container);
@@ -398,6 +425,14 @@ var EmbeddedDesigner = class {
398
425
  this.designerOrigin = "";
399
426
  this.overlay = null;
400
427
  this.timeoutTimer = null;
428
+ /** Proactive session-renewal timer; armed from each `ready`, cleared on re-mount. */
429
+ this.renewTimer = null;
430
+ /**
431
+ * One automatic recovery relaunch is allowed per expiry. Reset ONLY when a fresh
432
+ * `ready` arrives — deliberately not on re-mount — so a session that keeps failing
433
+ * to load can't loop the host through endless silent relaunches.
434
+ */
435
+ this.autoRecoverUsed = false;
401
436
  this.phase = "loading";
402
437
  this.restoreContainerPosition = null;
403
438
  // Host-side fullscreen pin: saved state we restore on `off`/Escape/destroy.
@@ -408,10 +443,17 @@ var EmbeddedDesigner = class {
408
443
  this.fsKeyHandler = null;
409
444
  /** Latest height (px string) the Designer reported; re-applied after unpin. */
410
445
  this.lastAutoHeight = "";
411
- // Viewport-fill sizing: pending rAF handle + whether listeners are attached.
446
+ // Fill sizing: pending rAF handles + whether window listeners are attached.
412
447
  this.fillRaf = null;
448
+ this.reprobeRaf = null;
413
449
  this.fillListening = false;
414
- /** rAF-throttled fill recompute, so a burst of scroll/resize ticks coalesces. */
450
+ /** Resolved container element (fill measurement + ResizeObserver target). */
451
+ this.containerEl = null;
452
+ /** Cached fill verdict: 'container' = bounded block, 'viewport' = full page. */
453
+ this.fillMode = null;
454
+ /** Live block-size tracking in container-fill mode; disconnected on destroy. */
455
+ this.resizeObs = null;
456
+ /** rAF-throttled fill recompute, so a burst of scroll/RO ticks coalesces. */
415
457
  this.scheduleFill = () => {
416
458
  if (this.fillRaf !== null) return;
417
459
  this.fillRaf = requestAnimationFrame(() => {
@@ -419,6 +461,21 @@ var EmbeddedDesigner = class {
419
461
  this.applyFill();
420
462
  });
421
463
  };
464
+ /**
465
+ * rAF-throttled re-probe: a host layout change (responsive breakpoint, a block
466
+ * gaining/losing a definite height) can flip the verdict, so `resize` /
467
+ * `orientationchange` re-detect and swap the container observer accordingly.
468
+ */
469
+ this.scheduleReprobe = () => {
470
+ if (this.reprobeRaf !== null) return;
471
+ this.reprobeRaf = requestAnimationFrame(() => {
472
+ this.reprobeRaf = null;
473
+ if (this.pinned) return;
474
+ this.fillMode = this.detectFillMode();
475
+ this.syncContainerObserver();
476
+ this.applyFill();
477
+ });
478
+ };
422
479
  this.handleMessage = (event) => {
423
480
  if (!this.frame || event.origin !== this.designerOrigin || event.source !== this.frame.contentWindow) return;
424
481
  if (!event.data || typeof event.data !== "object") return;
@@ -426,7 +483,7 @@ var EmbeddedDesigner = class {
426
483
  if (data.type === "seatlayer.designer.resize") {
427
484
  if (!this.fillEnabled() && this.autoResizeEnabled() && typeof data.px === "number" && Number.isFinite(data.px) && data.px > 0) {
428
485
  this.lastAutoHeight = `${Math.round(data.px)}px`;
429
- if (!this.pinned) this.frame.style.height = this.lastAutoHeight;
486
+ if (!this.pinned) this.setFrameHeight(this.lastAutoHeight);
430
487
  }
431
488
  return;
432
489
  }
@@ -454,6 +511,8 @@ var EmbeddedDesigner = class {
454
511
  this.phase = "ready";
455
512
  this.clearTimeoutTimer();
456
513
  this.removeOverlay();
514
+ this.autoRecoverUsed = false;
515
+ this.scheduleRenewal(message.expiresAt);
457
516
  this.options.onReady?.(message);
458
517
  break;
459
518
  case "seatlayer.designer.saved":
@@ -465,10 +524,18 @@ var EmbeddedDesigner = class {
465
524
  case "seatlayer.designer.close":
466
525
  this.options.onClose?.(message);
467
526
  break;
468
- case "seatlayer.designer.error":
469
- this.showError(causeFromCode(message.code));
527
+ case "seatlayer.designer.error": {
528
+ const cause = causeFromCode(message.code);
529
+ if (cause === "expired" && this.autoRenewEnabled() && !this.autoRecoverUsed) {
530
+ this.autoRecoverUsed = true;
531
+ this.clearRenewTimer();
532
+ this.options.onRequestRelaunch();
533
+ return;
534
+ }
535
+ this.showError(cause);
470
536
  this.options.onError?.(message);
471
537
  break;
538
+ }
472
539
  }
473
540
  };
474
541
  this.options = options;
@@ -485,12 +552,17 @@ var EmbeddedDesigner = class {
485
552
  frame.allow = this.options.allow ?? "fullscreen; clipboard-write";
486
553
  frame.referrerPolicy = this.options.referrerPolicy ?? "origin";
487
554
  frame.src = url.toString();
488
- frame.style.width = "100%";
489
- frame.style.height = typeof this.options.height === "number" ? `${this.options.height}px` : "100%";
555
+ frame.style.setProperty("width", "100%", "important");
556
+ frame.style.setProperty(
557
+ "height",
558
+ typeof this.options.height === "number" ? `${this.options.height}px` : "100%",
559
+ "important"
560
+ );
490
561
  frame.style.border = "0";
491
562
  Object.assign(frame.style, this.options.style);
492
563
  if (this.options.className) frame.className = this.options.className;
493
564
  const container = resolveContainer2(this.options.container);
565
+ this.containerEl = container;
494
566
  window.addEventListener("message", this.handleMessage);
495
567
  container.append(frame);
496
568
  this.frame = frame;
@@ -521,10 +593,13 @@ var EmbeddedDesigner = class {
521
593
  this.stopFill();
522
594
  this.unpinFullscreen();
523
595
  this.clearTimeoutTimer();
596
+ this.clearRenewTimer();
524
597
  this.removeOverlay();
525
598
  this.restoreContainerStyle();
526
599
  this.frame?.remove();
527
600
  this.frame = null;
601
+ this.containerEl = null;
602
+ this.fillMode = null;
528
603
  this.designerOrigin = "";
529
604
  this.phase = "loading";
530
605
  this.lastAutoHeight = "";
@@ -539,24 +614,77 @@ var EmbeddedDesigner = class {
539
614
  fillEnabled() {
540
615
  return typeof this.options.height !== "number";
541
616
  }
617
+ /** Write an SDK-managed height with `!important` so a host theme can't win. */
618
+ setFrameHeight(value) {
619
+ this.frame?.style.setProperty("height", value, "important");
620
+ }
542
621
  /**
543
- * Size the iframe so its bottom edge meets the bottom of the viewport
544
- * (`window.innerHeight - top`), clamped to `minHeight`. No-op while pinned
545
- * fullscreen (the pin fills the viewport itself).
622
+ * Decide whether the host gave the container a DEFINITE (bounded) height a
623
+ * fixed block the embed should fill 100% of — versus a content-sized container
624
+ * that collapses to whatever the iframe measures (full-page usage).
625
+ *
626
+ * We drive the iframe to two extreme heights within a single synchronous task
627
+ * and watch whether the container follows: a bounded box barely moves, a
628
+ * content-sized one grows with the iframe. Because we restore the height before
629
+ * yielding, the browser only lays out — it never paints the extremes, so there
630
+ * is no visible flash. Works for px, resolved `%`, and flex (`flex:1;min-h:0`)
631
+ * heights, and leaves a mere `min-height` floor classified as content-sized so
632
+ * full-page hosts keep the old viewport-fill behavior.
633
+ */
634
+ detectFillMode() {
635
+ const container = this.containerEl;
636
+ const frame = this.frame;
637
+ if (this.pinned || !container || !frame) return this.fillMode ?? "viewport";
638
+ const measure = () => container.getBoundingClientRect().height;
639
+ const savedValue = frame.style.getPropertyValue("height");
640
+ const savedPriority = frame.style.getPropertyPriority("height");
641
+ frame.style.setProperty("height", "0px", "important");
642
+ const collapsed = measure();
643
+ frame.style.setProperty("height", `${FILL_PROBE_HEIGHT_PX}px`, "important");
644
+ const expanded = measure();
645
+ if (savedValue) frame.style.setProperty("height", savedValue, savedPriority);
646
+ else frame.style.removeProperty("height");
647
+ const tracksIframe = expanded - collapsed > FILL_PROBE_TRACK_EPSILON_PX;
648
+ const bounded = !tracksIframe && collapsed >= FILL_MIN_DEFINITE_HEIGHT_PX;
649
+ return bounded ? "container" : "viewport";
650
+ }
651
+ /**
652
+ * Size the iframe for the current fill verdict, clamped to `minHeight`. In
653
+ * container mode it fills 100% of the bounded block; in viewport mode its
654
+ * bottom edge meets the bottom of the viewport (`window.innerHeight - top`).
655
+ * No-op while pinned fullscreen (the pin fills the viewport itself).
546
656
  */
547
657
  applyFill() {
548
658
  if (!this.frame || this.pinned) return;
549
659
  const min = this.options.minHeight ?? DEFAULT_MIN_FILL_HEIGHT;
660
+ if (this.fillMode === "container" && this.containerEl) {
661
+ const target2 = Math.max(min, Math.round(this.containerEl.getBoundingClientRect().height));
662
+ this.setFrameHeight(`${target2}px`);
663
+ return;
664
+ }
550
665
  const top = this.frame.getBoundingClientRect().top;
551
666
  const target = Math.max(min, Math.round(window.innerHeight - top));
552
- this.frame.style.height = `${target}px`;
667
+ this.setFrameHeight(`${target}px`);
668
+ }
669
+ /** Attach/detach the container ResizeObserver to match the current verdict. */
670
+ syncContainerObserver() {
671
+ const want = this.fillMode === "container" && !!this.containerEl && typeof ResizeObserver !== "undefined";
672
+ if (want && !this.resizeObs) {
673
+ this.resizeObs = new ResizeObserver(() => this.scheduleFill());
674
+ this.resizeObs.observe(this.containerEl);
675
+ } else if (!want && this.resizeObs) {
676
+ this.resizeObs.disconnect();
677
+ this.resizeObs = null;
678
+ }
553
679
  }
554
680
  startFill() {
681
+ this.fillMode = this.detectFillMode();
682
+ this.syncContainerObserver();
555
683
  this.applyFill();
556
684
  if (this.fillListening) return;
557
685
  this.fillListening = true;
558
- window.addEventListener("resize", this.scheduleFill);
559
- window.addEventListener("orientationchange", this.scheduleFill);
686
+ window.addEventListener("resize", this.scheduleReprobe);
687
+ window.addEventListener("orientationchange", this.scheduleReprobe);
560
688
  window.addEventListener("scroll", this.scheduleFill, { passive: true });
561
689
  }
562
690
  stopFill() {
@@ -564,10 +692,18 @@ var EmbeddedDesigner = class {
564
692
  cancelAnimationFrame(this.fillRaf);
565
693
  this.fillRaf = null;
566
694
  }
695
+ if (this.reprobeRaf !== null) {
696
+ cancelAnimationFrame(this.reprobeRaf);
697
+ this.reprobeRaf = null;
698
+ }
699
+ if (this.resizeObs) {
700
+ this.resizeObs.disconnect();
701
+ this.resizeObs = null;
702
+ }
567
703
  if (!this.fillListening) return;
568
704
  this.fillListening = false;
569
- window.removeEventListener("resize", this.scheduleFill);
570
- window.removeEventListener("orientationchange", this.scheduleFill);
705
+ window.removeEventListener("resize", this.scheduleReprobe);
706
+ window.removeEventListener("orientationchange", this.scheduleReprobe);
571
707
  window.removeEventListener("scroll", this.scheduleFill);
572
708
  }
573
709
  /**
@@ -579,16 +715,22 @@ var EmbeddedDesigner = class {
579
715
  if (this.pinned || !this.frame) return;
580
716
  this.pinned = true;
581
717
  this.frameStyleBeforeFs = this.frame.getAttribute("style");
582
- Object.assign(this.frame.style, {
718
+ const pin = {
583
719
  position: "fixed",
584
- inset: "0",
720
+ top: "0",
721
+ right: "0",
722
+ bottom: "0",
723
+ left: "0",
585
724
  width: "100vw",
586
725
  height: "100vh",
587
726
  margin: "0",
588
727
  border: "0",
589
- zIndex: "2147483000",
728
+ "z-index": "2147483000",
590
729
  background: "#101625"
591
- });
730
+ };
731
+ for (const [property, value] of Object.entries(pin)) {
732
+ this.frame.style.setProperty(property, value, "important");
733
+ }
592
734
  const docEl = document.documentElement;
593
735
  this.docOverflowBeforeFs = docEl.style.overflow;
594
736
  docEl.style.overflow = "hidden";
@@ -609,7 +751,7 @@ var EmbeddedDesigner = class {
609
751
  if (this.frameStyleBeforeFs === null) this.frame.removeAttribute("style");
610
752
  else this.frame.setAttribute("style", this.frameStyleBeforeFs);
611
753
  if (this.fillEnabled()) this.applyFill();
612
- else if (this.autoResizeEnabled() && this.lastAutoHeight) this.frame.style.height = this.lastAutoHeight;
754
+ else if (this.autoResizeEnabled() && this.lastAutoHeight) this.setFrameHeight(this.lastAutoHeight);
613
755
  }
614
756
  this.frameStyleBeforeFs = null;
615
757
  if (this.docOverflowBeforeFs !== null) {
@@ -631,6 +773,48 @@ var EmbeddedDesigner = class {
631
773
  this.timeoutTimer = null;
632
774
  }
633
775
  }
776
+ /**
777
+ * Auto-renewal (proactive + one expiry recovery) is on when the host wired a
778
+ * relaunch hook and did not opt out. Without the hook there is nothing to call,
779
+ * so it is a no-op.
780
+ */
781
+ autoRenewEnabled() {
782
+ return !!this.options.onRequestRelaunch && this.options.autoRenewSession !== false;
783
+ }
784
+ clearRenewTimer() {
785
+ if (this.renewTimer !== null) {
786
+ clearTimeout(this.renewTimer);
787
+ this.renewTimer = null;
788
+ }
789
+ }
790
+ /**
791
+ * Arm the proactive renewal timer from a `ready` message's `expiresAt` (epoch
792
+ * ms). We relaunch a comfortable lead before expiry so the host can mint a fresh
793
+ * session and swap `designerUrl` without the user ever seeing the expiry card:
794
+ *
795
+ * - normal TTL (≥ 15 min): renew {@link RENEW_LEAD_MS} (~3 min) before expiry;
796
+ * - short TTL (< 15 min): renew after {@link RENEW_SHORT_TTL_FRACTION} (80%) of
797
+ * the remaining life, so the lead can't overshoot the whole session;
798
+ * - either way, never sooner than {@link RENEW_MIN_DELAY_MS} (30s) after `ready`
799
+ * so a burst of `ready` messages can't spin the host.
800
+ *
801
+ * Re-armed on every `ready`; cleared on destroy / setDesignerUrl (via re-mount).
802
+ * A no-op when auto-renewal is off or `expiresAt` is missing/already past — the
803
+ * expiry-error path recovers a session that has already lapsed.
804
+ */
805
+ scheduleRenewal(expiresAt) {
806
+ this.clearRenewTimer();
807
+ if (!this.autoRenewEnabled()) return;
808
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return;
809
+ const remaining = expiresAt - Date.now();
810
+ if (remaining <= 0) return;
811
+ const lead = remaining < RENEW_SHORT_TTL_MS ? remaining * RENEW_SHORT_TTL_FRACTION : remaining - RENEW_LEAD_MS;
812
+ const delay = Math.max(RENEW_MIN_DELAY_MS, lead);
813
+ this.renewTimer = setTimeout(() => {
814
+ this.renewTimer = null;
815
+ if (this.autoRenewEnabled()) this.options.onRequestRelaunch();
816
+ }, delay);
817
+ }
634
818
  ensureContainerPositioned(container) {
635
819
  const position = getComputedStyle(container).position;
636
820
  if (position === "static") {