@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/README.md
CHANGED
|
@@ -87,6 +87,11 @@ const designer = new EmbeddedDesigner({
|
|
|
87
87
|
onPublished: ({ chartId }) => refreshVenue(chartId),
|
|
88
88
|
onClose: () => closeVenueEditor(),
|
|
89
89
|
onError: ({ code, message }) => showError(code ?? message),
|
|
90
|
+
// Mint a fresh session when the user retries an expired/failed editor:
|
|
91
|
+
onRequestRelaunch: async () => {
|
|
92
|
+
const next = await mintDesignerSession(session.chartId);
|
|
93
|
+
designer.setDesignerUrl(next.designerUrl);
|
|
94
|
+
},
|
|
90
95
|
});
|
|
91
96
|
designer.mount();
|
|
92
97
|
|
|
@@ -99,6 +104,24 @@ designer.destroy();
|
|
|
99
104
|
Give the container a height, for example `min-height: 760px`. Keep the default
|
|
100
105
|
`referrerPolicy: 'origin'`; the Designer uses it to verify the parent origin.
|
|
101
106
|
|
|
107
|
+
### Built-in loading, error, and expiry states
|
|
108
|
+
|
|
109
|
+
By default the host paints a lightweight, branded skeleton inside the container
|
|
110
|
+
while the Designer boots, then removes it the moment the iframe reports `ready`.
|
|
111
|
+
If the session expires, the identity check fails, the iframe reports an error, or
|
|
112
|
+
it never becomes ready, the host swaps in a dark **"Try again"** card with copy
|
|
113
|
+
matched to the cause. The skeleton respects `prefers-reduced-motion` and adds no
|
|
114
|
+
CSS files or external assets.
|
|
115
|
+
|
|
116
|
+
| Option | Type | Default | What it does |
|
|
117
|
+
| --- | --- | --- | --- |
|
|
118
|
+
| `showLoadingState` | `boolean` | `true` | Render the built-in skeleton and error card. Set `false` when you draw your own chrome. |
|
|
119
|
+
| `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. |
|
|
121
|
+
|
|
122
|
+
`setDesignerUrl()` always returns the host to the loading state, so a relaunch
|
|
123
|
+
flow needs no extra bookkeeping.
|
|
124
|
+
|
|
102
125
|
## API
|
|
103
126
|
|
|
104
127
|
`new SeatingChart(options)` — options: `container` (selector or element, required),
|
package/dist/index.cjs
CHANGED
|
@@ -358,16 +358,46 @@ var TYPES = /* @__PURE__ */ new Set([
|
|
|
358
358
|
"seatlayer.designer.close",
|
|
359
359
|
"seatlayer.designer.error"
|
|
360
360
|
]);
|
|
361
|
+
var DEFAULT_LOADING_TIMEOUT_MS = 2e4;
|
|
361
362
|
function resolveContainer2(container) {
|
|
362
363
|
if (typeof container !== "string") return container;
|
|
363
364
|
const element = document.querySelector(container);
|
|
364
365
|
if (!element) throw new Error(`EmbeddedDesigner container not found: ${container}`);
|
|
365
366
|
return element;
|
|
366
367
|
}
|
|
368
|
+
function causeFromCode(code) {
|
|
369
|
+
const value = (code ?? "").toLowerCase();
|
|
370
|
+
if (value.includes("expire") || value.includes("revoke") || value === "401") return "expired";
|
|
371
|
+
if (value.includes("mismatch")) return "mismatch";
|
|
372
|
+
if (value.includes("timeout")) return "timeout";
|
|
373
|
+
return "load";
|
|
374
|
+
}
|
|
375
|
+
var ERROR_COPY = {
|
|
376
|
+
expired: {
|
|
377
|
+
title: "This design session expired",
|
|
378
|
+
body: "For your security, editing sessions are short-lived. Start a fresh one to keep designing."
|
|
379
|
+
},
|
|
380
|
+
mismatch: {
|
|
381
|
+
title: "This editor doesn't match this chart",
|
|
382
|
+
body: "The session that loaded belongs to a different chart or workspace. Reopen the designer to continue."
|
|
383
|
+
},
|
|
384
|
+
timeout: {
|
|
385
|
+
title: "The designer is taking too long",
|
|
386
|
+
body: "It did not finish loading in time. This is usually a slow connection \u2014 try again."
|
|
387
|
+
},
|
|
388
|
+
load: {
|
|
389
|
+
title: "We couldn't load the designer",
|
|
390
|
+
body: "Something went wrong while opening the editor. Please try again."
|
|
391
|
+
}
|
|
392
|
+
};
|
|
367
393
|
var EmbeddedDesigner = class {
|
|
368
394
|
constructor(options) {
|
|
369
395
|
this.frame = null;
|
|
370
396
|
this.designerOrigin = "";
|
|
397
|
+
this.overlay = null;
|
|
398
|
+
this.timeoutTimer = null;
|
|
399
|
+
this.phase = "loading";
|
|
400
|
+
this.restoreContainerPosition = null;
|
|
371
401
|
this.handleMessage = (event) => {
|
|
372
402
|
if (!this.frame || event.origin !== this.designerOrigin || event.source !== this.frame.contentWindow) return;
|
|
373
403
|
if (!event.data || typeof event.data !== "object") return;
|
|
@@ -382,10 +412,15 @@ var EmbeddedDesigner = class {
|
|
|
382
412
|
message: typeof data.message === "string" ? data.message : void 0,
|
|
383
413
|
meta: data.meta
|
|
384
414
|
};
|
|
385
|
-
if (this.options.expectedChartId && message.chartId && message.chartId !== this.options.expectedChartId)
|
|
386
|
-
|
|
415
|
+
if (this.options.expectedChartId && message.chartId && message.chartId !== this.options.expectedChartId || this.options.expectedWorkspaceId && message.workspaceId && message.workspaceId !== this.options.expectedWorkspaceId) {
|
|
416
|
+
this.showError("mismatch");
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
387
419
|
switch (message.type) {
|
|
388
420
|
case "seatlayer.designer.ready":
|
|
421
|
+
this.phase = "ready";
|
|
422
|
+
this.clearTimeoutTimer();
|
|
423
|
+
this.removeOverlay();
|
|
389
424
|
this.options.onReady?.(message);
|
|
390
425
|
break;
|
|
391
426
|
case "seatlayer.designer.saved":
|
|
@@ -398,6 +433,7 @@ var EmbeddedDesigner = class {
|
|
|
398
433
|
this.options.onClose?.(message);
|
|
399
434
|
break;
|
|
400
435
|
case "seatlayer.designer.error":
|
|
436
|
+
this.showError(causeFromCode(message.code));
|
|
401
437
|
this.options.onError?.(message);
|
|
402
438
|
break;
|
|
403
439
|
}
|
|
@@ -421,9 +457,21 @@ var EmbeddedDesigner = class {
|
|
|
421
457
|
frame.style.border = "0";
|
|
422
458
|
Object.assign(frame.style, this.options.style);
|
|
423
459
|
if (this.options.className) frame.className = this.options.className;
|
|
460
|
+
const container = resolveContainer2(this.options.container);
|
|
424
461
|
window.addEventListener("message", this.handleMessage);
|
|
425
|
-
|
|
462
|
+
container.append(frame);
|
|
426
463
|
this.frame = frame;
|
|
464
|
+
this.phase = "loading";
|
|
465
|
+
if (this.loadingStateEnabled()) {
|
|
466
|
+
this.ensureContainerPositioned(container);
|
|
467
|
+
this.renderOverlay(container, "loading");
|
|
468
|
+
const timeout = this.options.loadingTimeoutMs ?? DEFAULT_LOADING_TIMEOUT_MS;
|
|
469
|
+
if (timeout > 0 && Number.isFinite(timeout)) {
|
|
470
|
+
this.timeoutTimer = setTimeout(() => {
|
|
471
|
+
if (this.phase === "loading") this.showError("timeout");
|
|
472
|
+
}, timeout);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
427
475
|
return frame;
|
|
428
476
|
}
|
|
429
477
|
/** Replace the iframe instead of assigning a new fragment to an existing one. */
|
|
@@ -436,9 +484,208 @@ var EmbeddedDesigner = class {
|
|
|
436
484
|
}
|
|
437
485
|
destroy() {
|
|
438
486
|
window.removeEventListener("message", this.handleMessage);
|
|
487
|
+
this.clearTimeoutTimer();
|
|
488
|
+
this.removeOverlay();
|
|
489
|
+
this.restoreContainerStyle();
|
|
439
490
|
this.frame?.remove();
|
|
440
491
|
this.frame = null;
|
|
441
492
|
this.designerOrigin = "";
|
|
493
|
+
this.phase = "loading";
|
|
494
|
+
}
|
|
495
|
+
loadingStateEnabled() {
|
|
496
|
+
return this.options.showLoadingState !== false;
|
|
497
|
+
}
|
|
498
|
+
clearTimeoutTimer() {
|
|
499
|
+
if (this.timeoutTimer !== null) {
|
|
500
|
+
clearTimeout(this.timeoutTimer);
|
|
501
|
+
this.timeoutTimer = null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
ensureContainerPositioned(container) {
|
|
505
|
+
const position = getComputedStyle(container).position;
|
|
506
|
+
if (position === "static") {
|
|
507
|
+
this.restoreContainerPosition = container.style.position;
|
|
508
|
+
container.style.position = "relative";
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
restoreContainerStyle() {
|
|
512
|
+
if (this.restoreContainerPosition === null) return;
|
|
513
|
+
try {
|
|
514
|
+
resolveContainer2(this.options.container).style.position = this.restoreContainerPosition;
|
|
515
|
+
} catch {
|
|
516
|
+
}
|
|
517
|
+
this.restoreContainerPosition = null;
|
|
518
|
+
}
|
|
519
|
+
removeOverlay() {
|
|
520
|
+
this.overlay?.remove();
|
|
521
|
+
this.overlay = null;
|
|
522
|
+
}
|
|
523
|
+
showError(cause) {
|
|
524
|
+
this.phase = "error";
|
|
525
|
+
this.clearTimeoutTimer();
|
|
526
|
+
if (!this.loadingStateEnabled()) return;
|
|
527
|
+
let container;
|
|
528
|
+
try {
|
|
529
|
+
container = resolveContainer2(this.options.container);
|
|
530
|
+
} catch {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
this.renderOverlay(container, "error", cause);
|
|
534
|
+
}
|
|
535
|
+
handleTryAgain() {
|
|
536
|
+
if (this.options.onRequestRelaunch) {
|
|
537
|
+
this.options.onRequestRelaunch();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.mount();
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Build (or rebuild) the overlay for the given phase. A single overlay element
|
|
544
|
+
* is reused so we never stack stale skeletons or cards.
|
|
545
|
+
*/
|
|
546
|
+
renderOverlay(container, phase, cause) {
|
|
547
|
+
this.removeOverlay();
|
|
548
|
+
const overlay = document.createElement("div");
|
|
549
|
+
overlay.setAttribute("data-seatlayer-designer-overlay", phase);
|
|
550
|
+
overlay.setAttribute("role", phase === "error" ? "alert" : "status");
|
|
551
|
+
overlay.setAttribute("aria-live", "polite");
|
|
552
|
+
Object.assign(overlay.style, {
|
|
553
|
+
position: "absolute",
|
|
554
|
+
inset: "0",
|
|
555
|
+
display: "flex",
|
|
556
|
+
alignItems: "center",
|
|
557
|
+
justifyContent: "center",
|
|
558
|
+
background: "#101625",
|
|
559
|
+
color: "#e6ebf5",
|
|
560
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
561
|
+
zIndex: "2",
|
|
562
|
+
overflow: "hidden"
|
|
563
|
+
});
|
|
564
|
+
if (phase === "loading") this.buildSkeleton(overlay);
|
|
565
|
+
else this.buildErrorCard(overlay, cause ?? "load");
|
|
566
|
+
container.append(overlay);
|
|
567
|
+
this.overlay = overlay;
|
|
568
|
+
}
|
|
569
|
+
buildSkeleton(overlay) {
|
|
570
|
+
const style = document.createElement("style");
|
|
571
|
+
style.textContent = `
|
|
572
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
573
|
+
@keyframes seatlayer-designer-shimmer {
|
|
574
|
+
0% { background-position: -320px 0; }
|
|
575
|
+
100% { background-position: 320px 0; }
|
|
576
|
+
}
|
|
577
|
+
[data-seatlayer-designer-overlay="loading"] .sl-shimmer {
|
|
578
|
+
animation: seatlayer-designer-shimmer 1.25s ease-in-out infinite;
|
|
579
|
+
background-size: 640px 100%;
|
|
580
|
+
}
|
|
581
|
+
}`;
|
|
582
|
+
overlay.append(style);
|
|
583
|
+
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%)";
|
|
584
|
+
const scaffold = document.createElement("div");
|
|
585
|
+
Object.assign(scaffold.style, {
|
|
586
|
+
position: "absolute",
|
|
587
|
+
inset: "0",
|
|
588
|
+
display: "flex",
|
|
589
|
+
flexDirection: "column",
|
|
590
|
+
padding: "16px",
|
|
591
|
+
gap: "14px",
|
|
592
|
+
opacity: "0.9"
|
|
593
|
+
});
|
|
594
|
+
const bar = (styles) => {
|
|
595
|
+
const node = document.createElement("div");
|
|
596
|
+
node.className = "sl-shimmer";
|
|
597
|
+
Object.assign(node.style, {
|
|
598
|
+
background: shimmer,
|
|
599
|
+
borderRadius: "8px"
|
|
600
|
+
});
|
|
601
|
+
Object.assign(node.style, styles);
|
|
602
|
+
return node;
|
|
603
|
+
};
|
|
604
|
+
scaffold.append(bar({ height: "40px", width: "100%", flex: "0 0 auto" }));
|
|
605
|
+
const body = document.createElement("div");
|
|
606
|
+
Object.assign(body.style, {
|
|
607
|
+
display: "flex",
|
|
608
|
+
gap: "14px",
|
|
609
|
+
flex: "1 1 auto",
|
|
610
|
+
minHeight: "0"
|
|
611
|
+
});
|
|
612
|
+
body.append(bar({ width: "220px", height: "100%", flex: "0 0 auto" }));
|
|
613
|
+
body.append(bar({ flex: "1 1 auto", height: "100%" }));
|
|
614
|
+
scaffold.append(body);
|
|
615
|
+
overlay.append(scaffold);
|
|
616
|
+
const caption = document.createElement("div");
|
|
617
|
+
Object.assign(caption.style, {
|
|
618
|
+
position: "relative",
|
|
619
|
+
zIndex: "1",
|
|
620
|
+
display: "flex",
|
|
621
|
+
alignItems: "center",
|
|
622
|
+
gap: "10px",
|
|
623
|
+
padding: "10px 16px",
|
|
624
|
+
borderRadius: "999px",
|
|
625
|
+
background: "rgba(16, 22, 37, 0.72)",
|
|
626
|
+
fontSize: "13px",
|
|
627
|
+
fontWeight: "500",
|
|
628
|
+
letterSpacing: "0.01em"
|
|
629
|
+
});
|
|
630
|
+
const dot = document.createElement("span");
|
|
631
|
+
dot.className = "sl-shimmer";
|
|
632
|
+
Object.assign(dot.style, {
|
|
633
|
+
width: "9px",
|
|
634
|
+
height: "9px",
|
|
635
|
+
borderRadius: "50%",
|
|
636
|
+
background: shimmer,
|
|
637
|
+
flex: "0 0 auto"
|
|
638
|
+
});
|
|
639
|
+
caption.append(dot);
|
|
640
|
+
caption.append(document.createTextNode("Loading designer\u2026"));
|
|
641
|
+
overlay.append(caption);
|
|
642
|
+
}
|
|
643
|
+
buildErrorCard(overlay, cause) {
|
|
644
|
+
const copy = ERROR_COPY[cause];
|
|
645
|
+
const card = document.createElement("div");
|
|
646
|
+
Object.assign(card.style, {
|
|
647
|
+
maxWidth: "420px",
|
|
648
|
+
margin: "0 24px",
|
|
649
|
+
padding: "28px",
|
|
650
|
+
textAlign: "center",
|
|
651
|
+
background: "rgba(255, 255, 255, 0.03)",
|
|
652
|
+
border: "1px solid rgba(255, 255, 255, 0.08)",
|
|
653
|
+
borderRadius: "16px",
|
|
654
|
+
boxShadow: "0 12px 40px rgba(0, 0, 0, 0.35)"
|
|
655
|
+
});
|
|
656
|
+
const heading = document.createElement("h2");
|
|
657
|
+
heading.textContent = copy.title;
|
|
658
|
+
Object.assign(heading.style, {
|
|
659
|
+
margin: "0 0 8px",
|
|
660
|
+
fontSize: "17px",
|
|
661
|
+
fontWeight: "600",
|
|
662
|
+
color: "#f4f7ff"
|
|
663
|
+
});
|
|
664
|
+
const body = document.createElement("p");
|
|
665
|
+
body.textContent = copy.body;
|
|
666
|
+
Object.assign(body.style, {
|
|
667
|
+
margin: "0 0 20px",
|
|
668
|
+
fontSize: "13.5px",
|
|
669
|
+
lineHeight: "1.5",
|
|
670
|
+
color: "#aab4c8"
|
|
671
|
+
});
|
|
672
|
+
const button = document.createElement("button");
|
|
673
|
+
button.type = "button";
|
|
674
|
+
button.textContent = "Try again";
|
|
675
|
+
Object.assign(button.style, {
|
|
676
|
+
appearance: "none",
|
|
677
|
+
cursor: "pointer",
|
|
678
|
+
border: "0",
|
|
679
|
+
borderRadius: "10px",
|
|
680
|
+
padding: "10px 22px",
|
|
681
|
+
fontSize: "14px",
|
|
682
|
+
fontWeight: "600",
|
|
683
|
+
color: "#101625",
|
|
684
|
+
background: "#7aa2ff"
|
|
685
|
+
});
|
|
686
|
+
button.addEventListener("click", () => this.handleTryAgain());
|
|
687
|
+
card.append(heading, body, button);
|
|
688
|
+
overlay.append(card);
|
|
442
689
|
}
|
|
443
690
|
};
|
|
444
691
|
|