@seatlayer/core 0.15.1 → 0.16.1

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
@@ -963,6 +963,7 @@ var ISO_ANGLE_DEG = -11.5;
963
963
  var ISO_SQUASH = 0.58;
964
964
  var LIFT_PER_STEP = 58;
965
965
  var ISO_TWEEN_MS = 320;
966
+ var CAMERA_GLIDE_MS = 650;
966
967
  var BLOCK_FILL_ALPHA = 0.85;
967
968
  var SOLD_DARKEN = 0.5;
968
969
  var SECTION_LABEL_PX = 20;
@@ -1589,6 +1590,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1589
1590
  for (const id of ids) this.setSelected(id, false, true);
1590
1591
  this.overlayLayer.batchDraw();
1591
1592
  }
1593
+ setMaxSelection(maxSelection) {
1594
+ this.opts.maxSelection = Math.max(0, Math.floor(maxSelection));
1595
+ }
1596
+ select(seatIds) {
1597
+ const added = [];
1598
+ for (const id of seatIds) {
1599
+ if (this.selection.has(id) || !this.isSelectable(id)) continue;
1600
+ if (this.selection.size >= this.opts.maxSelection) {
1601
+ this.opts.onSelectionLimit?.(this.opts.maxSelection);
1602
+ break;
1603
+ }
1604
+ this.setSelected(id, true, true);
1605
+ const seat = this.seatById.get(id);
1606
+ if (seat) added.push(seat);
1607
+ }
1608
+ if (added.length) this.overlayLayer.batchDraw();
1609
+ return added;
1610
+ }
1592
1611
  /** Switch organizer interaction in place so the host preserves camera, LOD,
1593
1612
  * focus and live status state while moving between Monitor and Block. */
1594
1613
  setManageInteraction(options) {
@@ -1917,6 +1936,40 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1917
1936
  this.seatLayer.batchDraw();
1918
1937
  }
1919
1938
  }
1939
+ /** Frame the currently available inventory that survived a buyer price
1940
+ * filter. Clearing the filter glides back to the full venue. */
1941
+ focusCategories(keys) {
1942
+ if (!keys?.length) {
1943
+ this.focusRegion(this.bounds, { durationMs: CAMERA_GLIDE_MS });
1944
+ return;
1945
+ }
1946
+ const wanted = new Set(keys);
1947
+ const matching = this.seats.filter((seat) => {
1948
+ if (!wanted.has(seat.categoryKey) || this.seatInClosedSection(seat.id)) return false;
1949
+ const status = this.statusById.get(seat.id) ?? "free";
1950
+ return status === "free" || this.ownedHold.has(seat.id);
1951
+ });
1952
+ if (!matching.length) return;
1953
+ let minX = Infinity;
1954
+ let maxX = -Infinity;
1955
+ let minY = Infinity;
1956
+ let maxY = -Infinity;
1957
+ for (const seat of matching) {
1958
+ minX = Math.min(minX, seat.x);
1959
+ maxX = Math.max(maxX, seat.x);
1960
+ minY = Math.min(minY, seat.y);
1961
+ maxY = Math.max(maxY, seat.y);
1962
+ }
1963
+ const pad = Math.max(24, this.seatR * 3);
1964
+ minX -= pad;
1965
+ maxX += pad;
1966
+ minY -= pad;
1967
+ maxY += pad;
1968
+ this.focusRegion(
1969
+ { x: minX, y: minY, width: Math.max(40, maxX - minX), height: Math.max(40, maxY - minY) },
1970
+ { durationMs: CAMERA_GLIDE_MS }
1971
+ );
1972
+ }
1920
1973
  // ---- isometric ("3D") view mode -------------------------------------------
