@seatlayer/js 0.30.1 → 0.31.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/README.md CHANGED
@@ -208,6 +208,7 @@ The browser **holds**; your **server books** with a secret key. See
208
208
  - [Holds and checkout](https://docs.seatlayer.io/buyer-sdk/holds-and-checkout/)
209
209
  - [Complete checkout example](https://docs.seatlayer.io/examples/complete-checkout/)
210
210
  - [`@seatlayer/react`](https://www.npmjs.com/package/@seatlayer/react)
211
+ - [React Native SDK](https://github.com/seatlayer/seatlayer-react-native)
211
212
  - [iOS SDK](https://github.com/seatlayer/seatlayer-ios)
212
213
  - [Flutter package](https://pub.dev/packages/seatlayer)
213
214
  - [Agent-readable documentation](https://docs.seatlayer.io/llms.txt)
package/dist/index.cjs CHANGED
@@ -131,6 +131,9 @@ var PubApi = class {
131
131
  }
132
132
  };
133
133
 
134
+ // src/seatLayerBrand.ts
135
+ var SEATLAYER_ATTRIBUTION_MARK_SVG = '<svg viewBox="0 0 64 56" width="12" height="11" fill="none" aria-hidden="true" focusable="false" style="display:block"><path d="M4 13 Q16 6 29 7 L28.5 17 Q17 16.5 7 22 Z" fill="#f4b740"/><path d="M4 13 Q16 6 29 7 L28.5 17 Q17 16.5 7 22 Z" fill="#f4b740" transform="translate(64 0) scale(-1 1)"/><path d="M8 28 Q18 22 28.6 23 L28.2 32 Q18 28.5 10 36 Z" fill="#fcf7ee"/><path d="M8 28 Q18 22 28.6 23 L28.2 32 Q18 28.5 10 36 Z" fill="#fcf7ee" transform="translate(64 0) scale(-1 1)"/><path d="M11 41 Q19 35 28.2 36 L27.8 45 Q19.5 42 13.5 49 Z" fill="#fcf7ee"/><path d="M11 41 Q19 35 28.2 36 L27.8 45 Q19.5 42 13.5 49 Z" fill="#fcf7ee" transform="translate(64 0) scale(-1 1)"/></svg>';
136
+
134
137
  // src/SeatingChart.ts
135
138
  var DEFAULT_API_BASE = "https://api.seatlayer.io";
136
139
  var DEFAULT_MAX_SELECTION = 10;
@@ -246,7 +249,7 @@ var SeatingChart = class {
246
249
  badge.rel = "noopener noreferrer";
247
250
  badge.setAttribute("aria-label", (0, import_core.t)("picker.poweredBy"));
248
251
  badge.style.cssText = 'position:absolute;bottom:10px;right:12px;z-index:5;display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border-radius:999px;background:rgba(255,255,255,.92);color:#4a5163;text-decoration:none;font:600 11px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:.02em;box-shadow:0 2px 8px rgba(0,0,0,.12);';
249
- badge.innerHTML = `<span aria-hidden="true" style="width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;background:#f4b740;color:#1a1200"><svg viewBox="0 0 24 24" style="width:11px;height:11px;fill:currentColor"><path d="M4 15c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v3h-3v-2H7v2H4v-3Z"/><rect x="7" y="7" width="10" height="5" rx="1.6"/></svg></span><span>${(0, import_core.t)("picker.poweredBy")}</span>`;
252
+ badge.innerHTML = '<span aria-hidden="true" style="width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;background:#0c1220;color:#fcf7ee">' + SEATLAYER_ATTRIBUTION_MARK_SVG + `</span><span>${(0, import_core.t)("picker.poweredBy")}</span>`;
250
253
  host.appendChild(badge);
251
254
  }
252
255
  placeTooltip() {
@@ -1107,6 +1110,8 @@ var import_meta = {};
1107
1110
  var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
1108
1111
  var DEFAULT_MAX_SELECTION2 = 10;
1109
1112
  var EXTEND_PROMPT_MS = 6e4;
1113
+ var MAX_3D_SEATS_DEFAULT = 15e3;
1114
+ var MAX_3D_SECTION_PILLS = 12;
1110
1115
  function pointInPolygon(x, y, poly) {
1111
1116
  let inside = false;
1112
1117
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
@@ -1526,14 +1531,14 @@ var CSS = `
1526
1531
  .sl-closed-pill.on{display:inline-flex}
1527
1532
  .sl-closed-pill svg{width:13px;height:13px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
1528
1533
 
1529
- /* "Powered by SeatLayer" attribution badge (side-panel foot) \u2014 the small gold
1530
- rounded logo mark + wordmark. Hidden when the host opts out or the org's paid
1534
+ /* "Powered by SeatLayer" attribution badge (side-panel foot) \u2014 the canonical
1535
+ Layered Rows mark + wordmark. Hidden when the host opts out or the org's paid
1531
1536
  theme sets hideBadge. */
1532
1537
  .sl-powered{display:flex;align-items:center;justify-content:center;gap:6px;margin-top:10px;
1533
1538
  font-size:11px;letter-spacing:.03em;color:var(--sl-muted)}
1534
1539
  .sl-powered-mark{width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;
1535
- background:var(--sl-accent);color:var(--sl-accent-ink)}
1536
- .sl-powered-mark svg{width:11px;height:11px;fill:currentColor}
1540
+ background:#0c1220;color:#fcf7ee}
1541
+ .sl-powered-mark svg{width:12px;height:11px}
1537
1542
 
1538
1543
  /* a11y filter chips (flow within the top-left region) */
1539
1544
  .sl-chips{display:flex;gap:6px;flex-wrap:wrap}
@@ -1715,6 +1720,20 @@ var CSS = `
1715
1720
  color:#e6edf3;background:rgba(10,14,20,.62);border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px)}
1716
1721
  .sl-view3d-back:hover,.sl-view3d-back:focus-visible{background:rgba(16,22,30,.82);border-color:rgba(255,255,255,.4)}
1717
1722
  .sl-view3d-back svg{width:15px;height:15px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round;stroke-linejoin:round}
1723
+ /* Venue navigation inside 3D: levels (isolate a floor) and areas (fly to a zone).
1724
+ Bottom-LEFT, clear of the module's own chips (Overview bottom-right, 360
1725
+ bottom-centre) and of the bottom-sheeted confirm card. Horizontally scrollable
1726
+ so a venue with many zones never pushes the rail off a phone screen. */
1727
+ .sl-view3d-nav{position:absolute;left:12px;bottom:16px;z-index:3;display:flex;flex-direction:column;gap:6px;
1728
+ max-width:calc(100% - 24px);pointer-events:none}
1729
+ .sl-view3d-nav > div{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;pointer-events:auto;
1730
+ padding:1px;-webkit-overflow-scrolling:touch}
1731
+ .sl-view3d-nav > div::-webkit-scrollbar{display:none}
1732
+ .sl-view3d-nav button{flex:0 0 auto;min-height:32px;padding:7px 12px;border-radius:999px;white-space:nowrap;
1733
+ font-size:11.5px;font-weight:700;color:#c9d4ea;background:rgba(12,18,32,.72);
1734
+ border:1px solid rgba(150,165,205,.35);backdrop-filter:blur(6px);cursor:pointer}
1735
+ .sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
1736
+ .sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
1718
1737
  /* While immersed, the 2D-only chrome is meaningless \u2014 hide it, keep Map|3D. */
1719
1738
  .sl-picker[data-view3d="on"] .sl-rungs,
1720
1739
  .sl-picker[data-view3d="on"] .sl-floors,
@@ -1988,6 +2007,8 @@ var SeatPicker = class _SeatPicker {
1988
2007
  // F4 price-band filter — active band's category keys (null = all prices)
1989
2008
  this.priceBandKeys = null;
1990
2009
  this.focusedCatKey = null;
2010
+ /** "Hide limited-view seats" — mirrored into 3D by `seatState3dFor`. */
2011
+ this.limitedViewFilter = false;
1991
2012
  this.pricesExpanded = false;
1992
2013
  /** Last surfaced section summary (re-rendered when the price band changes). */
1993
2014
  this.lastSection = null;
@@ -2656,12 +2677,13 @@ var SeatPicker = class _SeatPicker {
2656
2677
  limited.setAttribute("aria-pressed", "false");
2657
2678
  limited.innerHTML = `\u25D0 ${this.tf("picker.hideLimitedView", "Hide limited-view seats")}`;
2658
2679
  chips.appendChild(limited);
2659
- let limitedOn = false;
2660
2680
  limited.addEventListener("click", () => {
2661
- limitedOn = !limitedOn;
2681
+ const limitedOn = !this.limitedViewFilter;
2682
+ this.limitedViewFilter = limitedOn;
2662
2683
  limited.classList.toggle("on", limitedOn);
2663
2684
  limited.setAttribute("aria-pressed", String(limitedOn));
2664
2685
  this.controller.setCommercialLimitedFilter(limitedOn);
2686
+ this.pushAvailabilityTo3d();
2665
2687
  if (limitedOn) focusSeatsForFilter();
2666
2688
  });
2667
2689
  }
@@ -2819,7 +2841,7 @@ var SeatPicker = class _SeatPicker {
2819
2841
  if (!foot) return;
2820
2842
  const el = document.createElement("div");
2821
2843
  el.className = "sl-powered";
2822
- el.innerHTML = `<span class="sl-powered-mark" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 15c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v3h-3v-2H7v2H4v-3Z"/><rect x="7" y="7" width="10" height="5" rx="1.6"/></svg></span><span>${this.tf("picker.poweredBy", "Powered by SeatLayer")}</span>`;
2844
+ el.innerHTML = `<span class="sl-powered-mark" aria-hidden="true">` + SEATLAYER_ATTRIBUTION_MARK_SVG + `</span><span>${this.tf("picker.poweredBy", "Powered by SeatLayer")}</span>`;
2823
2845
  foot.appendChild(el);
2824
2846
  }
2825
2847
  // ---- Feature 6: chrome anchor regions -------------------------------------
@@ -3250,6 +3272,7 @@ var SeatPicker = class _SeatPicker {
3250
3272
  this.priceBandKeys = keys ? new Set(keys) : null;
3251
3273
  this.controller.setCategoryFilter(keys);
3252
3274
  this.controller.focusCategoryFilter(keys);
3275
+ this.pushAvailabilityTo3d();
3253
3276
  this.syncFloors();
3254
3277
  this.syncRung();
3255
3278
  this.refreshMinimap();
@@ -3347,10 +3370,23 @@ var SeatPicker = class _SeatPicker {
3347
3370
  button.setAttribute("aria-pressed", String(on));
3348
3371
  });
3349
3372
  }
3350
- /** Can this picker offer the 3D venue view? Requires the option (default on)
3351
- * and WebGL2, and a chart to render. */
3373
+ /** Can this picker offer the 3D venue view? Requires the option (default on),
3374
+ * WebGL2, a chart to render, and a chart small enough to render WELL. */
3352
3375
  canOffer3d() {
3353
- return this.opts.enable3D !== false && hasWebGL2() && !!this.controller.doc;
3376
+ if (this.opts.enable3D === false || !this.controller.doc || !hasWebGL2()) return false;
3377
+ return this.allSeats().length <= this.max3dSeats();
3378
+ }
3379
+ /**
3380
+ * The seat ceiling for offering 3D. See `max3DSeats` — an explicit host value
3381
+ * always wins; otherwise a small/low-core device gets half the desktop budget,
3382
+ * because it is the device that turns "slow" into "stalled".
3383
+ */
3384
+ max3dSeats() {
3385
+ const authored = this.opts.max3DSeats;
3386
+ if (typeof authored === "number" && authored > 0) return authored;
3387
+ const nav = globalThis.navigator;
3388
+ const small = (nav?.hardwareConcurrency ?? 8) <= 4 || (nav?.deviceMemory ?? 8) <= 4 || (globalThis.matchMedia?.("(pointer: coarse)").matches ?? false);
3389
+ return small ? MAX_3D_SEATS_DEFAULT / 2 : MAX_3D_SEATS_DEFAULT;
3354
3390
  }
3355
3391
  /** Reflect the active floor onto the switcher rail. */
3356
3392
  syncFloors() {
@@ -3903,6 +3939,7 @@ var SeatPicker = class _SeatPicker {
3903
3939
  if (select) select.value = "all";
3904
3940
  this.controller.setCategoryFilter(next ? [next] : null);
3905
3941
  this.controller.focusCategoryFilter(next ? [next] : null);
3942
+ this.pushAvailabilityTo3d();
3906
3943
  this.syncFloors();
3907
3944
  this.syncRung();
3908
3945
  this.refreshMinimap();
@@ -4594,8 +4631,8 @@ var SeatPicker = class _SeatPicker {
4594
4631
  void this.enter3d(flyToSeatId);
4595
4632
  }
4596
4633
  /** SeatStatus → the view3d palette state. Selection is layered separately. */
4597
- seatState3dFor(id) {
4598
- switch (this.controller.getStatus(id)) {
4634
+ seatState3dFor(seat) {
4635
+ switch (this.controller.getStatus(seat.id)) {
4599
4636
  case "held":
4600
4637
  return "held";
4601
4638
  case "booked":
@@ -4603,14 +4640,19 @@ var SeatPicker = class _SeatPicker {
4603
4640
  case "not_for_sale":
4604
4641
  return "dimmed";
4605
4642
  default:
4606
- return "available";
4643
+ break;
4644
+ }
4645
+ if (this.priceBandKeys != null && !this.priceBandKeys.has(seat.categoryKey)) return "dimmed";
4646
+ if (this.limitedViewFilter && (seat.commercial?.restrictedView || seat.commercial?.obstructedView)) {
4647
+ return "dimmed";
4607
4648
  }
4649
+ return "available";
4608
4650
  }
4609
4651
  /** Push the full live availability snapshot into the 3D handle (selection is
4610
4652
  * preserved inside the module). Cheap enough per status delta. */
4611
4653
  pushAvailabilityTo3d() {
4612
4654
  if (!this.view3dHandle) return;
4613
- const updates = this.allSeats().map((s) => ({ seatId: s.id, state: this.seatState3dFor(s.id) }));
4655
+ const updates = this.allSeats().map((s) => ({ seatId: s.id, state: this.seatState3dFor(s) }));
4614
4656
  this.view3dHandle.setAvailability(updates);
4615
4657
  }
4616
4658
  /** Mirror the authoritative widget selection into the 3D handle. */
@@ -4662,6 +4704,75 @@ var SeatPicker = class _SeatPicker {
4662
4704
  if (this.opts.confirmSelection !== false) this.showConfirm(seat);
4663
4705
  else this.syncTray();
4664
4706
  }
4707
+ /**
4708
+ * The venue-navigation rail inside 3D: levels and areas.
4709
+ *
4710
+ * In 2D a buyer moves through the venue by floor switcher and by the LOD
4711
+ * rungs. Both are hidden while immersed, and the 3D module's own chips only
4712
+ * cover "go home" and "see the 360" — so on a multi-floor or multi-zone chart
4713
+ * the buyer entered 3D and LOST the ability to reach the level or area they
4714
+ * were booking. The camera moves for both already exist on the handle
4715
+ * (`focusFloor` / `focusZone`); this is the surface that offers them.
4716
+ *
4717
+ * Levels are a TOGGLE (a floor stays isolated until you pick another), areas
4718
+ * are an ACTION (the camera flies there and you are then free to orbit), which
4719
+ * is why only the level pills carry a pressed state.
4720
+ */
4721
+ buildView3dNav(overlay, handle) {
4722
+ const floors = handle.floors();
4723
+ const zones = handle.zones().filter((z) => z.seatCount > 0);
4724
+ const sections = zones.length > 1 ? [] : handle.sections().filter((s) => s.seatCount > 0).slice(0, MAX_3D_SECTION_PILLS);
4725
+ const wantFloors = floors.length > 1;
4726
+ const wantZones = zones.length > 1;
4727
+ const wantSections = sections.length > 1;
4728
+ if (!wantFloors && !wantZones && !wantSections) return;
4729
+ const nav = document.createElement("div");
4730
+ nav.className = "sl-view3d-nav";
4731
+ if (wantFloors) {
4732
+ const row = document.createElement("div");
4733
+ row.setAttribute("role", "group");
4734
+ row.setAttribute("aria-label", this.tf("picker.levels", "Levels"));
4735
+ const pills = [];
4736
+ const select = (index) => {
4737
+ if (!handle.focusFloor(index)) return;
4738
+ pills.forEach((p) => {
4739
+ p.setAttribute("aria-pressed", String((p.dataset.floor === "" ? null : Number(p.dataset.floor)) === index));
4740
+ });
4741
+ };
4742
+ const add = (label, index) => {
4743
+ const b = document.createElement("button");
4744
+ b.type = "button";
4745
+ b.textContent = label;
4746
+ b.dataset.floor = index === null ? "" : String(index);
4747
+ b.setAttribute("aria-pressed", String(index === null));
4748
+ b.addEventListener("click", () => select(index));
4749
+ pills.push(b);
4750
+ row.appendChild(b);
4751
+ };
4752
+ add(this.tf("picker.allLevels", "All levels"), null);
4753
+ for (const f of floors) add(f.label || `Level ${f.index + 1}`, f.index);
4754
+ nav.appendChild(row);
4755
+ }
4756
+ if (wantZones || wantSections) {
4757
+ const row = document.createElement("div");
4758
+ row.setAttribute("role", "group");
4759
+ row.setAttribute("aria-label", this.tf("picker.areas", "Areas"));
4760
+ const entries = wantZones ? zones.map((z) => ({ id: z.id, label: z.label || z.id, go: () => {
4761
+ handle.focusZone(z.id);
4762
+ } })) : sections.map((s) => ({ id: s.id, label: s.label || s.id, go: () => {
4763
+ handle.focusSection(s.id);
4764
+ } }));
4765
+ for (const e of entries) {
4766
+ const b = document.createElement("button");
4767
+ b.type = "button";
4768
+ b.textContent = e.label;
4769
+ b.addEventListener("click", e.go);
4770
+ row.appendChild(b);
4771
+ }
4772
+ nav.appendChild(row);
4773
+ }
4774
+ overlay.appendChild(nav);
4775
+ }
4665
4776
  async enter3d(flySeatId) {
4666
4777
  if (this.view3dEl || !this.canOffer3d() || !this.els.map) return;
4667
4778
  const doc = this.controller.doc;
@@ -4712,6 +4823,7 @@ var SeatPicker = class _SeatPicker {
4712
4823
  this.view3dHandle = handle;
4713
4824
  this.pushAvailabilityTo3d();
4714
4825
  this.syncSelectionTo3d();
4826
+ this.buildView3dNav(overlay, handle);
4715
4827
  if (flySeatId) void handle.flyToSeat(flySeatId);
4716
4828
  } catch (err) {
4717
4829
  this.opts.onError?.(err);