@seatlayer/js 0.5.0 → 0.6.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 +527 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +123 -1
- package/dist/index.d.ts +123 -1
- package/dist/index.js +531 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ApiError: () => ApiError,
|
|
24
24
|
EmbeddedDesigner: () => EmbeddedDesigner,
|
|
25
|
+
SeatPicker: () => SeatPicker,
|
|
25
26
|
SeatingChart: () => SeatingChart
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -401,10 +402,536 @@ var EmbeddedDesigner = class {
|
|
|
401
402
|
this.designerOrigin = "";
|
|
402
403
|
}
|
|
403
404
|
};
|
|
405
|
+
|
|
406
|
+
// src/SeatPicker.ts
|
|
407
|
+
var import_core2 = require("@seatlayer/core");
|
|
408
|
+
var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
|
|
409
|
+
var DEFAULT_MAX_SELECTION2 = 10;
|
|
410
|
+
function resolveContainer3(container) {
|
|
411
|
+
if (typeof container === "string") {
|
|
412
|
+
const el = document.querySelector(container);
|
|
413
|
+
if (!el) throw new Error(`seatmap: container "${container}" not found`);
|
|
414
|
+
return el;
|
|
415
|
+
}
|
|
416
|
+
if (!(container instanceof HTMLElement)) {
|
|
417
|
+
throw new Error("seatmap: container must be a CSS selector or an HTMLElement");
|
|
418
|
+
}
|
|
419
|
+
return container;
|
|
420
|
+
}
|
|
421
|
+
var STYLE_ID = "seatlayer-picker-style";
|
|
422
|
+
var CSS = `
|
|
423
|
+
.sl-picker{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:420px;overflow:hidden;
|
|
424
|
+
background:var(--sl-bg);color:var(--sl-text);font-family:var(--sl-font);border-radius:var(--sl-radius);
|
|
425
|
+
--sl-r-sm:calc(var(--sl-radius) * .55)}
|
|
426
|
+
.sl-picker *{box-sizing:border-box;margin:0;padding:0}
|
|
427
|
+
.sl-picker button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
|
|
428
|
+
|
|
429
|
+
/* header */
|
|
430
|
+
.sl-head{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
|
|
431
|
+
.sl-logo{width:34px;height:34px;border-radius:9px;flex:none;display:flex;align-items:center;justify-content:center;
|
|
432
|
+
background:var(--sl-accent);color:var(--sl-accent-ink);font-weight:800;font-size:15px;overflow:hidden}
|
|
433
|
+
.sl-logo img{width:100%;height:100%;object-fit:cover;display:block}
|
|
434
|
+
.sl-head-info{min-width:0;flex:1}
|
|
435
|
+
.sl-head-name{font-weight:700;font-size:15px;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
436
|
+
.sl-head-meta{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--sl-muted);margin-top:3px;
|
|
437
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
438
|
+
.sl-hold-pill{display:none;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;flex:none;
|
|
439
|
+
background:var(--sl-accent);color:var(--sl-accent-ink);font-weight:700;font-size:12px;font-variant-numeric:tabular-nums}
|
|
440
|
+
.sl-hold-pill.on{display:inline-flex}
|
|
441
|
+
.sl-close{width:32px;height:32px;border-radius:999px;flex:none;display:none;align-items:center;justify-content:center;
|
|
442
|
+
border:1px solid var(--sl-line);color:var(--sl-muted);transition:color .15s,border-color .15s}
|
|
443
|
+
.sl-close:hover{color:var(--sl-text);border-color:var(--sl-muted)}
|
|
444
|
+
.sl-close.on{display:inline-flex}
|
|
445
|
+
.sl-close svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round}
|
|
446
|
+
|
|
447
|
+
/* body */
|
|
448
|
+
.sl-body{display:flex;flex:1;min-height:0}
|
|
449
|
+
.sl-map{position:relative;flex:1;min-width:0}
|
|
450
|
+
.sl-map-host{position:absolute;inset:0}
|
|
451
|
+
.sl-side{width:300px;flex:none;border-left:1px solid var(--sl-line);display:flex;flex-direction:column;min-height:0;overflow-y:auto}
|
|
452
|
+
|
|
453
|
+
/* narrow (container < 640px): side panel becomes a bottom sheet */
|
|
454
|
+
.sl-picker[data-layout="narrow"] .sl-body{flex-direction:column}
|
|
455
|
+
.sl-picker[data-layout="narrow"] .sl-map{min-height:0;flex:1}
|
|
456
|
+
.sl-picker[data-layout="narrow"] .sl-side{width:100%;max-height:46%;border-left:0;border-top:1px solid var(--sl-line)}
|
|
457
|
+
|
|
458
|
+
/* price panel */
|
|
459
|
+
.sl-sec{padding:14px 16px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
|
|
460
|
+
.sl-prices{padding:4px 16px 10px;border-bottom:1px solid var(--sl-line)}
|
|
461
|
+
.sl-price-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:13px}
|
|
462
|
+
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
463
|
+
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
464
|
+
.sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
|
|
465
|
+
.sl-price-amt{font-weight:800;font-variant-numeric:tabular-nums}
|
|
466
|
+
|
|
467
|
+
/* tray */
|
|
468
|
+
.sl-tray{flex:1;padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}
|
|
469
|
+
.sl-tray-hint{font-size:12.5px;color:var(--sl-muted);line-height:1.5}
|
|
470
|
+
.sl-chip{display:flex;align-items:center;gap:9px;padding:9px 11px;border:1px solid var(--sl-line);
|
|
471
|
+
border-radius:var(--sl-r-sm);background:var(--sl-surface);font-size:13px}
|
|
472
|
+
.sl-chip b{font-weight:800}
|
|
473
|
+
.sl-chip .cat{color:var(--sl-muted);font-size:11.5px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
474
|
+
.sl-chip .amt{font-weight:700;font-variant-numeric:tabular-nums}
|
|
475
|
+
.sl-chip .rm{width:22px;height:22px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted)}
|
|
476
|
+
.sl-chip .rm:hover{color:var(--sl-text)}
|
|
477
|
+
.sl-chip .rm svg{width:11px;height:11px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round}
|
|
478
|
+
|
|
479
|
+
/* GA rows */
|
|
480
|
+
.sl-ga{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px dashed var(--sl-line);border-radius:var(--sl-r-sm)}
|
|
481
|
+
.sl-ga-info{flex:1;min-width:0}
|
|
482
|
+
.sl-ga-name{font-weight:700;font-size:13px}
|
|
483
|
+
.sl-ga-sub{font-size:11px;color:var(--sl-muted);margin-top:2px}
|
|
484
|
+
.sl-ga-qty{display:flex;align-items:center;gap:8px}
|
|
485
|
+
.sl-ga-qty button{width:26px;height:26px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
486
|
+
font-size:15px;font-weight:700;display:flex;align-items:center;justify-content:center;transition:border-color .15s}
|
|
487
|
+
.sl-ga-qty button:hover{border-color:var(--sl-muted)}
|
|
488
|
+
.sl-ga-qty span{min-width:16px;text-align:center;font-weight:800;font-variant-numeric:tabular-nums}
|
|
489
|
+
|
|
490
|
+
/* footer */
|
|
491
|
+
.sl-foot{padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none}
|
|
492
|
+
.sl-total{display:flex;justify-content:space-between;align-items:center;font-size:13px;margin-bottom:10px}
|
|
493
|
+
.sl-total b{font-size:17px;font-variant-numeric:tabular-nums}
|
|
494
|
+
.sl-cta{width:100%;padding:13px;border-radius:var(--sl-r-sm);font-weight:800;font-size:14px;
|
|
495
|
+
background:var(--sl-accent);color:var(--sl-accent-ink);transition:filter .15s,opacity .15s}
|
|
496
|
+
.sl-cta:hover{filter:brightness(1.08)}
|
|
497
|
+
.sl-cta:disabled{opacity:.45;cursor:not-allowed}
|
|
498
|
+
|
|
499
|
+
/* zoom column */
|
|
500
|
+
.sl-zoom{position:absolute;right:12px;bottom:12px;display:flex;flex-direction:column;gap:6px;z-index:5}
|
|
501
|
+
.sl-zoom button{width:36px;height:36px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
502
|
+
color:var(--sl-text);font-size:17px;font-weight:700;display:flex;align-items:center;justify-content:center;transition:border-color .15s}
|
|
503
|
+
.sl-zoom button:hover{border-color:var(--sl-muted)}
|
|
504
|
+
.sl-zoom svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
505
|
+
|
|
506
|
+
/* toast + boot states */
|
|
507
|
+
.sl-toast{position:absolute;left:50%;bottom:16px;transform:translateX(-50%) translateY(6px);z-index:8;max-width:88%;
|
|
508
|
+
background:var(--sl-surface);border:1px solid var(--sl-line);color:var(--sl-text);border-radius:999px;padding:9px 16px;
|
|
509
|
+
font-size:12.5px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;white-space:nowrap;
|
|
510
|
+
overflow:hidden;text-overflow:ellipsis}
|
|
511
|
+
.sl-toast.on{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
512
|
+
.sl-boot{position:absolute;inset:0;z-index:6;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
513
|
+
gap:10px;background:var(--sl-bg);font-size:13px;font-weight:600;color:var(--sl-muted)}
|
|
514
|
+
.sl-boot-spin{width:24px;height:24px;border-radius:50%;border:3px solid var(--sl-line);border-top-color:var(--sl-accent);
|
|
515
|
+
animation:slspin .8s linear infinite}
|
|
516
|
+
@keyframes slspin{to{transform:rotate(360deg)}}
|
|
517
|
+
.sl-boot-title{font-weight:800;font-size:15px;color:var(--sl-text)}
|
|
518
|
+
.sl-boot-retry{margin-top:4px;padding:9px 20px;border-radius:var(--sl-r-sm);background:var(--sl-accent);
|
|
519
|
+
color:var(--sl-accent-ink);font-weight:700;font-size:13px}
|
|
520
|
+
|
|
521
|
+
/* modal host */
|
|
522
|
+
.sl-modal-scrim{position:fixed;inset:0;z-index:2147483000;background:rgba(5,7,12,.66);display:flex;align-items:center;justify-content:center;padding:18px}
|
|
523
|
+
.sl-modal-frame{width:min(1200px,100%);height:min(820px,100%);border-radius:16px;overflow:hidden;box-shadow:0 40px 120px -30px rgba(0,0,0,.8)}
|
|
524
|
+
@media(max-width:640px){.sl-modal-scrim{padding:0}.sl-modal-frame{width:100%;height:100%;border-radius:0}}
|
|
525
|
+
`;
|
|
526
|
+
function ensureStyle() {
|
|
527
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
528
|
+
const el = document.createElement("style");
|
|
529
|
+
el.id = STYLE_ID;
|
|
530
|
+
el.textContent = CSS;
|
|
531
|
+
document.head.appendChild(el);
|
|
532
|
+
}
|
|
533
|
+
function resolveTokens(chart, host) {
|
|
534
|
+
const accent = host?.accent ?? chart?.accent ?? "#f4b740";
|
|
535
|
+
const accentInk = host?.accentInk ?? chart?.accentInk ?? "#1a1200";
|
|
536
|
+
return {
|
|
537
|
+
"--sl-accent": accent,
|
|
538
|
+
"--sl-accent-ink": accentInk,
|
|
539
|
+
"--sl-bg": host?.background ?? chart?.background ?? "#0f1522",
|
|
540
|
+
"--sl-surface": host?.surface ?? "#1a2234",
|
|
541
|
+
"--sl-text": host?.text ?? chart?.textColor ?? "#eef1f8",
|
|
542
|
+
"--sl-muted": host?.muted ?? "#8b93a7",
|
|
543
|
+
"--sl-line": host?.line ?? "rgba(139,147,167,.22)",
|
|
544
|
+
"--sl-font": host?.fontFamily ?? chart?.fontFamily ?? "-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,sans-serif",
|
|
545
|
+
"--sl-radius": `${host?.radius ?? 14}px`
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
var SeatPicker = class _SeatPicker {
|
|
549
|
+
constructor(options) {
|
|
550
|
+
this.root = null;
|
|
551
|
+
this.mapHost = null;
|
|
552
|
+
this.rendered = false;
|
|
553
|
+
this.destroyed = false;
|
|
554
|
+
// chrome refs
|
|
555
|
+
this.els = {};
|
|
556
|
+
this.ro = null;
|
|
557
|
+
this.holdTimer = null;
|
|
558
|
+
this.toastTimer = null;
|
|
559
|
+
// state
|
|
560
|
+
this.currency = "USD";
|
|
561
|
+
this.hold = null;
|
|
562
|
+
this.gaQty = /* @__PURE__ */ new Map();
|
|
563
|
+
this.tipEl = null;
|
|
564
|
+
this.tipPos = { x: 0, y: 0 };
|
|
565
|
+
// modal plumbing (set by open())
|
|
566
|
+
this.modalScrim = null;
|
|
567
|
+
this.prevFocus = null;
|
|
568
|
+
this.escHandler = null;
|
|
569
|
+
if (!options || typeof options !== "object") throw new Error("seatmap: options object is required");
|
|
570
|
+
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
571
|
+
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
572
|
+
this.opts = options;
|
|
573
|
+
const api = new PubApi((options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, ""));
|
|
574
|
+
this.controller = new import_core2.PickerController({
|
|
575
|
+
transport: api,
|
|
576
|
+
eventKey: options.event,
|
|
577
|
+
maxSelection: options.maxSelection ?? DEFAULT_MAX_SELECTION2,
|
|
578
|
+
currency: options.currency,
|
|
579
|
+
flashOnLiveChange: true,
|
|
580
|
+
colorblindSafe: options.colorblindSafe,
|
|
581
|
+
onSelectionChange: () => this.syncTray(),
|
|
582
|
+
onStatusChange: () => {
|
|
583
|
+
this.syncPrices();
|
|
584
|
+
this.evictTakenSelections();
|
|
585
|
+
},
|
|
586
|
+
onHoldExpired: () => {
|
|
587
|
+
this.hold = null;
|
|
588
|
+
this.stopHoldTimer();
|
|
589
|
+
this.gaQty.clear();
|
|
590
|
+
this.toast((0, import_core2.t)("picker.holdExpired", void 0) || "Your hold expired \u2014 seats released. Pick again.");
|
|
591
|
+
this.syncTray();
|
|
592
|
+
this.opts.onHoldExpired?.();
|
|
593
|
+
},
|
|
594
|
+
onSeatHover: (d) => this.updateTooltip(d),
|
|
595
|
+
onHint: (m) => {
|
|
596
|
+
if (m) this.toast(m);
|
|
597
|
+
},
|
|
598
|
+
onError: (err) => this.opts.onError?.(err)
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
/** Mount the full picker as a document-level modal. Resolves after render. */
|
|
602
|
+
static async open(options) {
|
|
603
|
+
ensureStyle();
|
|
604
|
+
const scrim = document.createElement("div");
|
|
605
|
+
scrim.className = "sl-modal-scrim";
|
|
606
|
+
const frame = document.createElement("div");
|
|
607
|
+
frame.className = "sl-modal-frame";
|
|
608
|
+
scrim.appendChild(frame);
|
|
609
|
+
document.body.appendChild(scrim);
|
|
610
|
+
const prevOverflow = document.body.style.overflow;
|
|
611
|
+
document.body.style.overflow = "hidden";
|
|
612
|
+
const picker = new _SeatPicker({ ...options, container: frame });
|
|
613
|
+
picker.modalScrim = scrim;
|
|
614
|
+
picker.prevFocus = document.activeElement;
|
|
615
|
+
const close = () => {
|
|
616
|
+
document.body.style.overflow = prevOverflow;
|
|
617
|
+
picker.destroy();
|
|
618
|
+
options.onClose?.();
|
|
619
|
+
};
|
|
620
|
+
scrim.addEventListener("mousedown", (e) => {
|
|
621
|
+
if (e.target === scrim) close();
|
|
622
|
+
});
|
|
623
|
+
picker.escHandler = (e) => {
|
|
624
|
+
if (e.key === "Escape") close();
|
|
625
|
+
};
|
|
626
|
+
document.addEventListener("keydown", picker.escHandler);
|
|
627
|
+
await picker.render();
|
|
628
|
+
picker.els.close?.classList.add("on");
|
|
629
|
+
picker.els.close?.addEventListener("click", close);
|
|
630
|
+
return picker;
|
|
631
|
+
}
|
|
632
|
+
async render() {
|
|
633
|
+
if (this.rendered) return this;
|
|
634
|
+
this.rendered = true;
|
|
635
|
+
ensureStyle();
|
|
636
|
+
await (0, import_core2.loadLocale)(this.opts.locale);
|
|
637
|
+
if (this.opts.messages) (0, import_core2.setStringOverrides)(this.opts.messages);
|
|
638
|
+
const mount = resolveContainer3(this.opts.container);
|
|
639
|
+
const root = document.createElement("div");
|
|
640
|
+
root.className = "sl-picker";
|
|
641
|
+
this.root = root;
|
|
642
|
+
mount.appendChild(root);
|
|
643
|
+
Object.entries(resolveTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
644
|
+
root.innerHTML = `
|
|
645
|
+
<div class="sl-head">
|
|
646
|
+
<div class="sl-logo" data-ref="logo"></div>
|
|
647
|
+
<div class="sl-head-info">
|
|
648
|
+
<div class="sl-head-name" data-ref="name"></div>
|
|
649
|
+
<div class="sl-head-meta" data-ref="meta"></div>
|
|
650
|
+
</div>
|
|
651
|
+
<span class="sl-hold-pill" data-ref="hold"></span>
|
|
652
|
+
<button type="button" class="sl-close" data-ref="close" aria-label="Close">
|
|
653
|
+
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
654
|
+
</button>
|
|
655
|
+
</div>
|
|
656
|
+
<div class="sl-body">
|
|
657
|
+
<div class="sl-map">
|
|
658
|
+
<div class="sl-map-host" data-ref="map"></div>
|
|
659
|
+
<div class="sl-zoom">
|
|
660
|
+
<button type="button" aria-label="Zoom in" data-ref="zin">+</button>
|
|
661
|
+
<button type="button" aria-label="Zoom out" data-ref="zout">\u2212</button>
|
|
662
|
+
<button type="button" aria-label="Fit to screen" data-ref="zfit">
|
|
663
|
+
<svg viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
|
|
664
|
+
</button>
|
|
665
|
+
</div>
|
|
666
|
+
<div class="sl-boot" data-ref="boot"><span class="sl-boot-spin"></span>Loading seat map\u2026</div>
|
|
667
|
+
<div class="sl-toast" data-ref="toast" role="status" aria-live="polite"></div>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="sl-side">
|
|
670
|
+
<div class="sl-sec" data-ref="pricesSec">Prices</div>
|
|
671
|
+
<div class="sl-prices" data-ref="prices"></div>
|
|
672
|
+
<div class="sl-sec">Your seats</div>
|
|
673
|
+
<div class="sl-tray" data-ref="tray"></div>
|
|
674
|
+
<div class="sl-foot">
|
|
675
|
+
<div class="sl-total"><span data-ref="count"></span><b data-ref="total"></b></div>
|
|
676
|
+
<button type="button" class="sl-cta" data-ref="cta" disabled></button>
|
|
677
|
+
</div>
|
|
678
|
+
</div>
|
|
679
|
+
</div>`;
|
|
680
|
+
root.querySelectorAll("[data-ref]").forEach((el) => {
|
|
681
|
+
this.els[el.dataset.ref] = el;
|
|
682
|
+
});
|
|
683
|
+
this.mapHost = this.els.map;
|
|
684
|
+
this.ro = new ResizeObserver(() => {
|
|
685
|
+
const w = root.clientWidth;
|
|
686
|
+
root.dataset.layout = w < 640 ? "narrow" : "wide";
|
|
687
|
+
});
|
|
688
|
+
this.ro.observe(root);
|
|
689
|
+
this.els.zin.addEventListener("click", () => this.controller.zoomIn());
|
|
690
|
+
this.els.zout.addEventListener("click", () => this.controller.zoomOut());
|
|
691
|
+
this.els.zfit.addEventListener("click", () => this.controller.zoomToFit());
|
|
692
|
+
this.tipEl = document.createElement("div");
|
|
693
|
+
this.tipEl.setAttribute("role", "tooltip");
|
|
694
|
+
this.tipEl.style.cssText = "position:absolute;z-index:7;pointer-events:none;display:none;max-width:240px;background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:10px;padding:9px 12px;font-size:12px;line-height:1.45;";
|
|
695
|
+
this.els.map.appendChild(this.tipEl);
|
|
696
|
+
this.els.map.addEventListener("mousemove", (e) => {
|
|
697
|
+
const r = this.els.map.getBoundingClientRect();
|
|
698
|
+
this.tipPos = { x: e.clientX - r.left, y: e.clientY - r.top };
|
|
699
|
+
if (this.tipEl && this.tipEl.style.display !== "none") this.placeTooltip();
|
|
700
|
+
});
|
|
701
|
+
this.els.cta.addEventListener("click", () => void this.handleCta());
|
|
702
|
+
const canvasHost = document.createElement("div");
|
|
703
|
+
canvasHost.style.cssText = "position:absolute;inset:0";
|
|
704
|
+
this.mapHost.appendChild(canvasHost);
|
|
705
|
+
const info = await this.controller.render(canvasHost);
|
|
706
|
+
if (this.destroyed) return this;
|
|
707
|
+
if (!info) {
|
|
708
|
+
this.els.boot.innerHTML = '<div class="sl-boot-title">The seat map didn\u2019t load</div><div>Check your connection and try again.</div><button type="button" class="sl-boot-retry">Try again</button>';
|
|
709
|
+
this.els.boot.querySelector("button").addEventListener("click", () => {
|
|
710
|
+
const container = this.opts.container;
|
|
711
|
+
const opts = this.opts;
|
|
712
|
+
this.destroy();
|
|
713
|
+
void new _SeatPicker({ ...opts, container }).render();
|
|
714
|
+
});
|
|
715
|
+
return this;
|
|
716
|
+
}
|
|
717
|
+
this.els.boot.remove();
|
|
718
|
+
const chartTheme = this.controller.doc?.theme;
|
|
719
|
+
Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
720
|
+
this.currency = info.currency ?? this.opts.currency ?? "USD";
|
|
721
|
+
const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl;
|
|
722
|
+
if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
|
|
723
|
+
else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
|
|
724
|
+
this.els.name.textContent = info.eventName ?? "";
|
|
725
|
+
const when = info.startsAt ? new Date(info.startsAt).toLocaleString(this.opts.locale, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : "";
|
|
726
|
+
this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
|
|
727
|
+
this.syncPrices();
|
|
728
|
+
this.syncTray();
|
|
729
|
+
return this;
|
|
730
|
+
}
|
|
731
|
+
// ---- chrome sync ----------------------------------------------------------
|
|
732
|
+
money(n) {
|
|
733
|
+
try {
|
|
734
|
+
return new Intl.NumberFormat(this.opts.locale, { style: "currency", currency: this.currency }).format(n);
|
|
735
|
+
} catch {
|
|
736
|
+
return `${n} ${this.currency}`;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
syncPrices() {
|
|
740
|
+
const doc = this.controller.doc;
|
|
741
|
+
if (!doc || !this.els.prices) return;
|
|
742
|
+
const left = this.controller.categoryAvailability();
|
|
743
|
+
this.els.prices.innerHTML = doc.categories.map((c) => {
|
|
744
|
+
const price = c.tiers?.length ? c.tiers[0].price : c.price;
|
|
745
|
+
return `<div class="sl-price-row"><span class="sl-dot" style="background:${c.color}"></span><span class="sl-price-label">${c.label}</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
746
|
+
}).join("");
|
|
747
|
+
}
|
|
748
|
+
/** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
|
|
749
|
+
evictTakenSelections() {
|
|
750
|
+
const ownLabels = new Set(this.controller.currentHold()?.labels ?? []);
|
|
751
|
+
const gone = this.controller.getSelection().filter((s) => !ownLabels.has(s.label) && (this.controller.getStatus(s.id) ?? "free") !== "free");
|
|
752
|
+
if (!gone.length) return;
|
|
753
|
+
this.controller.deselect(gone.map((s) => s.id));
|
|
754
|
+
this.toast(`Seat ${gone[0].label} was just taken by another buyer.`);
|
|
755
|
+
}
|
|
756
|
+
syncTray() {
|
|
757
|
+
if (!this.els.tray) return;
|
|
758
|
+
const seats = this.controller.getSelection();
|
|
759
|
+
const gaAreas = this.controller.getGAAreas();
|
|
760
|
+
const parts = [];
|
|
761
|
+
if (!seats.length && !gaAreas.length) {
|
|
762
|
+
parts.push(`<div class="sl-tray-hint">Tap a seat on the map, or let us pick the best available for you.</div>`);
|
|
763
|
+
} else if (!seats.length) {
|
|
764
|
+
parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
|
|
765
|
+
}
|
|
766
|
+
for (const s of seats) {
|
|
767
|
+
const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
|
|
768
|
+
parts.push(
|
|
769
|
+
`<div class="sl-chip" data-seat="${s.id}"><b>${s.label}</b><span class="cat">${cat?.label ?? s.categoryKey}</span><span class="amt">${this.money(s.price)}</span><button type="button" class="rm" aria-label="Remove ${s.label}"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div>`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
for (const area of gaAreas) {
|
|
773
|
+
const qty = this.gaQty.get(area.id) ?? 0;
|
|
774
|
+
parts.push(
|
|
775
|
+
`<div class="sl-ga" data-ga="${area.id}"><div class="sl-ga-info"><div class="sl-ga-name">${area.label}</div><div class="sl-ga-sub">${this.money(area.price)} \xB7 ${area.available} left</div></div><div class="sl-ga-qty"><button type="button" data-d="-1" aria-label="Fewer">\u2212</button><span>${qty}</span><button type="button" data-d="1" aria-label="More">+</button></div></div>`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
this.els.tray.innerHTML = parts.join("");
|
|
779
|
+
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
780
|
+
btn.addEventListener("click", () => {
|
|
781
|
+
const id = btn.closest(".sl-chip").dataset.seat;
|
|
782
|
+
this.controller.deselect([id]);
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
this.els.tray.querySelectorAll(".sl-ga button").forEach((btn) => {
|
|
786
|
+
btn.addEventListener("click", () => {
|
|
787
|
+
const areaEl = btn.closest(".sl-ga");
|
|
788
|
+
const id = areaEl.dataset.ga;
|
|
789
|
+
const area = gaAreas.find((a) => a.id === id);
|
|
790
|
+
const next = Math.max(0, Math.min(area?.available ?? 0, (this.gaQty.get(id) ?? 0) + Number(btn.dataset.d)));
|
|
791
|
+
this.gaQty.set(id, next);
|
|
792
|
+
this.syncTray();
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
const gaTotal = gaAreas.reduce((sum, a) => sum + a.price * (this.gaQty.get(a.id) ?? 0), 0);
|
|
796
|
+
const gaCount = [...this.gaQty.values()].reduce((a, b) => a + b, 0);
|
|
797
|
+
const total = seats.reduce((sum, s) => sum + s.price, 0) + gaTotal;
|
|
798
|
+
const count = seats.length + gaCount;
|
|
799
|
+
this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
|
|
800
|
+
this.els.total.textContent = count ? this.money(total) : "";
|
|
801
|
+
const cta = this.els.cta;
|
|
802
|
+
cta.disabled = count === 0;
|
|
803
|
+
cta.textContent = count ? "Hold seats & checkout" : "Select seats";
|
|
804
|
+
this.opts.onSelectionChange?.(seats);
|
|
805
|
+
}
|
|
806
|
+
async handleCta() {
|
|
807
|
+
const cta = this.els.cta;
|
|
808
|
+
cta.disabled = true;
|
|
809
|
+
cta.textContent = "Holding\u2026";
|
|
810
|
+
try {
|
|
811
|
+
let hold = null;
|
|
812
|
+
const gaEntries = [...this.gaQty.entries()].filter(([, q]) => q > 0);
|
|
813
|
+
const chosenSeats = this.controller.getSelection();
|
|
814
|
+
if (chosenSeats.length) {
|
|
815
|
+
const h = await this.controller.hold(void 0, this.opts.holdTtlMs);
|
|
816
|
+
hold = h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : null;
|
|
817
|
+
}
|
|
818
|
+
for (const [areaId, qty] of gaEntries) {
|
|
819
|
+
const h = await this.controller.holdGA(areaId, qty, { ttlMs: this.opts.holdTtlMs });
|
|
820
|
+
hold = h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : hold;
|
|
821
|
+
}
|
|
822
|
+
if (!hold) {
|
|
823
|
+
this.toast("One or more seats were just taken. Please pick again.");
|
|
824
|
+
this.syncTray();
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
this.hold = hold;
|
|
828
|
+
this.startHoldTimer(hold.expiresAt);
|
|
829
|
+
this.opts.onCheckout?.(hold, chosenSeats);
|
|
830
|
+
} catch (err) {
|
|
831
|
+
this.opts.onError?.(err);
|
|
832
|
+
this.toast("One or more seats were just taken. Please pick again.");
|
|
833
|
+
} finally {
|
|
834
|
+
this.syncTray();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
startHoldTimer(expiresAt) {
|
|
838
|
+
this.stopHoldTimer();
|
|
839
|
+
const pill = this.els.hold;
|
|
840
|
+
const tick = () => {
|
|
841
|
+
const ms = Math.max(0, expiresAt - Date.now());
|
|
842
|
+
const m = Math.floor(ms / 6e4);
|
|
843
|
+
const s = String(Math.floor(ms % 6e4 / 1e3)).padStart(2, "0");
|
|
844
|
+
pill.textContent = `Held ${m}:${s}`;
|
|
845
|
+
pill.classList.add("on");
|
|
846
|
+
if (ms <= 0) this.stopHoldTimer();
|
|
847
|
+
};
|
|
848
|
+
tick();
|
|
849
|
+
this.holdTimer = setInterval(tick, 500);
|
|
850
|
+
}
|
|
851
|
+
stopHoldTimer() {
|
|
852
|
+
if (this.holdTimer) clearInterval(this.holdTimer);
|
|
853
|
+
this.holdTimer = null;
|
|
854
|
+
this.els.hold?.classList.remove("on");
|
|
855
|
+
}
|
|
856
|
+
toast(msg) {
|
|
857
|
+
const el = this.els.toast;
|
|
858
|
+
if (!el) return;
|
|
859
|
+
el.textContent = msg;
|
|
860
|
+
el.classList.add("on");
|
|
861
|
+
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
862
|
+
this.toastTimer = setTimeout(() => el.classList.remove("on"), 4200);
|
|
863
|
+
}
|
|
864
|
+
placeTooltip() {
|
|
865
|
+
if (!this.tipEl) return;
|
|
866
|
+
const hw = this.els.map.clientWidth;
|
|
867
|
+
const tw = this.tipEl.offsetWidth;
|
|
868
|
+
const th = this.tipEl.offsetHeight;
|
|
869
|
+
let x = this.tipPos.x + 14;
|
|
870
|
+
let y = this.tipPos.y - th - 12;
|
|
871
|
+
if (x + tw > hw - 8) x = this.tipPos.x - tw - 14;
|
|
872
|
+
if (y < 8) y = this.tipPos.y + 18;
|
|
873
|
+
this.tipEl.style.left = `${Math.max(8, x)}px`;
|
|
874
|
+
this.tipEl.style.top = `${Math.max(8, y)}px`;
|
|
875
|
+
}
|
|
876
|
+
updateTooltip(details) {
|
|
877
|
+
if (!this.tipEl) return;
|
|
878
|
+
if (!details) {
|
|
879
|
+
this.tipEl.style.display = "none";
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const statusLine = details.status === "free" ? "" : `<div style="margin-top:5px;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;font-weight:700">${details.status === "held" ? (0, import_core2.t)("map.statusHeld") : (0, import_core2.t)("map.statusTaken")}</div>`;
|
|
883
|
+
this.tipEl.innerHTML = `<div style="font-weight:800;font-size:13px">${details.label}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span style="opacity:.75">${details.categoryLabel}</span><span style="margin-left:auto;font-weight:800">${this.money(details.price)}</span></div>` + statusLine;
|
|
884
|
+
this.tipEl.style.display = "block";
|
|
885
|
+
this.placeTooltip();
|
|
886
|
+
}
|
|
887
|
+
// ---- public conveniences ----------------------------------------------------
|
|
888
|
+
getSelection() {
|
|
889
|
+
return this.controller.getSelection();
|
|
890
|
+
}
|
|
891
|
+
async bestAvailable(qty, categoryKey) {
|
|
892
|
+
try {
|
|
893
|
+
const h = await this.controller.bestAvailable(qty, categoryKey);
|
|
894
|
+
if (h) {
|
|
895
|
+
this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
|
|
896
|
+
this.startHoldTimer(h.expiresAt);
|
|
897
|
+
this.syncTray();
|
|
898
|
+
return this.hold;
|
|
899
|
+
}
|
|
900
|
+
return null;
|
|
901
|
+
} catch (err) {
|
|
902
|
+
this.opts.onError?.(err);
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
async release() {
|
|
907
|
+
await this.controller.release();
|
|
908
|
+
this.hold = null;
|
|
909
|
+
this.stopHoldTimer();
|
|
910
|
+
this.gaQty.clear();
|
|
911
|
+
this.syncTray();
|
|
912
|
+
}
|
|
913
|
+
destroy() {
|
|
914
|
+
this.destroyed = true;
|
|
915
|
+
this.stopHoldTimer();
|
|
916
|
+
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
917
|
+
this.ro?.disconnect();
|
|
918
|
+
this.ro = null;
|
|
919
|
+
if (this.escHandler) document.removeEventListener("keydown", this.escHandler);
|
|
920
|
+
this.controller.destroy();
|
|
921
|
+
this.root?.remove();
|
|
922
|
+
this.root = null;
|
|
923
|
+
if (this.modalScrim) {
|
|
924
|
+
this.modalScrim.remove();
|
|
925
|
+
this.modalScrim = null;
|
|
926
|
+
this.prevFocus?.focus?.();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
};
|
|
404
930
|
// Annotate the CommonJS export names for ESM import in node:
|
|
405
931
|
0 && (module.exports = {
|
|
406
932
|
ApiError,
|
|
407
933
|
EmbeddedDesigner,
|
|
934
|
+
SeatPicker,
|
|
408
935
|
SeatingChart
|
|
409
936
|
});
|
|
410
937
|
//# sourceMappingURL=index.cjs.map
|