1921
1974
  /**
1922
1975
  * Switch the projection between flat top-down and the isometric "3D" view
@@ -3105,7 +3158,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
3105
3158
  if (seat) this.opts.onDeselect?.(seat);
3106
3159
  } else {
3107
3160
  if (!this.isSelectable(id)) return;
3108
- if (this.selection.size >= this.opts.maxSelection) return;
3161
+ if (this.selection.size >= this.opts.maxSelection) {
3162
+ this.opts.onSelectionLimit?.(this.opts.maxSelection);
3163
+ return;
3164
+ }
3109
3165
  this.setSelected(id, true);
3110
3166
  const seat = this.seatById.get(id);
3111
3167
  if (seat) this.opts.onSelect?.(seat);
@@ -3443,7 +3499,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
3443
3499
  }
3444
3500
  /**
3445
3501
  * Smoothly glide the camera to frame a section (by id) or a world-space bounds
3446
- * rect — the Slice 5 "glide in". Pan+zoom tween over ~450ms easeInOutCubic; the
3502
+ * rect — the Slice 5 "glide in". Pan+zoom tween over a calm easeInOutCubic; the
3447
3503
  * melt/LOD rides the camera every frame. `prefers-reduced-motion` (or
3448
3504
  * `opts.animate === false`) snaps via zoomToBounds. A grab (pointer-down) or a
3449
3505
  * newer glide cancels an in-flight one.
@@ -3475,13 +3531,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
3475
3531
  return;
3476
3532
  }
3477
3533
  const start = performance.now();
3478
- const DUR = 450;
3534
+ const duration = Math.max(180, Math.min(1200, opts?.durationMs ?? CAMERA_GLIDE_MS));
3479
3535
  const step = (now) => {
3480
3536
  if (this.destroyed) {
3481
3537
  this.glideRaf = 0;
3482
3538
  return;
3483
3539
  }
3484
- const raw = Math.min(1, (now - start) / DUR);
3540
+ const raw = Math.min(1, (now - start) / duration);
3485
3541
  const e = raw < 0.5 ? 4 * raw * raw * raw : 1 - Math.pow(-2 * raw + 2, 3) / 2;
3486
3542
  const sc = fromScale + (toScale - fromScale) * e;
3487
3543
  this.stage.scale({ x: sc, y: sc });
@@ -3606,15 +3662,49 @@ var _SeatmapRenderer = class _SeatmapRenderer {
3606
3662
  if (seat.kind === "booth") continue;
3607
3663
  if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
3608
3664
  const status = this.statusById.get(seat.id) ?? "free";
3609
- const statusCue = status === "booked" ? "\xD7" : status === "held" && !this.ownedHold.has(seat.id) ? "H" : null;
3665
+ const unavailable = status === "booked" || status === "held" && !this.ownedHold.has(seat.id);
3666
+ if (unavailable) {
3667
+ const cue = new import_Group.Group({ x: seat.x, y: seat.y, listening: false });
3668
+ if (status === "held") {
3669
+ cue.add(new import_Rect.Rect({
3670
+ x: -4.2,
3671
+ y: -0.7,
3672
+ width: 8.4,
3673
+ height: 6.5,
3674
+ cornerRadius: 1.3,
3675
+ stroke: "#ffffff",
3676
+ strokeWidth: 1.25,
3677
+ listening: false
3678
+ }));
3679
+ cue.add(new import_Line.Line({
3680
+ points: [-2.4, -0.8, -2.4, -2.7, -1.2, -4, 0, -4.35, 1.2, -4, 2.4, -2.7, 2.4, -0.8],
3681
+ stroke: "#ffffff",
3682
+ strokeWidth: 1.2,
3683
+ lineCap: "round",
3684
+ lineJoin: "round",
3685
+ listening: false
3686
+ }));
3687
+ } else {
3688
+ cue.add(new import_Line.Line({
3689
+ points: [-4.2, 4.2, 4.2, -4.2],
3690
+ stroke: "#ffffff",
3691
+ strokeWidth: 1.8,
3692
+ lineCap: "round",
3693
+ listening: false
3694
+ }));
3695
+ }
3696
+ this.labelGroup.add(cue);
3697
+ if (++count >= MAX_LABELS) break;
3698
+ continue;
3699
+ }
3610
3700
  const t2 = new import_Text.Text({
3611
3701
  x: seat.x,
3612
3702
  y: seat.y,
3613
- text: statusCue ?? seat.label,
3614
- fontSize: statusCue ? status === "booked" ? 13 : 8 : 7,
3615
- fontStyle: statusCue ? "800" : "600",
3703
+ text: seat.label,
3704
+ fontSize: 7,
3705
+ fontStyle: "600",
3616
3706
  fontFamily: this.labelFont(),
3617
- fill: statusCue ? "#ffffff" : this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
3707
+ fill: this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
3618
3708
  listening: false,
3619
3709
  perfectDrawEnabled: false
3620
3710
  });
@@ -3838,6 +3928,7 @@ var PickerController = class {
3838
3928
  this.opts.onDeselect?.(seat);
3839
3929
  this.emitSelectionChange();
3840
3930
  },
3931
+ onSelectionLimit: this.opts.onSelectionLimit,
3841
3932
  onHover: (seat) => {
3842
3933
  this.opts.onHover?.(seat);
3843
3934
  if (this.opts.onSeatHover) this.opts.onSeatHover(seat ? this.describeSeat(seat) : null);
@@ -3905,6 +3996,15 @@ var PickerController = class {
3905
3996
  this.renderer?.deselect(ids);
3906
3997
  this.emitSelectionChange();
3907
3998
  }
3999
+ setMaxSelection(maxSelection) {
4000
+ this.maxSelection = Math.max(0, Math.floor(maxSelection));
4001
+ this.renderer?.setMaxSelection?.(this.maxSelection);
4002
+ }
4003
+ select(ids) {
4004
+ const added = this.renderer?.select?.(ids) ?? [];
4005
+ if (added.length) this.emitSelectionChange();
4006
+ return added.map((seat) => this.toSeat(seat));
4007
+ }
3908
4008
  // ---- booking machine ------------------------------------------------------
3909
4009
  /**
3910
4010
  * Hold the current selection (or a given label set). The controller does the
@@ -4271,6 +4371,17 @@ var PickerController = class {
4271
4371
  setCategoryFilter(keys) {
4272
4372
  this.renderer?.setCategoryFilter?.(keys);
4273
4373
  }
4374
+ /** An explicit buyer price-filter action should not only dim the rest of the
4375
+ * chart: clear any older section drill-in and guide the camera to the seats
4376
+ * that remain relevant. Clearing the filter glides back to the full chart. */
4377
+ focusCategoryFilter(keys) {
4378
+ const renderer = this.renderer;
4379
+ if (!renderer) return;
4380
+ renderer.clearSectionFocus?.();
4381
+ this.opts.onSectionFocus?.(null);
4382
+ if (renderer.focusCategories) renderer.focusCategories(keys);
4383
+ else renderer.zoomToFit();
4384
+ }
4274
4385
  /** World-space rect currently visible + full chart bounds (F3 minimap frame). */
4275
4386
  getViewport() {
4276
4387
  const r = this.renderer;