@seatlayer/js 0.6.0 → 0.7.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 +174 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +175 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -454,6 +454,8 @@ var CSS = `
|
|
|
454
454
|
.sl-picker[data-layout="narrow"] .sl-body{flex-direction:column}
|
|
455
455
|
.sl-picker[data-layout="narrow"] .sl-map{min-height:0;flex:1}
|
|
456
456
|
.sl-picker[data-layout="narrow"] .sl-side{width:100%;max-height:46%;border-left:0;border-top:1px solid var(--sl-line)}
|
|
457
|
+
.sl-picker[data-layout="narrow"] .sl-tray{flex:none}
|
|
458
|
+
.sl-picker[data-layout="narrow"] .sl-foot{position:sticky;bottom:0;background:var(--sl-bg)}
|
|
457
459
|
|
|
458
460
|
/* price panel */
|
|
459
461
|
.sl-sec{padding:14px 16px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
|
|
@@ -518,6 +520,39 @@ var CSS = `
|
|
|
518
520
|
.sl-boot-retry{margin-top:4px;padding:9px 20px;border-radius:var(--sl-r-sm);background:var(--sl-accent);
|
|
519
521
|
color:var(--sl-accent-ink);font-weight:700;font-size:13px}
|
|
520
522
|
|
|
523
|
+
/* a11y filter chips (over the map, top-left) */
|
|
524
|
+
.sl-chips{position:absolute;top:12px;left:12px;z-index:5;display:flex;gap:6px;flex-wrap:wrap;max-width:70%}
|
|
525
|
+
.sl-chip-f{display:inline-flex;align-items:center;gap:6px;padding:7px 12px;border-radius:999px;font-size:12px;font-weight:700;
|
|
526
|
+
background:var(--sl-surface);border:1px solid var(--sl-line);color:var(--sl-muted);transition:color .15s,border-color .15s}
|
|
527
|
+
.sl-chip-f:hover{color:var(--sl-text)}
|
|
528
|
+
.sl-chip-f.on{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
529
|
+
|
|
530
|
+
/* confirm popover */
|
|
531
|
+
.sl-confirm{position:absolute;z-index:9;min-width:190px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
532
|
+
border-radius:12px;padding:12px;box-shadow:0 18px 50px -18px rgba(0,0,0,.7);transform:translate(-50%,calc(-100% - 14px))}
|
|
533
|
+
.sl-confirm-label{font-weight:800;font-size:14px}
|
|
534
|
+
.sl-confirm-meta{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--sl-muted);margin-top:4px}
|
|
535
|
+
.sl-confirm-meta b{color:var(--sl-text);margin-left:auto}
|
|
536
|
+
.sl-confirm-row{display:flex;gap:8px;margin-top:10px}
|
|
537
|
+
.sl-confirm-row button{flex:1;padding:8px;border-radius:8px;font-weight:700;font-size:12.5px}
|
|
538
|
+
.sl-confirm-add{background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
539
|
+
.sl-confirm-cancel{border:1px solid var(--sl-line);color:var(--sl-muted)}
|
|
540
|
+
.sl-confirm-cancel:hover{color:var(--sl-text)}
|
|
541
|
+
|
|
542
|
+
/* best-available row */
|
|
543
|
+
.sl-ba{display:flex;align-items:center;gap:8px;padding:9px 11px;border:1px solid var(--sl-line);border-radius:var(--sl-r-sm)}
|
|
544
|
+
.sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
|
|
545
|
+
font:inherit;font-size:12px;padding:5px 6px;max-width:110px}
|
|
546
|
+
.sl-ba-qty{display:flex;align-items:center;gap:7px}
|
|
547
|
+
.sl-ba-qty button{width:24px;height:24px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
|
|
548
|
+
font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center}
|
|
549
|
+
.sl-ba-qty span{min-width:14px;text-align:center;font-weight:800}
|
|
550
|
+
.sl-ba-go{margin-left:auto;padding:7px 12px;border-radius:999px;border:1px solid var(--sl-line);font-weight:700;font-size:12px;transition:border-color .15s}
|
|
551
|
+
.sl-ba-go:hover{border-color:var(--sl-muted)}
|
|
552
|
+
|
|
553
|
+
/* screen-reader live region */
|
|
554
|
+
.sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
555
|
+
|
|
521
556
|
/* modal host */
|
|
522
557
|
.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
558
|
.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)}
|
|
@@ -562,6 +597,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
562
597
|
this.gaQty = /* @__PURE__ */ new Map();
|
|
563
598
|
this.tipEl = null;
|
|
564
599
|
this.tipPos = { x: 0, y: 0 };
|
|
600
|
+
this.confirmEl = null;
|
|
601
|
+
this.confirmSeat = null;
|
|
602
|
+
this.srEl = null;
|
|
603
|
+
this.a11yFilter = "all";
|
|
604
|
+
this.baQty = 2;
|
|
605
|
+
this.baCat = "";
|
|
565
606
|
// modal plumbing (set by open())
|
|
566
607
|
this.modalScrim = null;
|
|
567
608
|
this.prevFocus = null;
|
|
@@ -591,6 +632,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
591
632
|
this.syncTray();
|
|
592
633
|
this.opts.onHoldExpired?.();
|
|
593
634
|
},
|
|
635
|
+
confirmSelection: options.confirmSelection,
|
|
636
|
+
onSelect: (seat) => {
|
|
637
|
+
if (this.opts.confirmSelection) this.showConfirm(seat);
|
|
638
|
+
},
|
|
639
|
+
onViewChange: () => this.reanchorConfirm(),
|
|
640
|
+
onFocusSeat: (seat) => this.announceSeat(seat),
|
|
594
641
|
onSeatHover: (d) => this.updateTooltip(d),
|
|
595
642
|
onHint: (m) => {
|
|
596
643
|
if (m) this.toast(m);
|
|
@@ -724,10 +771,92 @@ var SeatPicker = class _SeatPicker {
|
|
|
724
771
|
this.els.name.textContent = info.eventName ?? "";
|
|
725
772
|
const when = info.startsAt ? new Date(info.startsAt).toLocaleString(this.opts.locale, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : "";
|
|
726
773
|
this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
|
|
774
|
+
const present = /* @__PURE__ */ new Set();
|
|
775
|
+
if (this.controller.doc) {
|
|
776
|
+
for (const seat of (0, import_core2.expandChart)(this.controller.doc)) {
|
|
777
|
+
for (const type of seat.accessibility ?? []) present.add(type);
|
|
778
|
+
if (seat.accessible && !seat.accessibility?.length) present.add("wheelchair");
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (present.size) {
|
|
782
|
+
const chips = document.createElement("div");
|
|
783
|
+
chips.className = "sl-chips";
|
|
784
|
+
const GLYPH = { wheelchair: "\u267F", companion: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" };
|
|
785
|
+
const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-f="${key}">${label}</button>`;
|
|
786
|
+
chips.innerHTML = mk("all", "All seats") + [...present].map((type) => mk(type, `${GLYPH[type] ? GLYPH[type] + " " : ""}${type[0].toUpperCase()}${type.slice(1).replace(/-/g, " ")}`)).join("");
|
|
787
|
+
this.els.map.appendChild(chips);
|
|
788
|
+
chips.querySelectorAll("button").forEach((btn) => {
|
|
789
|
+
btn.addEventListener("click", () => {
|
|
790
|
+
const f = btn.dataset.f;
|
|
791
|
+
this.a11yFilter = f;
|
|
792
|
+
chips.querySelectorAll("button").forEach((b) => b.classList.toggle("on", b === btn));
|
|
793
|
+
this.controller.setAccessibilityFilter(f === "all" ? null : [f]);
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
const cb = document.createElement("button");
|
|
798
|
+
cb.type = "button";
|
|
799
|
+
cb.setAttribute("aria-label", "Toggle colorblind-friendly colors");
|
|
800
|
+
cb.setAttribute("aria-pressed", String(!!this.opts.colorblindSafe));
|
|
801
|
+
cb.innerHTML = '<svg viewBox="0 0 24 24"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>';
|
|
802
|
+
this.els.zfit.parentElement.appendChild(cb);
|
|
803
|
+
let cbOn = !!this.opts.colorblindSafe;
|
|
804
|
+
cb.addEventListener("click", () => {
|
|
805
|
+
cbOn = !cbOn;
|
|
806
|
+
cb.setAttribute("aria-pressed", String(cbOn));
|
|
807
|
+
this.controller.setColorblindSafe(cbOn);
|
|
808
|
+
});
|
|
809
|
+
this.srEl = document.createElement("div");
|
|
810
|
+
this.srEl.className = "sl-sr";
|
|
811
|
+
this.srEl.setAttribute("aria-live", "polite");
|
|
812
|
+
root.appendChild(this.srEl);
|
|
727
813
|
this.syncPrices();
|
|
728
814
|
this.syncTray();
|
|
729
815
|
return this;
|
|
730
816
|
}
|
|
817
|
+
/** aria-live readout when keyboard focus lands on a seat. */
|
|
818
|
+
announceSeat(seat) {
|
|
819
|
+
if (!this.srEl) return;
|
|
820
|
+
if (!seat) {
|
|
821
|
+
this.srEl.textContent = "";
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
|
|
825
|
+
const status = this.controller.getStatus(seat.id) ?? "free";
|
|
826
|
+
const statusText = status === "free" ? "available" : status === "held" ? "on hold" : "taken";
|
|
827
|
+
const price = cat?.tiers?.length ? cat.tiers[0].price : cat?.price;
|
|
828
|
+
this.srEl.textContent = `Seat ${seat.label}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
|
|
829
|
+
}
|
|
830
|
+
// ---- confirm popover (opt-in confirmSelection mode) ------------------------
|
|
831
|
+
showConfirm(seat) {
|
|
832
|
+
this.closeConfirm();
|
|
833
|
+
if (this.tipEl) this.tipEl.style.display = "none";
|
|
834
|
+
const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
|
|
835
|
+
const price = cat?.tiers?.length ? cat.tiers[0].price : cat?.price;
|
|
836
|
+
const el = document.createElement("div");
|
|
837
|
+
el.className = "sl-confirm";
|
|
838
|
+
el.innerHTML = `<div class="sl-confirm-label">${seat.label}</div><div class="sl-confirm-meta"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span>${cat?.label ?? seat.categoryKey}${price != null ? `<b>${this.money(price)}</b>` : ""}</div><div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add">Add seat</button></div>`;
|
|
839
|
+
this.els.map.appendChild(el);
|
|
840
|
+
this.confirmEl = el;
|
|
841
|
+
this.confirmSeat = seat;
|
|
842
|
+
this.reanchorConfirm();
|
|
843
|
+
el.querySelector(".sl-confirm-add").addEventListener("click", () => this.closeConfirm());
|
|
844
|
+
el.querySelector(".sl-confirm-cancel").addEventListener("click", () => {
|
|
845
|
+
this.controller.deselect([seat.id]);
|
|
846
|
+
this.closeConfirm();
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
reanchorConfirm() {
|
|
850
|
+
if (!this.confirmEl || !this.confirmSeat) return;
|
|
851
|
+
const p = this.controller.worldToScreen({ x: this.confirmSeat.x, y: this.confirmSeat.y });
|
|
852
|
+
this.confirmEl.style.left = `${p.x}px`;
|
|
853
|
+
this.confirmEl.style.top = `${p.y}px`;
|
|
854
|
+
}
|
|
855
|
+
closeConfirm() {
|
|
856
|
+
this.confirmEl?.remove();
|
|
857
|
+
this.confirmEl = null;
|
|
858
|
+
this.confirmSeat = null;
|
|
859
|
+
}
|
|
731
860
|
// ---- chrome sync ----------------------------------------------------------
|
|
732
861
|
money(n) {
|
|
733
862
|
try {
|
|
@@ -742,8 +871,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
742
871
|
const left = this.controller.categoryAvailability();
|
|
743
872
|
this.els.prices.innerHTML = doc.categories.map((c) => {
|
|
744
873
|
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>`;
|
|
874
|
+
return `<div class="sl-price-row" data-cat="${c.key}"><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
875
|
}).join("");
|
|
876
|
+
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
877
|
+
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
878
|
+
row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
|
|
879
|
+
});
|
|
747
880
|
}
|
|
748
881
|
/** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
|
|
749
882
|
evictTakenSelections() {
|
|
@@ -757,13 +890,21 @@ var SeatPicker = class _SeatPicker {
|
|
|
757
890
|
if (!this.els.tray) return;
|
|
758
891
|
const seats = this.controller.getSelection();
|
|
759
892
|
const gaAreas = this.controller.getGAAreas();
|
|
893
|
+
const heldItems = this.hold?.items ?? [];
|
|
760
894
|
const parts = [];
|
|
761
|
-
if (!seats.length && !gaAreas.length) {
|
|
895
|
+
if (!seats.length && !heldItems.length && !gaAreas.length) {
|
|
762
896
|
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) {
|
|
897
|
+
} else if (!seats.length && !heldItems.length) {
|
|
764
898
|
parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
|
|
765
899
|
}
|
|
766
|
-
for (const
|
|
900
|
+
for (const item of heldItems) {
|
|
901
|
+
const cat = this.controller.doc?.categories.find((c) => c.key === item.categoryKey);
|
|
902
|
+
parts.push(
|
|
903
|
+
`<div class="sl-chip"><b>${item.label}</b><span class="cat">${cat?.label ?? item.categoryKey}</span><span class="amt">${this.money(item.unitPrice * (item.quantity ?? 1))}</span></div>`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
907
|
+
for (const s of seats.filter((seat) => !heldLabels.has(seat.label))) {
|
|
767
908
|
const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
|
|
768
909
|
parts.push(
|
|
769
910
|
`<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>`
|
|
@@ -775,7 +916,25 @@ var SeatPicker = class _SeatPicker {
|
|
|
775
916
|
`<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
917
|
);
|
|
777
918
|
}
|
|
919
|
+
if (!this.hold) {
|
|
920
|
+
const cats = this.controller.doc?.categories ?? [];
|
|
921
|
+
parts.push(
|
|
922
|
+
`<div class="sl-ba">` + (cats.length > 1 ? `<select aria-label="Category" data-ba-cat><option value="">Any tier</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : "") + `<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">Best available</button></div>`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
778
925
|
this.els.tray.innerHTML = parts.join("");
|
|
926
|
+
this.els.tray.querySelectorAll("[data-ba]").forEach((btn) => {
|
|
927
|
+
btn.addEventListener("click", () => {
|
|
928
|
+
this.baQty = Math.max(1, Math.min(8, this.baQty + Number(btn.dataset.ba)));
|
|
929
|
+
this.syncTray();
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
this.els.tray.querySelector("[data-ba-cat]")?.addEventListener("change", (e) => {
|
|
933
|
+
this.baCat = e.target.value;
|
|
934
|
+
});
|
|
935
|
+
this.els.tray.querySelector(".sl-ba-go")?.addEventListener("click", () => {
|
|
936
|
+
void this.bestAvailable(this.baQty, this.baCat || void 0);
|
|
937
|
+
});
|
|
779
938
|
this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
|
|
780
939
|
btn.addEventListener("click", () => {
|
|
781
940
|
const id = btn.closest(".sl-chip").dataset.seat;
|
|
@@ -794,17 +953,24 @@ var SeatPicker = class _SeatPicker {
|
|
|
794
953
|
});
|
|
795
954
|
const gaTotal = gaAreas.reduce((sum, a) => sum + a.price * (this.gaQty.get(a.id) ?? 0), 0);
|
|
796
955
|
const gaCount = [...this.gaQty.values()].reduce((a, b) => a + b, 0);
|
|
797
|
-
const
|
|
798
|
-
const
|
|
956
|
+
const heldTotal = heldItems.reduce((sum, item) => sum + item.unitPrice * (item.quantity ?? 1), 0);
|
|
957
|
+
const heldCount = heldItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
|
|
958
|
+
const freshSeats = seats.filter((seat) => !heldLabels.has(seat.label));
|
|
959
|
+
const total = freshSeats.reduce((sum, s) => sum + s.price, 0) + gaTotal + heldTotal;
|
|
960
|
+
const count = freshSeats.length + gaCount + heldCount;
|
|
799
961
|
this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
|
|
800
962
|
this.els.total.textContent = count ? this.money(total) : "";
|
|
801
963
|
const cta = this.els.cta;
|
|
802
964
|
cta.disabled = count === 0;
|
|
803
|
-
cta.textContent = count ? "Hold seats & checkout" : "Select seats";
|
|
965
|
+
cta.textContent = this.hold ? "Continue to checkout" : count ? "Hold seats & checkout" : "Select seats";
|
|
804
966
|
this.opts.onSelectionChange?.(seats);
|
|
805
967
|
}
|
|
806
968
|
async handleCta() {
|
|
807
969
|
const cta = this.els.cta;
|
|
970
|
+
if (this.hold && !this.controller.getSelection().some((s) => !(this.hold.items ?? []).some((i) => i.label === s.label))) {
|
|
971
|
+
this.opts.onCheckout?.(this.hold, this.controller.getSelection());
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
808
974
|
cta.disabled = true;
|
|
809
975
|
cta.textContent = "Holding\u2026";
|
|
810
976
|
try {
|
|
@@ -912,6 +1078,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
912
1078
|
}
|
|
913
1079
|
destroy() {
|
|
914
1080
|
this.destroyed = true;
|
|
1081
|
+
this.closeConfirm();
|
|
915
1082
|
this.stopHoldTimer();
|
|
916
1083
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
917
1084
|
this.ro?.disconnect();
|