@seatlayer/js 0.17.0 → 0.18.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 +23 -0
- package/dist/index.cjs +250 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +250 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -242,6 +242,25 @@ interface EmbeddedDesignerOptions {
|
|
|
242
242
|
style?: Partial<CSSStyleDeclaration>;
|
|
243
243
|
allow?: string;
|
|
244
244
|
referrerPolicy?: ReferrerPolicy;
|
|
245
|
+
/**
|
|
246
|
+
* Show the built-in branded loading skeleton and error/expiry card inside the
|
|
247
|
+
* container while the Designer boots. Defaults to `true`. Set `false` when the
|
|
248
|
+
* host renders its own loading and error chrome.
|
|
249
|
+
*/
|
|
250
|
+
showLoadingState?: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* If the Designer never posts `ready` within this many milliseconds, the host
|
|
253
|
+
* transitions to the error card with a timeout message. Defaults to `20000`.
|
|
254
|
+
* Only used when `showLoadingState` is enabled.
|
|
255
|
+
*/
|
|
256
|
+
loadingTimeoutMs?: number;
|
|
257
|
+
/**
|
|
258
|
+
* Called when the user presses "Try again" on the error card. Use it to mint a
|
|
259
|
+
* fresh Designer session and call `setDesignerUrl()` with the new URL, which
|
|
260
|
+
* recreates the iframe and returns to the loading state. When omitted, "Try
|
|
261
|
+
* again" reloads the current `designerUrl` in place.
|
|
262
|
+
*/
|
|
263
|
+
onRequestRelaunch?: () => void;
|
|
245
264
|
onReady?: (message: EmbeddedDesignerMessage) => void;
|
|
246
265
|
onSaved?: (message: EmbeddedDesignerMessage) => void;
|
|
247
266
|
onPublished?: (message: EmbeddedDesignerMessage) => void;
|
|
@@ -253,12 +272,30 @@ declare class EmbeddedDesigner {
|
|
|
253
272
|
private options;
|
|
254
273
|
private frame;
|
|
255
274
|
private designerOrigin;
|
|
275
|
+
private overlay;
|
|
276
|
+
private timeoutTimer;
|
|
277
|
+
private phase;
|
|
278
|
+
private restoreContainerPosition;
|
|
256
279
|
constructor(options: EmbeddedDesignerOptions);
|
|
257
280
|
mount(): HTMLIFrameElement;
|
|
258
281
|
/** Replace the iframe instead of assigning a new fragment to an existing one. */
|
|
259
282
|
setDesignerUrl(designerUrl: string): HTMLIFrameElement;
|
|
260
283
|
getIframe(): HTMLIFrameElement | null;
|
|
261
284
|
destroy(): void;
|
|
285
|
+
private loadingStateEnabled;
|
|
286
|
+
private clearTimeoutTimer;
|
|
287
|
+
private ensureContainerPositioned;
|
|
288
|
+
private restoreContainerStyle;
|
|
289
|
+
private removeOverlay;
|
|
290
|
+
private showError;
|
|
291
|
+
private handleTryAgain;
|
|
292
|
+
/**
|
|
293
|
+
* Build (or rebuild) the overlay for the given phase. A single overlay element
|
|
294
|
+
* is reused so we never stack stale skeletons or cards.
|
|
295
|
+
*/
|
|
296
|
+
private renderOverlay;
|
|
297
|
+
private buildSkeleton;
|
|
298
|
+
private buildErrorCard;
|
|
262
299
|
private handleMessage;
|
|
263
300
|
}
|
|
264
301
|
|
package/dist/index.d.ts
CHANGED
|
@@ -242,6 +242,25 @@ interface EmbeddedDesignerOptions {
|
|
|
242
242
|
style?: Partial<CSSStyleDeclaration>;
|
|
243
243
|
allow?: string;
|
|
244
244
|
referrerPolicy?: ReferrerPolicy;
|
|
245
|
+
/**
|
|
246
|
+
* Show the built-in branded loading skeleton and error/expiry card inside the
|
|
247
|
+
* container while the Designer boots. Defaults to `true`. Set `false` when the
|
|
248
|
+
* host renders its own loading and error chrome.
|
|
249
|
+
*/
|
|
250
|
+
showLoadingState?: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* If the Designer never posts `ready` within this many milliseconds, the host
|
|
253
|
+
* transitions to the error card with a timeout message. Defaults to `20000`.
|
|
254
|
+
* Only used when `showLoadingState` is enabled.
|
|
255
|
+
*/
|
|
256
|
+
loadingTimeoutMs?: number;
|
|
257
|
+
/**
|
|
258
|
+
* Called when the user presses "Try again" on the error card. Use it to mint a
|
|
259
|
+
* fresh Designer session and call `setDesignerUrl()` with the new URL, which
|
|
260
|
+
* recreates the iframe and returns to the loading state. When omitted, "Try
|
|
261
|
+
* again" reloads the current `designerUrl` in place.
|
|
262
|
+
*/
|
|
263
|
+
onRequestRelaunch?: () => void;
|
|
245
264
|
onReady?: (message: EmbeddedDesignerMessage) => void;
|
|
246
265
|
onSaved?: (message: EmbeddedDesignerMessage) => void;
|
|
247
266
|
onPublished?: (message: EmbeddedDesignerMessage) => void;
|
|
@@ -253,12 +272,30 @@ declare class EmbeddedDesigner {
|
|
|
253
272
|
private options;
|
|
254
273
|
private frame;
|
|
255
274
|
private designerOrigin;
|
|
275
|
+
private overlay;
|
|
276
|
+
private timeoutTimer;
|
|
277
|
+
private phase;
|
|
278
|
+
private restoreContainerPosition;
|
|
256
279
|
constructor(options: EmbeddedDesignerOptions);
|
|
257
280
|
mount(): HTMLIFrameElement;
|
|
258
281
|
/** Replace the iframe instead of assigning a new fragment to an existing one. */
|
|
259
282
|
setDesignerUrl(designerUrl: string): HTMLIFrameElement;
|
|
260
283
|
getIframe(): HTMLIFrameElement | null;
|
|
261
284
|
destroy(): void;
|
|
285
|
+
private loadingStateEnabled;
|
|
286
|
+
private clearTimeoutTimer;
|
|
287
|
+
private ensureContainerPositioned;
|
|
288
|
+
private restoreContainerStyle;
|
|
289
|
+
private removeOverlay;
|
|
290
|
+
private showError;
|
|
291
|
+
private handleTryAgain;
|
|
292
|
+
/**
|
|
293
|
+
* Build (or rebuild) the overlay for the given phase. A single overlay element
|
|
294
|
+
* is reused so we never stack stale skeletons or cards.
|
|
295
|
+
*/
|
|
296
|
+
private renderOverlay;
|
|
297
|
+
private buildSkeleton;
|
|
298
|
+
private buildErrorCard;
|
|
262
299
|
private handleMessage;
|
|
263
300
|
}
|
|
264
301
|
|
package/dist/index.js
CHANGED
|
@@ -326,16 +326,46 @@ var TYPES = /* @__PURE__ */ new Set([
|
|
|
326
326
|
"seatlayer.designer.close",
|
|
327
327
|
"seatlayer.designer.error"
|
|
328
328
|
]);
|
|
329
|
+
var DEFAULT_LOADING_TIMEOUT_MS = 2e4;
|
|
329
330
|
function resolveContainer2(container) {
|
|
330
331
|
if (typeof container !== "string") return container;
|
|
331
332
|
const element = document.querySelector(container);
|
|
332
333
|
if (!element) throw new Error(`EmbeddedDesigner container not found: ${container}`);
|
|
333
334
|
return element;
|
|
334
335
|
}
|
|
336
|
+
function causeFromCode(code) {
|
|
337
|
+
const value = (code ?? "").toLowerCase();
|
|
338
|
+
if (value.includes("expire") || value.includes("revoke") || value === "401") return "expired";
|
|
339
|
+
if (value.includes("mismatch")) return "mismatch";
|
|
340
|
+
if (value.includes("timeout")) return "timeout";
|
|
341
|
+
return "load";
|
|
342
|
+
}
|
|
343
|
+
var ERROR_COPY = {
|
|
344
|
+
expired: {
|
|
345
|
+
title: "This design session expired",
|
|
346
|
+
body: "For your security, editing sessions are short-lived. Start a fresh one to keep designing."
|
|
347
|
+
},
|
|
348
|
+
mismatch: {
|
|
349
|
+
title: "This editor doesn't match this chart",
|
|
350
|
+
body: "The session that loaded belongs to a different chart or workspace. Reopen the designer to continue."
|
|
351
|
+
},
|
|
352
|
+
timeout: {
|
|
353
|
+
title: "The designer is taking too long",
|
|
354
|
+
body: "It did not finish loading in time. This is usually a slow connection \u2014 try again."
|
|
355
|
+
},
|
|
356
|
+
load: {
|
|
357
|
+
title: "We couldn't load the designer",
|
|
358
|
+
body: "Something went wrong while opening the editor. Please try again."
|
|
359
|
+
}
|
|
360
|
+
};
|
|
335
361
|
var EmbeddedDesigner = class {
|
|
336
362
|
constructor(options) {
|
|
337
363
|
this.frame = null;
|
|
338
364
|
this.designerOrigin = "";
|
|
365
|
+
this.overlay = null;
|
|
366
|
+
this.timeoutTimer = null;
|
|
367
|
+
this.phase = "loading";
|
|
368
|
+
this.restoreContainerPosition = null;
|
|
339
369
|
this.handleMessage = (event) => {
|
|
340
370
|
if (!this.frame || event.origin !== this.designerOrigin || event.source !== this.frame.contentWindow) return;
|
|
341
371
|
if (!event.data || typeof event.data !== "object") return;
|
|
@@ -350,10 +380,15 @@ var EmbeddedDesigner = class {
|
|
|
350
380
|
message: typeof data.message === "string" ? data.message : void 0,
|
|
351
381
|
meta: data.meta
|
|
352
382
|
};
|
|
353
|
-
if (this.options.expectedChartId && message.chartId && message.chartId !== this.options.expectedChartId)
|
|
354
|
-
|
|
383
|
+
if (this.options.expectedChartId && message.chartId && message.chartId !== this.options.expectedChartId || this.options.expectedWorkspaceId && message.workspaceId && message.workspaceId !== this.options.expectedWorkspaceId) {
|
|
384
|
+
this.showError("mismatch");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
355
387
|
switch (message.type) {
|
|
356
388
|
case "seatlayer.designer.ready":
|
|
389
|
+
this.phase = "ready";
|
|
390
|
+
this.clearTimeoutTimer();
|
|
391
|
+
this.removeOverlay();
|
|
357
392
|
this.options.onReady?.(message);
|
|
358
393
|
break;
|
|
359
394
|
case "seatlayer.designer.saved":
|
|
@@ -366,6 +401,7 @@ var EmbeddedDesigner = class {
|
|
|
366
401
|
this.options.onClose?.(message);
|
|
367
402
|
break;
|
|
368
403
|
case "seatlayer.designer.error":
|
|
404
|
+
this.showError(causeFromCode(message.code));
|
|
369
405
|
this.options.onError?.(message);
|
|
370
406
|
break;
|
|
371
407
|
}
|
|
@@ -389,9 +425,21 @@ var EmbeddedDesigner = class {
|
|
|
389
425
|
frame.style.border = "0";
|
|
390
426
|
Object.assign(frame.style, this.options.style);
|
|
391
427
|
if (this.options.className) frame.className = this.options.className;
|
|
428
|
+
const container = resolveContainer2(this.options.container);
|
|
392
429
|
window.addEventListener("message", this.handleMessage);
|
|
393
|
-
|
|
430
|
+
container.append(frame);
|
|
394
431
|
this.frame = frame;
|
|
432
|
+
this.phase = "loading";
|
|
433
|
+
if (this.loadingStateEnabled()) {
|
|
434
|
+
this.ensureContainerPositioned(container);
|
|
435
|
+
this.renderOverlay(container, "loading");
|
|
436
|
+
const timeout = this.options.loadingTimeoutMs ?? DEFAULT_LOADING_TIMEOUT_MS;
|
|
437
|
+
if (timeout > 0 && Number.isFinite(timeout)) {
|
|
438
|
+
this.timeoutTimer = setTimeout(() => {
|
|
439
|
+
if (this.phase === "loading") this.showError("timeout");
|
|
440
|
+
}, timeout);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
395
443
|
return frame;
|
|
396
444
|
}
|
|
397
445
|
/** Replace the iframe instead of assigning a new fragment to an existing one. */
|
|
@@ -404,9 +452,208 @@ var EmbeddedDesigner = class {
|
|
|
404
452
|
}
|
|
405
453
|
destroy() {
|
|
406
454
|
window.removeEventListener("message", this.handleMessage);
|
|
455
|
+
this.clearTimeoutTimer();
|
|
456
|
+
this.removeOverlay();
|
|
457
|
+
this.restoreContainerStyle();
|
|
407
458
|
this.frame?.remove();
|
|
408
459
|
this.frame = null;
|
|
409
460
|
this.designerOrigin = "";
|
|
461
|
+
this.phase = "loading";
|
|
462
|
+
}
|
|
463
|
+
loadingStateEnabled() {
|
|
464
|
+
return this.options.showLoadingState !== false;
|
|
465
|
+
}
|
|
466
|
+
clearTimeoutTimer() {
|
|
467
|
+
if (this.timeoutTimer !== null) {
|
|
468
|
+
clearTimeout(this.timeoutTimer);
|
|
469
|
+
this.timeoutTimer = null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
ensureContainerPositioned(container) {
|
|
473
|
+
const position = getComputedStyle(container).position;
|
|
474
|
+
if (position === "static") {
|
|
475
|
+
this.restoreContainerPosition = container.style.position;
|
|
476
|
+
container.style.position = "relative";
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
restoreContainerStyle() {
|
|
480
|
+
if (this.restoreContainerPosition === null) return;
|
|
481
|
+
try {
|
|
482
|
+
resolveContainer2(this.options.container).style.position = this.restoreContainerPosition;
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
this.restoreContainerPosition = null;
|
|
486
|
+
}
|
|
487
|
+
removeOverlay() {
|
|
488
|
+
this.overlay?.remove();
|
|
489
|
+
this.overlay = null;
|
|
490
|
+
}
|
|
491
|
+
showError(cause) {
|
|
492
|
+
this.phase = "error";
|
|
493
|
+
this.clearTimeoutTimer();
|
|
494
|
+
if (!this.loadingStateEnabled()) return;
|
|
495
|
+
let container;
|
|
496
|
+
try {
|
|
497
|
+
container = resolveContainer2(this.options.container);
|
|
498
|
+
} catch {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
this.renderOverlay(container, "error", cause);
|
|
502
|
+
}
|
|
503
|
+
handleTryAgain() {
|
|
504
|
+
if (this.options.onRequestRelaunch) {
|
|
505
|
+
this.options.onRequestRelaunch();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
this.mount();
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Build (or rebuild) the overlay for the given phase. A single overlay element
|
|
512
|
+
* is reused so we never stack stale skeletons or cards.
|
|
513
|
+
*/
|
|
514
|
+
renderOverlay(container, phase, cause) {
|
|
515
|
+
this.removeOverlay();
|
|
516
|
+
const overlay = document.createElement("div");
|
|
517
|
+
overlay.setAttribute("data-seatlayer-designer-overlay", phase);
|
|
518
|
+
overlay.setAttribute("role", phase === "error" ? "alert" : "status");
|
|
519
|
+
overlay.setAttribute("aria-live", "polite");
|
|
520
|
+
Object.assign(overlay.style, {
|
|
521
|
+
position: "absolute",
|
|
522
|
+
inset: "0",
|
|
523
|
+
display: "flex",
|
|
524
|
+
alignItems: "center",
|
|
525
|
+
justifyContent: "center",
|
|
526
|
+
background: "#101625",
|
|
527
|
+
color: "#e6ebf5",
|
|
528
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
529
|
+
zIndex: "2",
|
|
530
|
+
overflow: "hidden"
|
|
531
|
+
});
|
|
532
|
+
if (phase === "loading") this.buildSkeleton(overlay);
|
|
533
|
+
else this.buildErrorCard(overlay, cause ?? "load");
|
|
534
|
+
container.append(overlay);
|
|
535
|
+
this.overlay = overlay;
|
|
536
|
+
}
|
|
537
|
+
buildSkeleton(overlay) {
|
|
538
|
+
const style = document.createElement("style");
|
|
539
|
+
style.textContent = `
|
|
540
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
541
|
+
@keyframes seatlayer-designer-shimmer {
|
|
542
|
+
0% { background-position: -320px 0; }
|
|
543
|
+
100% { background-position: 320px 0; }
|
|
544
|
+
}
|
|
545
|
+
[data-seatlayer-designer-overlay="loading"] .sl-shimmer {
|
|
546
|
+
animation: seatlayer-designer-shimmer 1.25s ease-in-out infinite;
|
|
547
|
+
background-size: 640px 100%;
|
|
548
|
+
}
|
|
549
|
+
}`;
|
|
550
|
+
overlay.append(style);
|
|
551
|
+
const shimmer = "linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.10) 37%, rgba(255,255,255,0.04) 63%)";
|
|
552
|
+
const scaffold = document.createElement("div");
|
|
553
|
+
Object.assign(scaffold.style, {
|
|
554
|
+
position: "absolute",
|
|
555
|
+
inset: "0",
|
|
556
|
+
display: "flex",
|
|
557
|
+
flexDirection: "column",
|
|
558
|
+
padding: "16px",
|
|
559
|
+
gap: "14px",
|
|
560
|
+
opacity: "0.9"
|
|
561
|
+
});
|
|
562
|
+
const bar = (styles) => {
|
|
563
|
+
const node = document.createElement("div");
|
|
564
|
+
node.className = "sl-shimmer";
|
|
565
|
+
Object.assign(node.style, {
|
|
566
|
+
background: shimmer,
|
|
567
|
+
borderRadius: "8px"
|
|
568
|
+
});
|
|
569
|
+
Object.assign(node.style, styles);
|
|
570
|
+
return node;
|
|
571
|
+
};
|
|
572
|
+
scaffold.append(bar({ height: "40px", width: "100%", flex: "0 0 auto" }));
|
|
573
|
+
const body = document.createElement("div");
|
|
574
|
+
Object.assign(body.style, {
|
|
575
|
+
display: "flex",
|
|
576
|
+
gap: "14px",
|
|
577
|
+
flex: "1 1 auto",
|
|
578
|
+
minHeight: "0"
|
|
579
|
+
});
|
|
580
|
+
body.append(bar({ width: "220px", height: "100%", flex: "0 0 auto" }));
|
|
581
|
+
body.append(bar({ flex: "1 1 auto", height: "100%" }));
|
|
582
|
+
scaffold.append(body);
|
|
583
|
+
overlay.append(scaffold);
|
|
584
|
+
const caption = document.createElement("div");
|
|
585
|
+
Object.assign(caption.style, {
|
|
586
|
+
position: "relative",
|
|
587
|
+
zIndex: "1",
|
|
588
|
+
display: "flex",
|
|
589
|
+
alignItems: "center",
|
|
590
|
+
gap: "10px",
|
|
591
|
+
padding: "10px 16px",
|
|
592
|
+
borderRadius: "999px",
|
|
593
|
+
background: "rgba(16, 22, 37, 0.72)",
|
|
594
|
+
fontSize: "13px",
|
|
595
|
+
fontWeight: "500",
|
|
596
|
+
letterSpacing: "0.01em"
|
|
597
|
+
});
|
|
598
|
+
const dot = document.createElement("span");
|
|
599
|
+
dot.className = "sl-shimmer";
|
|
600
|
+
Object.assign(dot.style, {
|
|
601
|
+
width: "9px",
|
|
602
|
+
height: "9px",
|
|
603
|
+
borderRadius: "50%",
|
|
604
|
+
background: shimmer,
|
|
605
|
+
flex: "0 0 auto"
|
|
606
|
+
});
|
|
607
|
+
caption.append(dot);
|
|
608
|
+
caption.append(document.createTextNode("Loading designer\u2026"));
|
|
609
|
+
overlay.append(caption);
|
|
610
|
+
}
|
|
611
|
+
buildErrorCard(overlay, cause) {
|
|
612
|
+
const copy = ERROR_COPY[cause];
|
|
613
|
+
const card = document.createElement("div");
|
|
614
|
+
Object.assign(card.style, {
|
|
615
|
+
maxWidth: "420px",
|
|
616
|
+
margin: "0 24px",
|
|
617
|
+
padding: "28px",
|
|
618
|
+
textAlign: "center",
|
|
619
|
+
background: "rgba(255, 255, 255, 0.03)",
|
|
620
|
+
border: "1px solid rgba(255, 255, 255, 0.08)",
|
|
621
|
+
borderRadius: "16px",
|
|
622
|
+
boxShadow: "0 12px 40px rgba(0, 0, 0, 0.35)"
|
|
623
|
+
});
|
|
624
|
+
const heading = document.createElement("h2");
|
|
625
|
+
heading.textContent = copy.title;
|
|
626
|
+
Object.assign(heading.style, {
|
|
627
|
+
margin: "0 0 8px",
|
|
628
|
+
fontSize: "17px",
|
|
629
|
+
fontWeight: "600",
|
|
630
|
+
color: "#f4f7ff"
|
|
631
|
+
});
|
|
632
|
+
const body = document.createElement("p");
|
|
633
|
+
body.textContent = copy.body;
|
|
634
|
+
Object.assign(body.style, {
|
|
635
|
+
margin: "0 0 20px",
|
|
636
|
+
fontSize: "13.5px",
|
|
637
|
+
lineHeight: "1.5",
|
|
638
|
+
color: "#aab4c8"
|
|
639
|
+
});
|
|
640
|
+
const button = document.createElement("button");
|
|
641
|
+
button.type = "button";
|
|
642
|
+
button.textContent = "Try again";
|
|
643
|
+
Object.assign(button.style, {
|
|
644
|
+
appearance: "none",
|
|
645
|
+
cursor: "pointer",
|
|
646
|
+
border: "0",
|
|
647
|
+
borderRadius: "10px",
|
|
648
|
+
padding: "10px 22px",
|
|
649
|
+
fontSize: "14px",
|
|
650
|
+
fontWeight: "600",
|
|
651
|
+
color: "#101625",
|
|
652
|
+
background: "#7aa2ff"
|
|
653
|
+
});
|
|
654
|
+
button.addEventListener("click", () => this.handleTryAgain());
|
|
655
|
+
card.append(heading, body, button);
|
|
656
|
+
overlay.append(card);
|
|
410
657
|
}
|
|
411
658
|
};
|
|
412
659
|
|