@seatlayer/js 0.21.0 → 0.22.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.cjs +65 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +65 -5
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.d.cts
CHANGED
|
@@ -254,10 +254,27 @@ interface EmbeddedDesignerOptions {
|
|
|
254
254
|
* Only used when `showLoadingState` is enabled.
|
|
255
255
|
*/
|
|
256
256
|
loadingTimeoutMs?: number;
|
|
257
|
+
/**
|
|
258
|
+
* How to size the iframe's height. The Designer is a full application (its
|
|
259
|
+
* shell is `position:fixed; height:100dvh`), not flowing content, so it should
|
|
260
|
+
* fill the viewport rather than be measured.
|
|
261
|
+
*
|
|
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.
|
|
267
|
+
* - a number: a fixed pixel height. In this mode the legacy resize message is
|
|
268
|
+
* still honoured (unless `autoResize` is `false`) so older hosts keep growing.
|
|
269
|
+
*/
|
|
270
|
+
height?: 'fill' | number;
|
|
271
|
+
/** Minimum height (px) that `'fill'` mode clamps to. Defaults to `480`. */
|
|
272
|
+
minHeight?: number;
|
|
257
273
|
/**
|
|
258
274
|
* Auto-grow the iframe to the height the Designer reports over the resize
|
|
259
|
-
* protocol (`seatlayer.designer.resize`).
|
|
260
|
-
*
|
|
275
|
+
* protocol (`seatlayer.designer.resize`). Only applies when `height` is a fixed
|
|
276
|
+
* number; ignored in `'fill'` mode. Defaults to `true`. Set `false` when the
|
|
277
|
+
* host sizes a fixed-height iframe itself.
|
|
261
278
|
*/
|
|
262
279
|
autoResize?: boolean;
|
|
263
280
|
/**
|
|
@@ -289,6 +306,8 @@ declare class EmbeddedDesigner {
|
|
|
289
306
|
private fsKeyHandler;
|
|
290
307
|
/** Latest height (px string) the Designer reported; re-applied after unpin. */
|
|
291
308
|
private lastAutoHeight;
|
|
309
|
+
private fillRaf;
|
|
310
|
+
private fillListening;
|
|
292
311
|
constructor(options: EmbeddedDesignerOptions);
|
|
293
312
|
mount(): HTMLIFrameElement;
|
|
294
313
|
/** Replace the iframe instead of assigning a new fragment to an existing one. */
|
|
@@ -297,6 +316,18 @@ declare class EmbeddedDesigner {
|
|
|
297
316
|
destroy(): void;
|
|
298
317
|
private loadingStateEnabled;
|
|
299
318
|
private autoResizeEnabled;
|
|
319
|
+
/** Fill mode is the default; a numeric `height` opts into a fixed pixel box. */
|
|
320
|
+
private fillEnabled;
|
|
321
|
+
/**
|
|
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).
|
|
325
|
+
*/
|
|
326
|
+
private applyFill;
|
|
327
|
+
/** rAF-throttled fill recompute, so a burst of scroll/resize ticks coalesces. */
|
|
328
|
+
private scheduleFill;
|
|
329
|
+
private startFill;
|
|
330
|
+
private stopFill;
|
|
300
331
|
/**
|
|
301
332
|
* Pin the iframe over the host page as a viewport-filling overlay. We save the
|
|
302
333
|
* iframe's inline style and the document scroll state so `unpinFullscreen`
|
|
@@ -325,7 +356,7 @@ declare class EmbeddedDesigner {
|
|
|
325
356
|
* SeatPicker — the full buyer experience as a widget.
|
|
326
357
|
*
|
|
327
358
|
* Where `SeatingChart` is canvas-only, SeatPicker owns the complete chrome
|
|
328
|
-
* from the canonical UX
|
|
359
|
+
* from the canonical UX contract: branded header,
|
|
329
360
|
* live price panel, selection tray with GA steppers, hold countdown, snipe
|
|
330
361
|
* toasts and expiry recovery — all on top of the shared PickerController, so
|
|
331
362
|
* every host gets the whole experience with one mount.
|
package/dist/index.d.ts
CHANGED
|
@@ -254,10 +254,27 @@ interface EmbeddedDesignerOptions {
|
|
|
254
254
|
* Only used when `showLoadingState` is enabled.
|
|
255
255
|
*/
|
|
256
256
|
loadingTimeoutMs?: number;
|
|
257
|
+
/**
|
|
258
|
+
* How to size the iframe's height. The Designer is a full application (its
|
|
259
|
+
* shell is `position:fixed; height:100dvh`), not flowing content, so it should
|
|
260
|
+
* fill the viewport rather than be measured.
|
|
261
|
+
*
|
|
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.
|
|
267
|
+
* - a number: a fixed pixel height. In this mode the legacy resize message is
|
|
268
|
+
* still honoured (unless `autoResize` is `false`) so older hosts keep growing.
|
|
269
|
+
*/
|
|
270
|
+
height?: 'fill' | number;
|
|
271
|
+
/** Minimum height (px) that `'fill'` mode clamps to. Defaults to `480`. */
|
|
272
|
+
minHeight?: number;
|
|
257
273
|
/**
|
|
258
274
|
* Auto-grow the iframe to the height the Designer reports over the resize
|
|
259
|
-
* protocol (`seatlayer.designer.resize`).
|
|
260
|
-
*
|
|
275
|
+
* protocol (`seatlayer.designer.resize`). Only applies when `height` is a fixed
|
|
276
|
+
* number; ignored in `'fill'` mode. Defaults to `true`. Set `false` when the
|
|
277
|
+
* host sizes a fixed-height iframe itself.
|
|
261
278
|
*/
|
|
262
279
|
autoResize?: boolean;
|
|
263
280
|
/**
|
|
@@ -289,6 +306,8 @@ declare class EmbeddedDesigner {
|
|
|
289
306
|
private fsKeyHandler;
|
|
290
307
|
/** Latest height (px string) the Designer reported; re-applied after unpin. */
|
|
291
308
|
private lastAutoHeight;
|
|
309
|
+
private fillRaf;
|
|
310
|
+
private fillListening;
|
|
292
311
|
constructor(options: EmbeddedDesignerOptions);
|
|
293
312
|
mount(): HTMLIFrameElement;
|
|
294
313
|
/** Replace the iframe instead of assigning a new fragment to an existing one. */
|
|
@@ -297,6 +316,18 @@ declare class EmbeddedDesigner {
|
|
|
297
316
|
destroy(): void;
|
|
298
317
|
private loadingStateEnabled;
|
|
299
318
|
private autoResizeEnabled;
|
|
319
|
+
/** Fill mode is the default; a numeric `height` opts into a fixed pixel box. */
|
|
320
|
+
private fillEnabled;
|
|
321
|
+
/**
|
|
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).
|
|
325
|
+
*/
|
|
326
|
+
private applyFill;
|
|
327
|
+
/** rAF-throttled fill recompute, so a burst of scroll/resize ticks coalesces. */
|
|
328
|
+
private scheduleFill;
|
|
329
|
+
private startFill;
|
|
330
|
+
private stopFill;
|
|
300
331
|
/**
|
|
301
332
|
* Pin the iframe over the host page as a viewport-filling overlay. We save the
|
|
302
333
|
* iframe's inline style and the document scroll state so `unpinFullscreen`
|
|
@@ -325,7 +356,7 @@ declare class EmbeddedDesigner {
|
|
|
325
356
|
* SeatPicker — the full buyer experience as a widget.
|
|
326
357
|
*
|
|
327
358
|
* Where `SeatingChart` is canvas-only, SeatPicker owns the complete chrome
|
|
328
|
-
* from the canonical UX
|
|
359
|
+
* from the canonical UX contract: branded header,
|
|
329
360
|
* live price panel, selection tray with GA steppers, hold countdown, snipe
|
|
330
361
|
* toasts and expiry recovery — all on top of the shared PickerController, so
|
|
331
362
|
* every host gets the whole experience with one mount.
|
package/dist/index.js
CHANGED
|
@@ -327,6 +327,7 @@ var TYPES = /* @__PURE__ */ new Set([
|
|
|
327
327
|
"seatlayer.designer.error"
|
|
328
328
|
]);
|
|
329
329
|
var DEFAULT_LOADING_TIMEOUT_MS = 2e4;
|
|
330
|
+
var DEFAULT_MIN_FILL_HEIGHT = 480;
|
|
330
331
|
function resolveContainer2(container) {
|
|
331
332
|
if (typeof container !== "string") return container;
|
|
332
333
|
const element = document.querySelector(container);
|
|
@@ -374,12 +375,23 @@ var EmbeddedDesigner = class {
|
|
|
374
375
|
this.fsKeyHandler = null;
|
|
375
376
|
/** Latest height (px string) the Designer reported; re-applied after unpin. */
|
|
376
377
|
this.lastAutoHeight = "";
|
|
378
|
+
// Viewport-fill sizing: pending rAF handle + whether listeners are attached.
|
|
379
|
+
this.fillRaf = null;
|
|
380
|
+
this.fillListening = false;
|
|
381
|
+
/** rAF-throttled fill recompute, so a burst of scroll/resize ticks coalesces. */
|
|
382
|
+
this.scheduleFill = () => {
|
|
383
|
+
if (this.fillRaf !== null) return;
|
|
384
|
+
this.fillRaf = requestAnimationFrame(() => {
|
|
385
|
+
this.fillRaf = null;
|
|
386
|
+
this.applyFill();
|
|
387
|
+
});
|
|
388
|
+
};
|
|
377
389
|
this.handleMessage = (event) => {
|
|
378
390
|
if (!this.frame || event.origin !== this.designerOrigin || event.source !== this.frame.contentWindow) return;
|
|
379
391
|
if (!event.data || typeof event.data !== "object") return;
|
|
380
392
|
const data = event.data;
|
|
381
393
|
if (data.type === "seatlayer.designer.resize") {
|
|
382
|
-
if (this.autoResizeEnabled() && typeof data.px === "number" && Number.isFinite(data.px) && data.px > 0) {
|
|
394
|
+
if (!this.fillEnabled() && this.autoResizeEnabled() && typeof data.px === "number" && Number.isFinite(data.px) && data.px > 0) {
|
|
383
395
|
this.lastAutoHeight = `${Math.round(data.px)}px`;
|
|
384
396
|
if (!this.pinned) this.frame.style.height = this.lastAutoHeight;
|
|
385
397
|
}
|
|
@@ -437,11 +449,11 @@ var EmbeddedDesigner = class {
|
|
|
437
449
|
this.designerOrigin = url.origin;
|
|
438
450
|
const frame = document.createElement("iframe");
|
|
439
451
|
frame.title = this.options.title ?? "Venue chart Designer";
|
|
440
|
-
frame.allow = this.options.allow ?? "clipboard-write";
|
|
452
|
+
frame.allow = this.options.allow ?? "fullscreen; clipboard-write";
|
|
441
453
|
frame.referrerPolicy = this.options.referrerPolicy ?? "origin";
|
|
442
454
|
frame.src = url.toString();
|
|
443
455
|
frame.style.width = "100%";
|
|
444
|
-
frame.style.height = "100%";
|
|
456
|
+
frame.style.height = typeof this.options.height === "number" ? `${this.options.height}px` : "100%";
|
|
445
457
|
frame.style.border = "0";
|
|
446
458
|
Object.assign(frame.style, this.options.style);
|
|
447
459
|
if (this.options.className) frame.className = this.options.className;
|
|
@@ -449,6 +461,7 @@ var EmbeddedDesigner = class {
|
|
|
449
461
|
window.addEventListener("message", this.handleMessage);
|
|
450
462
|
container.append(frame);
|
|
451
463
|
this.frame = frame;
|
|
464
|
+
if (this.fillEnabled()) this.startFill();
|
|
452
465
|
this.phase = "loading";
|
|
453
466
|
if (this.loadingStateEnabled()) {
|
|
454
467
|
this.ensureContainerPositioned(container);
|
|
@@ -472,6 +485,7 @@ var EmbeddedDesigner = class {
|
|
|
472
485
|
}
|
|
473
486
|
destroy() {
|
|
474
487
|
window.removeEventListener("message", this.handleMessage);
|
|
488
|
+
this.stopFill();
|
|
475
489
|
this.unpinFullscreen();
|
|
476
490
|
this.clearTimeoutTimer();
|
|
477
491
|
this.removeOverlay();
|
|
@@ -488,6 +502,41 @@ var EmbeddedDesigner = class {
|
|
|
488
502
|
autoResizeEnabled() {
|
|
489
503
|
return this.options.autoResize !== false;
|
|
490
504
|
}
|
|
505
|
+
/** Fill mode is the default; a numeric `height` opts into a fixed pixel box. */
|
|
506
|
+
fillEnabled() {
|
|
507
|
+
return typeof this.options.height !== "number";
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Size the iframe so its bottom edge meets the bottom of the viewport
|
|
511
|
+
* (`window.innerHeight - top`), clamped to `minHeight`. No-op while pinned
|
|
512
|
+
* fullscreen (the pin fills the viewport itself).
|
|
513
|
+
*/
|
|
514
|
+
applyFill() {
|
|
515
|
+
if (!this.frame || this.pinned) return;
|
|
516
|
+
const min = this.options.minHeight ?? DEFAULT_MIN_FILL_HEIGHT;
|
|
517
|
+
const top = this.frame.getBoundingClientRect().top;
|
|
518
|
+
const target = Math.max(min, Math.round(window.innerHeight - top));
|
|
519
|
+
this.frame.style.height = `${target}px`;
|
|
520
|
+
}
|
|
521
|
+
startFill() {
|
|
522
|
+
this.applyFill();
|
|
523
|
+
if (this.fillListening) return;
|
|
524
|
+
this.fillListening = true;
|
|
525
|
+
window.addEventListener("resize", this.scheduleFill);
|
|
526
|
+
window.addEventListener("orientationchange", this.scheduleFill);
|
|
527
|
+
window.addEventListener("scroll", this.scheduleFill, { passive: true });
|
|
528
|
+
}
|
|
529
|
+
stopFill() {
|
|
530
|
+
if (this.fillRaf !== null) {
|
|
531
|
+
cancelAnimationFrame(this.fillRaf);
|
|
532
|
+
this.fillRaf = null;
|
|
533
|
+
}
|
|
534
|
+
if (!this.fillListening) return;
|
|
535
|
+
this.fillListening = false;
|
|
536
|
+
window.removeEventListener("resize", this.scheduleFill);
|
|
537
|
+
window.removeEventListener("orientationchange", this.scheduleFill);
|
|
538
|
+
window.removeEventListener("scroll", this.scheduleFill);
|
|
539
|
+
}
|
|
491
540
|
/**
|
|
492
541
|
* Pin the iframe over the host page as a viewport-filling overlay. We save the
|
|
493
542
|
* iframe's inline style and the document scroll state so `unpinFullscreen`
|
|
@@ -526,7 +575,8 @@ var EmbeddedDesigner = class {
|
|
|
526
575
|
if (this.frame) {
|
|
527
576
|
if (this.frameStyleBeforeFs === null) this.frame.removeAttribute("style");
|
|
528
577
|
else this.frame.setAttribute("style", this.frameStyleBeforeFs);
|
|
529
|
-
if (this.
|
|
578
|
+
if (this.fillEnabled()) this.applyFill();
|
|
579
|
+
else if (this.autoResizeEnabled() && this.lastAutoHeight) this.frame.style.height = this.lastAutoHeight;
|
|
530
580
|
}
|
|
531
581
|
this.frameStyleBeforeFs = null;
|
|
532
582
|
if (this.docOverflowBeforeFs !== null) {
|
|
@@ -838,6 +888,11 @@ var CSS = `
|
|
|
838
888
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec,
|
|
839
889
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters,
|
|
840
890
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-prices-sec{display:none}
|
|
891
|
+
/* Reclaim the bottom sheet once the cart has anything: the "Find best seats"
|
|
892
|
+
panel collapses too. EXCEPT the confirm ("Replace your current choices?") and
|
|
893
|
+
in-flight busy states, which legitimately show with a non-empty cart \u2014 those
|
|
894
|
+
set data-ba-active="true" (see setAttribute alongside data-has-selection). */
|
|
895
|
+
.sl-picker[data-layout="narrow"][data-has-selection="true"]:not([data-ba-active="true"]) .sl-ba{display:none}
|
|
841
896
|
/* touch chrome: pinch-zoom exists \u2014 hide +/\u2212 on the sheet layout (keep fit) */
|
|
842
897
|
.sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zin"],
|
|
843
898
|
.sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zout"]{display:none}
|
|
@@ -2688,6 +2743,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
2688
2743
|
if (narrow) {
|
|
2689
2744
|
card.className = "sl-seccard strip on";
|
|
2690
2745
|
card.setAttribute("role", "status");
|
|
2746
|
+
card.setAttribute("aria-label", t2("picker.sectionSummaryAria", { label: summary.label }));
|
|
2691
2747
|
card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn;
|
|
2692
2748
|
card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
|
|
2693
2749
|
(this.els.sheetHead ?? this.els.side ?? this.els.map).appendChild(card);
|
|
@@ -3098,7 +3154,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3098
3154
|
} else if (!seats.length && !heldItems.length) {
|
|
3099
3155
|
parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
|
|
3100
3156
|
}
|
|
3101
|
-
const noPicks = !seats.length && !heldItems.length;
|
|
3157
|
+
const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
|
|
3102
3158
|
if (!this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
|
|
3103
3159
|
const cats = this.controller.doc?.categories ?? [];
|
|
3104
3160
|
parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Replace your current choices?</div><div class="sl-ba-replace"><b>We\u2019ll find ${this.baQty} seats together.</b><span>Your manually selected tickets will be removed only after a new group is secured.</span></div><div class="sl-ba-actions"><button type="button" data-ba-cancel>Keep mine</button><button type="button" class="replace" data-ba-replace>Find new seats</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Find the best seats together</div><div class="sl-ba-copy"><span class="wide">We\u2019ll choose the closest available group for you.</span><span class="narrow">Closest available group, chosen instantly.</span></div>` + (cats.length > 1 ? `<select aria-label="Preferred ticket type" data-ba-cat><option value="">Any ticket type</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : `<span aria-hidden="true"></span>`) + `<div class="sl-ba-qty"><button type="button" data-ba="-1" aria-label="Fewer seats">\u2212</button><span>${this.baQty}</span><button type="button" data-ba="1" aria-label="More seats">+</button></div><button type="button" class="sl-ba-go"${this.bestAvailableBusy ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>Finding the best seats\u2026` : `Find ${this.baQty} best ${this.baQty === 1 ? "seat" : "seats"}`) + `</button></div>`);
|
|
@@ -3242,6 +3298,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
3242
3298
|
this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
|
|
3243
3299
|
this.els.total.textContent = count ? this.money(total) : "";
|
|
3244
3300
|
this.root?.setAttribute("data-has-selection", String(count > 0));
|
|
3301
|
+
this.root?.setAttribute(
|
|
3302
|
+
"data-ba-active",
|
|
3303
|
+
String(this.bestAvailableConfirm || this.bestAvailableBusy)
|
|
3304
|
+
);
|
|
3245
3305
|
this.els.foot?.classList.toggle("empty", count === 0);
|
|
3246
3306
|
if (this.els.seatSummary) {
|
|
3247
3307
|
this.els.seatSummary.textContent = count ? `${count} selected` : "";
|