@seatlayer/js 0.7.3 → 0.8.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 +315 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +318 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -553,6 +553,79 @@ var CSS = `
|
|
|
553
553
|
/* screen-reader live region */
|
|
554
554
|
.sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
555
555
|
|
|
556
|
+
/* per-seat ticket-tier select + view-from-seat button in tray chips */
|
|
557
|
+
.sl-chip .tier{background:var(--sl-bg);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
|
|
558
|
+
font:inherit;font-size:11px;padding:3px 5px;max-width:130px;cursor:pointer}
|
|
559
|
+
.sl-chip .view{width:24px;height:24px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
|
|
560
|
+
color:var(--sl-muted);transition:color .15s}
|
|
561
|
+
.sl-chip .view:hover{color:var(--sl-text)}
|
|
562
|
+
.sl-chip .view svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
563
|
+
|
|
564
|
+
/* arena: LOD rung pills (top-center over the map) */
|
|
565
|
+
.sl-rungs{position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:5;display:none;
|
|
566
|
+
background:var(--sl-surface);border:1px solid var(--sl-line);border-radius:999px;padding:3px}
|
|
567
|
+
.sl-rungs.on{display:inline-flex;gap:2px}
|
|
568
|
+
.sl-rungs button{padding:6px 13px;border-radius:999px;font-size:10.5px;font-weight:800;letter-spacing:.07em;
|
|
569
|
+
color:var(--sl-muted);white-space:nowrap;transition:color .15s}
|
|
570
|
+
.sl-rungs button:hover{color:var(--sl-text)}
|
|
571
|
+
.sl-rungs button.on{background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
572
|
+
|
|
573
|
+
/* multi-floor switcher (center-left rail over the map) */
|
|
574
|
+
.sl-floors{position:absolute;top:50%;left:12px;transform:translateY(-50%);z-index:5;display:none;
|
|
575
|
+
flex-direction:column;gap:6px;max-width:42%}
|
|
576
|
+
.sl-floors.on{display:flex}
|
|
577
|
+
.sl-floors button{padding:7px 13px;border-radius:999px;font-size:12px;font-weight:700;background:var(--sl-surface);
|
|
578
|
+
border:1px solid var(--sl-line);color:var(--sl-muted);white-space:nowrap;max-width:100%;overflow:hidden;
|
|
579
|
+
text-overflow:ellipsis;transition:color .15s,border-color .15s}
|
|
580
|
+
.sl-floors button:hover{color:var(--sl-text)}
|
|
581
|
+
.sl-floors button.on{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
582
|
+
|
|
583
|
+
/* tapped-section summary card (top-center, under the rung pills) */
|
|
584
|
+
.sl-seccard{position:absolute;top:54px;left:50%;transform:translateX(-50%);z-index:6;width:250px;
|
|
585
|
+
max-width:calc(100% - 24px);background:var(--sl-surface);border:1px solid var(--sl-line);border-radius:12px;
|
|
586
|
+
padding:12px 14px;box-shadow:0 18px 50px -18px rgba(0,0,0,.6);display:none}
|
|
587
|
+
.sl-seccard.on{display:block}
|
|
588
|
+
.sl-seccard-head{display:flex;align-items:center;gap:8px}
|
|
589
|
+
.sl-seccard-dot{width:10px;height:10px;border-radius:50%;flex:none}
|
|
590
|
+
.sl-seccard-name{font-weight:800;font-size:14px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
591
|
+
.sl-seccard-price{font-weight:800;font-size:12.5px;font-variant-numeric:tabular-nums}
|
|
592
|
+
.sl-seccard-x{width:22px;height:22px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
|
|
593
|
+
color:var(--sl-muted);font-size:12px}
|
|
594
|
+
.sl-seccard-x:hover{color:var(--sl-text)}
|
|
595
|
+
.sl-seccard-zone{font-size:11.5px;color:var(--sl-muted);margin-top:6px}
|
|
596
|
+
.sl-seccard-left{color:var(--sl-text);font-weight:700}
|
|
597
|
+
.sl-seccard-mix{display:flex;flex-wrap:wrap;gap:6px 10px;margin-top:8px}
|
|
598
|
+
.sl-seccard-mix-item{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--sl-muted)}
|
|
599
|
+
.sl-seccard-mix-dot{width:8px;height:8px;border-radius:50%;flex:none}
|
|
600
|
+
.sl-seccard-mix-price{font-weight:700;color:var(--sl-text)}
|
|
601
|
+
.sl-seccard-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}
|
|
602
|
+
.sl-seccard-overview{font-size:12px;font-weight:800;color:var(--sl-accent)}
|
|
603
|
+
.sl-seccard-hint{font-size:10.5px;color:var(--sl-muted)}
|
|
604
|
+
|
|
605
|
+
/* view-from-seat button on the confirm popover */
|
|
606
|
+
.sl-confirm-view{width:100%;margin-top:9px;padding:8px;border-radius:8px;border:1px solid var(--sl-line);
|
|
607
|
+
color:var(--sl-text);font-weight:700;font-size:12px;display:flex;align-items:center;justify-content:center;gap:7px}
|
|
608
|
+
.sl-confirm-view:hover{border-color:var(--sl-muted)}
|
|
609
|
+
.sl-confirm-view svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
610
|
+
|
|
611
|
+
/* 360\xB0 seat-view modal (fills the widget; drag-to-look-around equirectangular) */
|
|
612
|
+
.sl-view{position:absolute;inset:0;z-index:12;display:flex;flex-direction:column;background:var(--sl-bg)}
|
|
613
|
+
.sl-view-head{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
|
|
614
|
+
.sl-view-title{font-weight:800;font-size:15px}
|
|
615
|
+
.sl-view-cap{font-size:11px;color:var(--sl-muted)}
|
|
616
|
+
.sl-view-x{margin-left:auto;width:32px;height:32px;border-radius:999px;border:1px solid var(--sl-line);color:var(--sl-muted);
|
|
617
|
+
flex:none;display:flex;align-items:center;justify-content:center;transition:color .15s,border-color .15s}
|
|
618
|
+
.sl-view-x:hover{color:var(--sl-text);border-color:var(--sl-muted)}
|
|
619
|
+
.sl-view-x svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round}
|
|
620
|
+
.sl-view-pano{position:relative;flex:1;min-height:0;overflow:hidden;cursor:grab;background-color:#05070c;
|
|
621
|
+
background-repeat:repeat-x;touch-action:none;user-select:none}
|
|
622
|
+
.sl-view-pano.drag{cursor:grabbing}
|
|
623
|
+
.sl-view-badge{position:absolute;top:12px;left:12px;padding:5px 11px;border-radius:999px;font-size:10px;font-weight:800;
|
|
624
|
+
letter-spacing:.08em;background:var(--sl-surface);border:1px solid var(--sl-line);color:var(--sl-muted)}
|
|
625
|
+
.sl-view-hint{position:absolute;left:50%;bottom:12px;transform:translateX(-50%);padding:6px 14px;border-radius:999px;
|
|
626
|
+
font-size:11.5px;font-weight:600;background:var(--sl-surface);border:1px solid var(--sl-line);color:var(--sl-muted);
|
|
627
|
+
white-space:nowrap;pointer-events:none;max-width:90%;overflow:hidden;text-overflow:ellipsis}
|
|
628
|
+
|
|
556
629
|
/* modal host */
|
|
557
630
|
.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}
|
|
558
631
|
.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)}
|
|
@@ -603,6 +676,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
603
676
|
this.a11yFilter = "all";
|
|
604
677
|
this.baQty = 2;
|
|
605
678
|
this.baCat = "";
|
|
679
|
+
// arena / multi-floor / seat-view chrome
|
|
680
|
+
this.rungsEl = null;
|
|
681
|
+
this.floorsEl = null;
|
|
682
|
+
this.secCardEl = null;
|
|
683
|
+
this.viewEl = null;
|
|
684
|
+
this.viewCleanup = null;
|
|
685
|
+
this.allSeatsCache = null;
|
|
606
686
|
// modal plumbing (set by open())
|
|
607
687
|
this.modalScrim = null;
|
|
608
688
|
this.prevFocus = null;
|
|
@@ -638,7 +718,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
638
718
|
onSelect: (seat) => {
|
|
639
719
|
if (this.opts.confirmSelection) this.showConfirm(seat);
|
|
640
720
|
},
|
|
641
|
-
onViewChange: () =>
|
|
721
|
+
onViewChange: () => {
|
|
722
|
+
this.reanchorConfirm();
|
|
723
|
+
this.syncRung();
|
|
724
|
+
},
|
|
725
|
+
// Tapped-section glide-in → surface (or clear) the section-summary card.
|
|
726
|
+
onSectionFocus: (summary) => this.showSectionCard(summary),
|
|
642
727
|
onFocusSeat: (seat) => this.announceSeat(seat),
|
|
643
728
|
onSeatHover: (d) => this.updateTooltip(d),
|
|
644
729
|
onHint: (m) => {
|
|
@@ -830,10 +915,104 @@ var SeatPicker = class _SeatPicker {
|
|
|
830
915
|
this.srEl.className = "sl-sr";
|
|
831
916
|
this.srEl.setAttribute("aria-live", "polite");
|
|
832
917
|
root.appendChild(this.srEl);
|
|
918
|
+
this.buildArenaChrome();
|
|
833
919
|
this.syncPrices();
|
|
834
920
|
this.syncTray();
|
|
835
921
|
return this;
|
|
836
922
|
}
|
|
923
|
+
// ---- arena / multi-floor chrome -------------------------------------------
|
|
924
|
+
/** Build the rung pills (charts with sections) and floor switcher (>1 floor). */
|
|
925
|
+
buildArenaChrome() {
|
|
926
|
+
const doc = this.controller.doc;
|
|
927
|
+
if (!doc || !this.els.map) return;
|
|
928
|
+
const hasSections = doc.objects.some((o) => o.type === "section") || (doc.floors ?? []).some((f) => f.objects.some((o) => o.type === "section"));
|
|
929
|
+
if (hasSections) {
|
|
930
|
+
const RUNGS = ["zones", "sections", "seats"];
|
|
931
|
+
const pills = document.createElement("div");
|
|
932
|
+
pills.className = "sl-rungs on";
|
|
933
|
+
pills.setAttribute("role", "group");
|
|
934
|
+
pills.setAttribute("aria-label", (0, import_core2.t)("picker.zoomLevel"));
|
|
935
|
+
const LABEL = {
|
|
936
|
+
zones: (0, import_core2.t)("picker.rungLabel.zones"),
|
|
937
|
+
sections: (0, import_core2.t)("picker.rungLabel.sections"),
|
|
938
|
+
seats: (0, import_core2.t)("picker.rungLabel.seats")
|
|
939
|
+
};
|
|
940
|
+
const TIP = {
|
|
941
|
+
zones: (0, import_core2.t)("picker.rungTip.zones"),
|
|
942
|
+
sections: (0, import_core2.t)("picker.rungTip.sections"),
|
|
943
|
+
seats: (0, import_core2.t)("picker.rungTip.seats")
|
|
944
|
+
};
|
|
945
|
+
pills.innerHTML = RUNGS.map(
|
|
946
|
+
(r) => `<button type="button" data-rung="${r}" title="${TIP[r]}" aria-pressed="false">${LABEL[r]}</button>`
|
|
947
|
+
).join("");
|
|
948
|
+
pills.querySelectorAll("button").forEach((btn) => {
|
|
949
|
+
btn.addEventListener("click", () => this.controller.setRung(btn.dataset.rung));
|
|
950
|
+
});
|
|
951
|
+
this.els.map.appendChild(pills);
|
|
952
|
+
this.rungsEl = pills;
|
|
953
|
+
this.syncRung();
|
|
954
|
+
}
|
|
955
|
+
if (this.controller.isMultiFloor()) {
|
|
956
|
+
const floors = this.controller.getFloors();
|
|
957
|
+
const rail = document.createElement("div");
|
|
958
|
+
rail.className = "sl-floors on";
|
|
959
|
+
rail.setAttribute("role", "group");
|
|
960
|
+
rail.setAttribute("aria-label", (0, import_core2.t)("picker.floor"));
|
|
961
|
+
rail.innerHTML = floors.map((f) => `<button type="button" data-floor="${f.id}">${f.name}</button>`).join("");
|
|
962
|
+
rail.querySelectorAll("button").forEach((btn) => {
|
|
963
|
+
btn.addEventListener("click", () => {
|
|
964
|
+
this.controller.setFloor(btn.dataset.floor);
|
|
965
|
+
this.showSectionCard(null);
|
|
966
|
+
this.syncFloors();
|
|
967
|
+
this.syncRung();
|
|
968
|
+
});
|
|
969
|
+
});
|
|
970
|
+
this.els.map.appendChild(rail);
|
|
971
|
+
this.floorsEl = rail;
|
|
972
|
+
this.syncFloors();
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
/** Reflect the engine's current LOD rung onto the pill group. */
|
|
976
|
+
syncRung() {
|
|
977
|
+
if (!this.rungsEl) return;
|
|
978
|
+
const active = this.controller.getRung();
|
|
979
|
+
this.rungsEl.querySelectorAll("button").forEach((btn) => {
|
|
980
|
+
const on = btn.dataset.rung === active;
|
|
981
|
+
btn.classList.toggle("on", on);
|
|
982
|
+
btn.setAttribute("aria-pressed", String(on));
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
/** Reflect the active floor onto the switcher rail. */
|
|
986
|
+
syncFloors() {
|
|
987
|
+
if (!this.floorsEl) return;
|
|
988
|
+
const active = this.controller.getActiveFloorId();
|
|
989
|
+
this.floorsEl.querySelectorAll("button").forEach((btn) => {
|
|
990
|
+
btn.classList.toggle("on", btn.dataset.floor === active);
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
/** Show (or clear, on null) the tapped-section summary card. */
|
|
994
|
+
showSectionCard(summary) {
|
|
995
|
+
if (!summary) {
|
|
996
|
+
this.secCardEl?.remove();
|
|
997
|
+
this.secCardEl = null;
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (!this.els.map) return;
|
|
1001
|
+
this.secCardEl?.remove();
|
|
1002
|
+
const priceLabel = summary.priceMin === summary.priceMax ? this.money(summary.priceMin) : `${this.money(summary.priceMin)}\u2013${this.money(summary.priceMax)}`;
|
|
1003
|
+
const card = document.createElement("div");
|
|
1004
|
+
card.className = "sl-seccard on";
|
|
1005
|
+
card.setAttribute("role", "dialog");
|
|
1006
|
+
card.setAttribute("aria-label", (0, import_core2.t)("picker.sectionSummaryAria", { label: summary.label }));
|
|
1007
|
+
const mix = summary.categories.map(
|
|
1008
|
+
(c) => `<span class="sl-seccard-mix-item"><span class="sl-seccard-mix-dot" style="background:${c.color}"></span>${c.label} <span class="sl-seccard-mix-price">${this.money(c.price)}</span></span>`
|
|
1009
|
+
).join("");
|
|
1010
|
+
card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + `<button type="button" class="sl-seccard-x" aria-label="${(0, import_core2.t)("picker.closeSectionSummary")}">\u2715</button></div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${(0, import_core2.tCount)("picker.seatsLeftInSection", summary.seatsLeft)}</span></div>` + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${(0, import_core2.t)("picker.overview")}</button><span class="sl-seccard-hint">${(0, import_core2.t)("picker.tapSeatHint")}</span></div>`;
|
|
1011
|
+
card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
|
|
1012
|
+
card.querySelector(".sl-seccard-overview").addEventListener("click", () => this.controller.overview());
|
|
1013
|
+
this.els.map.appendChild(card);
|
|
1014
|
+
this.secCardEl = card;
|
|
1015
|
+
}
|
|
837
1016
|
/** aria-live readout when keyboard focus lands on a seat. */
|
|
838
1017
|
announceSeat(seat) {
|
|
839
1018
|
if (!this.srEl) return;
|
|
@@ -855,11 +1034,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
855
1034
|
const price = cat?.tiers?.length ? cat.tiers[0].price : cat?.price;
|
|
856
1035
|
const el = document.createElement("div");
|
|
857
1036
|
el.className = "sl-confirm";
|
|
858
|
-
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>`;
|
|
1037
|
+
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>` + (this.seatViewEnabled() ? `<button type="button" class="sl-confirm-view"><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>${(0, import_core2.t)("picker.open360")}</button>` : "") + `<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>`;
|
|
859
1038
|
this.els.map.appendChild(el);
|
|
860
1039
|
this.confirmEl = el;
|
|
861
1040
|
this.confirmSeat = seat;
|
|
862
1041
|
this.reanchorConfirm();
|
|
1042
|
+
el.querySelector(".sl-confirm-view")?.addEventListener("click", () => this.openSeatView(seat));
|
|
863
1043
|
el.querySelector(".sl-confirm-add").addEventListener("click", () => this.closeConfirm());
|
|
864
1044
|
el.querySelector(".sl-confirm-cancel").addEventListener("click", () => {
|
|
865
1045
|
this.controller.deselect([seat.id]);
|
|
@@ -877,6 +1057,122 @@ var SeatPicker = class _SeatPicker {
|
|
|
877
1057
|
this.confirmEl = null;
|
|
878
1058
|
this.confirmSeat = null;
|
|
879
1059
|
}
|
|
1060
|
+
// ---- 360° view-from-seat modal --------------------------------------------
|
|
1061
|
+
seatViewEnabled() {
|
|
1062
|
+
return this.opts.seatView !== false;
|
|
1063
|
+
}
|
|
1064
|
+
/** Every bookable seat (cached) — neighbor heads for the generated panorama. */
|
|
1065
|
+
allSeats() {
|
|
1066
|
+
if (!this.allSeatsCache) {
|
|
1067
|
+
const doc = this.controller.doc;
|
|
1068
|
+
this.allSeatsCache = doc ? (0, import_core2.expandChart)(doc) : [];
|
|
1069
|
+
}
|
|
1070
|
+
return this.allSeatsCache;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Open the drag-to-look-around 360° preview for a seat. Uses the organizer's
|
|
1074
|
+
* uploaded photo (seat.viewUrl) when present, else a panorama generated from
|
|
1075
|
+
* the chart geometry — the stage placed at this seat's true bearing + size.
|
|
1076
|
+
* Zero extra dependencies: an equirectangular image panned with `repeat-x`.
|
|
1077
|
+
*/
|
|
1078
|
+
openSeatView(seat) {
|
|
1079
|
+
if (!this.root || !this.seatViewEnabled()) return;
|
|
1080
|
+
this.closeSeatView();
|
|
1081
|
+
this.closeConfirm();
|
|
1082
|
+
const doc = this.controller.doc;
|
|
1083
|
+
const activeId = this.controller.getActiveFloorId();
|
|
1084
|
+
const focal = doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
1085
|
+
let panoUrl;
|
|
1086
|
+
let caption;
|
|
1087
|
+
let real = false;
|
|
1088
|
+
if (seat.viewUrl) {
|
|
1089
|
+
panoUrl = seat.viewUrl;
|
|
1090
|
+
caption = (0, import_core2.t)("picker.panorama360");
|
|
1091
|
+
real = true;
|
|
1092
|
+
} else {
|
|
1093
|
+
const pano2 = (0, import_core2.generateSeatPanorama)(seat, focal, this.allSeats());
|
|
1094
|
+
panoUrl = pano2.url;
|
|
1095
|
+
caption = (0, import_core2.t)("picker.illustrationCaption", { m: pano2.distanceM });
|
|
1096
|
+
}
|
|
1097
|
+
const el = document.createElement("div");
|
|
1098
|
+
el.className = "sl-view";
|
|
1099
|
+
el.setAttribute("role", "dialog");
|
|
1100
|
+
el.setAttribute("aria-label", (0, import_core2.t)("picker.viewFromSeat", { label: seat.label }));
|
|
1101
|
+
el.innerHTML = `<div class="sl-view-head"><span class="sl-view-title">${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}</span><span class="sl-view-cap">${caption}</span><button type="button" class="sl-view-x" aria-label="Close"><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><div class="sl-view-pano"><span class="sl-view-badge">${real ? (0, import_core2.t)("picker.real360") : (0, import_core2.t)("picker.preview")}</span><span class="sl-view-hint">Drag to look around \xB7 scroll to zoom</span></div>`;
|
|
1102
|
+
this.root.appendChild(el);
|
|
1103
|
+
this.viewEl = el;
|
|
1104
|
+
const pano = el.querySelector(".sl-view-pano");
|
|
1105
|
+
pano.style.backgroundImage = `url("${panoUrl}")`;
|
|
1106
|
+
let zoom = 1.2;
|
|
1107
|
+
let posX = 0;
|
|
1108
|
+
let posY = 0;
|
|
1109
|
+
const apply = () => {
|
|
1110
|
+
const h = pano.clientHeight || 1;
|
|
1111
|
+
const bgH = h * zoom;
|
|
1112
|
+
const overV = Math.max(0, bgH - h);
|
|
1113
|
+
posY = Math.min(overV / 2, Math.max(-overV / 2, posY));
|
|
1114
|
+
pano.style.backgroundSize = `auto ${bgH}px`;
|
|
1115
|
+
pano.style.backgroundPosition = `${posX}px ${posY + overV / 2}px`;
|
|
1116
|
+
};
|
|
1117
|
+
apply();
|
|
1118
|
+
let dragging = false;
|
|
1119
|
+
let lastX = 0;
|
|
1120
|
+
let lastY = 0;
|
|
1121
|
+
const onDown = (e) => {
|
|
1122
|
+
dragging = true;
|
|
1123
|
+
lastX = e.clientX;
|
|
1124
|
+
lastY = e.clientY;
|
|
1125
|
+
pano.classList.add("drag");
|
|
1126
|
+
pano.setPointerCapture?.(e.pointerId);
|
|
1127
|
+
};
|
|
1128
|
+
const onMove = (e) => {
|
|
1129
|
+
if (!dragging) return;
|
|
1130
|
+
posX += e.clientX - lastX;
|
|
1131
|
+
posY += e.clientY - lastY;
|
|
1132
|
+
lastX = e.clientX;
|
|
1133
|
+
lastY = e.clientY;
|
|
1134
|
+
apply();
|
|
1135
|
+
};
|
|
1136
|
+
const onUp = (e) => {
|
|
1137
|
+
dragging = false;
|
|
1138
|
+
pano.classList.remove("drag");
|
|
1139
|
+
pano.releasePointerCapture?.(e.pointerId);
|
|
1140
|
+
};
|
|
1141
|
+
const onWheel = (e) => {
|
|
1142
|
+
e.preventDefault();
|
|
1143
|
+
zoom = Math.min(2.4, Math.max(1, zoom + (e.deltaY < 0 ? 0.12 : -0.12)));
|
|
1144
|
+
apply();
|
|
1145
|
+
};
|
|
1146
|
+
pano.addEventListener("pointerdown", onDown);
|
|
1147
|
+
pano.addEventListener("pointermove", onMove);
|
|
1148
|
+
pano.addEventListener("pointerup", onUp);
|
|
1149
|
+
pano.addEventListener("pointercancel", onUp);
|
|
1150
|
+
pano.addEventListener("wheel", onWheel, { passive: false });
|
|
1151
|
+
const closeBtn = el.querySelector(".sl-view-x");
|
|
1152
|
+
closeBtn.addEventListener("click", () => this.closeSeatView());
|
|
1153
|
+
const onKey = (e) => {
|
|
1154
|
+
if (e.key === "Escape") {
|
|
1155
|
+
e.stopPropagation();
|
|
1156
|
+
this.closeSeatView();
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
el.addEventListener("keydown", onKey);
|
|
1160
|
+
closeBtn.focus();
|
|
1161
|
+
this.viewCleanup = () => {
|
|
1162
|
+
pano.removeEventListener("pointerdown", onDown);
|
|
1163
|
+
pano.removeEventListener("pointermove", onMove);
|
|
1164
|
+
pano.removeEventListener("pointerup", onUp);
|
|
1165
|
+
pano.removeEventListener("pointercancel", onUp);
|
|
1166
|
+
pano.removeEventListener("wheel", onWheel);
|
|
1167
|
+
el.removeEventListener("keydown", onKey);
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
closeSeatView() {
|
|
1171
|
+
this.viewCleanup?.();
|
|
1172
|
+
this.viewCleanup = null;
|
|
1173
|
+
this.viewEl?.remove();
|
|
1174
|
+
this.viewEl = null;
|
|
1175
|
+
}
|
|
880
1176
|
// ---- chrome sync ----------------------------------------------------------
|
|
881
1177
|
money(n) {
|
|
882
1178
|
try {
|
|
@@ -919,15 +1215,20 @@ var SeatPicker = class _SeatPicker {
|
|
|
919
1215
|
}
|
|
920
1216
|
for (const item of heldItems) {
|
|
921
1217
|
const cat = this.controller.doc?.categories.find((c) => c.key === item.categoryKey);
|
|
1218
|
+
const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
|
|
1219
|
+
const canView2 = this.seatViewEnabled() && item.objectType !== "ga" && !!this.controller.seatByLabel(item.label);
|
|
922
1220
|
parts.push(
|
|
923
|
-
`<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>`
|
|
1221
|
+
`<div class="sl-chip"><b>${item.label}</b><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span><span class="amt">${this.money(item.unitPrice * (item.quantity ?? 1))}</span>` + (canView2 ? `<button type="button" class="view" data-view-label="${item.label}" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: item.label })}"><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></button>` : "") + `</div>`
|
|
924
1222
|
);
|
|
925
1223
|
}
|
|
926
1224
|
const heldLabels = new Set(heldItems.map((item) => item.label));
|
|
1225
|
+
const canView = this.seatViewEnabled();
|
|
927
1226
|
for (const s of seats.filter((seat) => !heldLabels.has(seat.label))) {
|
|
928
1227
|
const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
|
|
1228
|
+
const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${(0, import_core2.t)("picker.ticketTierFor", { label: s.label })}">` + s.tiers.map((ti) => `<option value="${ti.id}"${ti.id === s.tierId ? " selected" : ""}>${ti.name} \xB7 ${this.money(ti.price)}</option>`).join("") + `</select>` : "";
|
|
1229
|
+
const viewBtn = canView ? `<button type="button" class="view" data-view-label="${s.label}" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: s.label })}"><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></button>` : "";
|
|
929
1230
|
parts.push(
|
|
930
|
-
`<div class="sl-chip" data-seat="${s.id}"><b>${s.label}</b><span class="cat">${cat?.label ?? s.categoryKey}</span
|
|
1231
|
+
`<div class="sl-chip" data-seat="${s.id}"><b>${s.label}</b><span class="cat">${cat?.label ?? s.categoryKey}</span>` + tierSelect + `<span class="amt">${this.money(s.price)}</span>` + viewBtn + `<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>`
|
|
931
1232
|
);
|
|
932
1233
|
}
|
|
933
1234
|
for (const area of gaAreas) {
|
|
@@ -961,6 +1262,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
961
1262
|
this.controller.deselect([id]);
|
|
962
1263
|
});
|
|
963
1264
|
});
|
|
1265
|
+
this.els.tray.querySelectorAll(".sl-chip .tier").forEach((sel) => {
|
|
1266
|
+
sel.addEventListener("change", () => this.controller.setSeatTier(sel.dataset.tier, sel.value || null));
|
|
1267
|
+
});
|
|
1268
|
+
this.els.tray.querySelectorAll(".sl-chip .view[data-view-label]").forEach((btn) => {
|
|
1269
|
+
btn.addEventListener("click", () => {
|
|
1270
|
+
const seat = this.controller.seatByLabel(btn.dataset.viewLabel);
|
|
1271
|
+
if (seat) this.openSeatView(seat);
|
|
1272
|
+
});
|
|
1273
|
+
});
|
|
964
1274
|
this.els.tray.querySelectorAll(".sl-ga button").forEach((btn) => {
|
|
965
1275
|
btn.addEventListener("click", () => {
|
|
966
1276
|
const areaEl = btn.closest(".sl-ga");
|
|
@@ -1099,6 +1409,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
1099
1409
|
destroy() {
|
|
1100
1410
|
this.destroyed = true;
|
|
1101
1411
|
this.closeConfirm();
|
|
1412
|
+
this.closeSeatView();
|
|
1102
1413
|
this.stopHoldTimer();
|
|
1103
1414
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
1104
1415
|
this.ro?.disconnect();
|