@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 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: () => this.reanchorConfirm(),
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><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>`
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();