@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 +120 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +120 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -362,6 +362,8 @@ type SeatStatus = 'free' | 'held' | 'booked' | 'not_for_sale';
|
|
|
362
362
|
interface RendererCallbacks {
|
|
363
363
|
onSelect?: (seat: ExpandedSeat) => void;
|
|
364
364
|
onDeselect?: (seat: ExpandedSeat) => void;
|
|
365
|
+
/** Buyer tried to add a seat after the active selection cap was reached. */
|
|
366
|
+
onSelectionLimit?: (maxSelection: number) => void;
|
|
365
367
|
/** seat is null when the pointer leaves any seat. */
|
|
366
368
|
onHover?: (seat: ExpandedSeat | null) => void;
|
|
367
369
|
/** Keyboard focus moved to a seat (arrow-key navigation) — for screen-reader announcements. */
|
|
@@ -461,6 +463,13 @@ interface ISeatmapRenderer {
|
|
|
461
463
|
getStatus(seatId: string): SeatStatus;
|
|
462
464
|
getSelection(): ExpandedSeat[];
|
|
463
465
|
clearSelection(): void;
|
|
466
|
+
/** Update the buyer selection cap without rebuilding the chart or camera. */
|
|
467
|
+
setMaxSelection?(maxSelection: number): void;
|
|
468
|
+
/**
|
|
469
|
+
* Programmatically restore free seats (for example an Undo action). Added
|
|
470
|
+
* seats respect the active cap and do not reopen a confirmation popover.
|
|
471
|
+
*/
|
|
472
|
+
select?(seatIds: string[]): ExpandedSeat[];
|
|
464
473
|
/**
|
|
465
474
|
* Dynamically update organizer-only interaction without rebuilding the
|
|
466
475
|
* renderer. No buyer surface calls this; every behavior remains gated by
|
|
@@ -522,6 +531,9 @@ interface ISeatmapRenderer {
|
|
|
522
531
|
/** Price-band filter (F4): dim free seats whose category is NOT in `keys`
|
|
523
532
|
* (null clears). The widget resolves which categories fall in the band. */
|
|
524
533
|
setCategoryFilter?(keys: string[] | null): void;
|
|
534
|
+
/** Smoothly frame the currently available seats in these categories. `null`
|
|
535
|
+
* returns to the full chart. Used after an explicit buyer price-filter action. */
|
|
536
|
+
focusCategories?(keys: string[] | null): void;
|
|
525
537
|
/** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */
|
|
526
538
|
setDimmedSections?(ids: string[] | null): void;
|
|
527
539
|
/**
|
|
@@ -587,7 +599,7 @@ interface ISeatmapRenderer {
|
|
|
587
599
|
sectionMembers?(id: string): string[];
|
|
588
600
|
/**
|
|
589
601
|
* Smoothly glide (pan+zoom) the camera to frame a section (by id) or a world-
|
|
590
|
-
* space bounds rect over
|
|
602
|
+
* space bounds rect over a calm easeInOutCubic glide. `prefers-reduced-motion` snaps.
|
|
591
603
|
* A pointer-down (grab/pan) cancels an in-flight glide. Slice 5 "glide in".
|
|
592
604
|
*/
|
|
593
605
|
focusRegion?(target: string | {
|
|
@@ -597,6 +609,8 @@ interface ISeatmapRenderer {
|
|
|
597
609
|
height: number;
|
|
598
610
|
}, opts?: {
|
|
599
611
|
animate?: boolean;
|
|
612
|
+
minScale?: number;
|
|
613
|
+
durationMs?: number;
|
|
600
614
|
}): void;
|
|
601
615
|
/** Current LOD rung derived from zoom (for the ZONES/SECTIONS/SEATS pill). */
|
|
602
616
|
getRung?(): LodRung;
|
|
@@ -908,6 +922,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
908
922
|
getStatus(seatId: string): SeatStatus;
|
|
909
923
|
getSelection(): ExpandedSeat[];
|
|
910
924
|
clearSelection(): void;
|
|
925
|
+
setMaxSelection(maxSelection: number): void;
|
|
926
|
+
select(seatIds: string[]): ExpandedSeat[];
|
|
911
927
|
/** Switch organizer interaction in place so the host preserves camera, LOD,
|
|
912
928
|
* focus and live status state while moving between Monitor and Block. */
|
|
913
929
|
setManageInteraction(options: {
|
|
@@ -970,6 +986,9 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
970
986
|
* widget resolves which categories fall inside the buyer's chosen band.
|
|
971
987
|
*/
|
|
972
988
|
setCategoryFilter(keys: string[] | null): void;
|
|
989
|
+
/** Frame the currently available inventory that survived a buyer price
|
|
990
|
+
* filter. Clearing the filter glides back to the full venue. */
|
|
991
|
+
focusCategories(keys: string[] | null): void;
|
|
973
992
|
/**
|
|
974
993
|
* Switch the projection between flat top-down and the isometric "3D" view
|
|
975
994
|
* (rotate + y-squash about the chart centre, plus per-elevation lift). Tweens
|
|
@@ -1214,7 +1233,7 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1214
1233
|
private zoomToBounds;
|
|
1215
1234
|
/**
|
|
1216
1235
|
* Smoothly glide the camera to frame a section (by id) or a world-space bounds
|
|
1217
|
-
* rect — the Slice 5 "glide in". Pan+zoom tween over
|
|
1236
|
+
* rect — the Slice 5 "glide in". Pan+zoom tween over a calm easeInOutCubic; the
|
|
1218
1237
|
* melt/LOD rides the camera every frame. `prefers-reduced-motion` (or
|
|
1219
1238
|
* `opts.animate === false`) snaps via zoomToBounds. A grab (pointer-down) or a
|
|
1220
1239
|
* newer glide cancels an in-flight one.
|
|
@@ -1227,6 +1246,7 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1227
1246
|
}, opts?: {
|
|
1228
1247
|
animate?: boolean;
|
|
1229
1248
|
minScale?: number;
|
|
1249
|
+
durationMs?: number;
|
|
1230
1250
|
}): void;
|
|
1231
1251
|
/** Cancel an in-flight camera glide (a grab or a newer glide interrupts it). */
|
|
1232
1252
|
private cancelGlide;
|
|
@@ -1453,7 +1473,7 @@ declare class PickerController {
|
|
|
1453
1473
|
private readonly opts;
|
|
1454
1474
|
private readonly api;
|
|
1455
1475
|
private readonly key;
|
|
1456
|
-
private
|
|
1476
|
+
private maxSelection;
|
|
1457
1477
|
private renderer;
|
|
1458
1478
|
private _doc;
|
|
1459
1479
|
/** Section/zone ids hidden from buyers this event (3.3) — seats vanish, not grey. */
|
|
@@ -1514,6 +1534,8 @@ declare class PickerController {
|
|
|
1514
1534
|
seatDetails(seatId: string): SeatHoverDetails | null;
|
|
1515
1535
|
clearSelection(): void;
|
|
1516
1536
|
deselect(ids: string[]): void;
|
|
1537
|
+
setMaxSelection(maxSelection: number): void;
|
|
1538
|
+
select(ids: string[]): PickerSeat[];
|
|
1517
1539
|
/**
|
|
1518
1540
|
* Hold the current selection (or a given label set). The controller does the
|
|
1519
1541
|
* renderer/hold side (409 → deselect + repaint the taken seats held) and then
|
|
@@ -1614,6 +1636,10 @@ declare class PickerController {
|
|
|
1614
1636
|
/** Price-band filter (F4): dim free seats whose category is outside `keys`
|
|
1615
1637
|
* (null clears). The widget resolves which categories fall in the band. */
|
|
1616
1638
|
setCategoryFilter(keys: string[] | null): void;
|
|
1639
|
+
/** An explicit buyer price-filter action should not only dim the rest of the
|
|
1640
|
+
* chart: clear any older section drill-in and guide the camera to the seats
|
|
1641
|
+
* that remain relevant. Clearing the filter glides back to the full chart. */
|
|
1642
|
+
focusCategoryFilter(keys: string[] | null): void;
|
|
1617
1643
|
/** World-space rect currently visible + full chart bounds (F3 minimap frame). */
|
|
1618
1644
|
getViewport(): {
|
|
1619
1645
|
visible: {
|
package/dist/index.d.ts
CHANGED
|
@@ -362,6 +362,8 @@ type SeatStatus = 'free' | 'held' | 'booked' | 'not_for_sale';
|
|
|
362
362
|
interface RendererCallbacks {
|
|
363
363
|
onSelect?: (seat: ExpandedSeat) => void;
|
|
364
364
|
onDeselect?: (seat: ExpandedSeat) => void;
|
|
365
|
+
/** Buyer tried to add a seat after the active selection cap was reached. */
|
|
366
|
+
onSelectionLimit?: (maxSelection: number) => void;
|
|
365
367
|
/** seat is null when the pointer leaves any seat. */
|
|
366
368
|
onHover?: (seat: ExpandedSeat | null) => void;
|
|
367
369
|
/** Keyboard focus moved to a seat (arrow-key navigation) — for screen-reader announcements. */
|
|
@@ -461,6 +463,13 @@ interface ISeatmapRenderer {
|
|
|
461
463
|
getStatus(seatId: string): SeatStatus;
|
|
462
464
|
getSelection(): ExpandedSeat[];
|
|
463
465
|
clearSelection(): void;
|
|
466
|
+
/** Update the buyer selection cap without rebuilding the chart or camera. */
|
|
467
|
+
setMaxSelection?(maxSelection: number): void;
|
|
468
|
+
/**
|
|
469
|
+
* Programmatically restore free seats (for example an Undo action). Added
|
|
470
|
+
* seats respect the active cap and do not reopen a confirmation popover.
|
|
471
|
+
*/
|
|
472
|
+
select?(seatIds: string[]): ExpandedSeat[];
|
|
464
473
|
/**
|
|
465
474
|
* Dynamically update organizer-only interaction without rebuilding the
|
|
466
475
|
* renderer. No buyer surface calls this; every behavior remains gated by
|
|
@@ -522,6 +531,9 @@ interface ISeatmapRenderer {
|
|
|
522
531
|
/** Price-band filter (F4): dim free seats whose category is NOT in `keys`
|
|
523
532
|
* (null clears). The widget resolves which categories fall in the band. */
|
|
524
533
|
setCategoryFilter?(keys: string[] | null): void;
|
|
534
|
+
/** Smoothly frame the currently available seats in these categories. `null`
|
|
535
|
+
* returns to the full chart. Used after an explicit buyer price-filter action. */
|
|
536
|
+
focusCategories?(keys: string[] | null): void;
|
|
525
537
|
/** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */
|
|
526
538
|
setDimmedSections?(ids: string[] | null): void;
|
|
527
539
|
/**
|
|
@@ -587,7 +599,7 @@ interface ISeatmapRenderer {
|
|
|
587
599
|
sectionMembers?(id: string): string[];
|
|
588
600
|
/**
|
|
589
601
|
* Smoothly glide (pan+zoom) the camera to frame a section (by id) or a world-
|
|
590
|
-
* space bounds rect over
|
|
602
|
+
* space bounds rect over a calm easeInOutCubic glide. `prefers-reduced-motion` snaps.
|
|
591
603
|
* A pointer-down (grab/pan) cancels an in-flight glide. Slice 5 "glide in".
|
|
592
604
|
*/
|
|
593
605
|
focusRegion?(target: string | {
|
|
@@ -597,6 +609,8 @@ interface ISeatmapRenderer {
|
|
|
597
609
|
height: number;
|
|
598
610
|
}, opts?: {
|
|
599
611
|
animate?: boolean;
|
|
612
|
+
minScale?: number;
|
|
613
|
+
durationMs?: number;
|
|
600
614
|
}): void;
|
|
601
615
|
/** Current LOD rung derived from zoom (for the ZONES/SECTIONS/SEATS pill). */
|
|
602
616
|
getRung?(): LodRung;
|
|
@@ -908,6 +922,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
908
922
|
getStatus(seatId: string): SeatStatus;
|
|
909
923
|
getSelection(): ExpandedSeat[];
|
|
910
924
|
clearSelection(): void;
|
|
925
|
+
setMaxSelection(maxSelection: number): void;
|
|
926
|
+
select(seatIds: string[]): ExpandedSeat[];
|
|
911
927
|
/** Switch organizer interaction in place so the host preserves camera, LOD,
|
|
912
928
|
* focus and live status state while moving between Monitor and Block. */
|
|
913
929
|
setManageInteraction(options: {
|
|
@@ -970,6 +986,9 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
970
986
|
* widget resolves which categories fall inside the buyer's chosen band.
|
|
971
987
|
*/
|
|
972
988
|
setCategoryFilter(keys: string[] | null): void;
|
|
989
|
+
/** Frame the currently available inventory that survived a buyer price
|
|
990
|
+
* filter. Clearing the filter glides back to the full venue. */
|
|
991
|
+
focusCategories(keys: string[] | null): void;
|
|
973
992
|
/**
|
|
974
993
|
* Switch the projection between flat top-down and the isometric "3D" view
|
|
975
994
|
* (rotate + y-squash about the chart centre, plus per-elevation lift). Tweens
|
|
@@ -1214,7 +1233,7 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1214
1233
|
private zoomToBounds;
|
|
1215
1234
|
/**
|
|
1216
1235
|
* Smoothly glide the camera to frame a section (by id) or a world-space bounds
|
|
1217
|
-
* rect — the Slice 5 "glide in". Pan+zoom tween over
|
|
1236
|
+
* rect — the Slice 5 "glide in". Pan+zoom tween over a calm easeInOutCubic; the
|
|
1218
1237
|
* melt/LOD rides the camera every frame. `prefers-reduced-motion` (or
|
|
1219
1238
|
* `opts.animate === false`) snaps via zoomToBounds. A grab (pointer-down) or a
|
|
1220
1239
|
* newer glide cancels an in-flight one.
|
|
@@ -1227,6 +1246,7 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
1227
1246
|
}, opts?: {
|
|
1228
1247
|
animate?: boolean;
|
|
1229
1248
|
minScale?: number;
|
|
1249
|
+
durationMs?: number;
|
|
1230
1250
|
}): void;
|
|
1231
1251
|
/** Cancel an in-flight camera glide (a grab or a newer glide interrupts it). */
|
|
1232
1252
|
private cancelGlide;
|
|
@@ -1453,7 +1473,7 @@ declare class PickerController {
|
|
|
1453
1473
|
private readonly opts;
|
|
1454
1474
|
private readonly api;
|
|
1455
1475
|
private readonly key;
|
|
1456
|
-
private
|
|
1476
|
+
private maxSelection;
|
|
1457
1477
|
private renderer;
|
|
1458
1478
|
private _doc;
|
|
1459
1479
|
/** Section/zone ids hidden from buyers this event (3.3) — seats vanish, not grey. */
|
|
@@ -1514,6 +1534,8 @@ declare class PickerController {
|
|
|
1514
1534
|
seatDetails(seatId: string): SeatHoverDetails | null;
|
|
1515
1535
|
clearSelection(): void;
|
|
1516
1536
|
deselect(ids: string[]): void;
|
|
1537
|
+
setMaxSelection(maxSelection: number): void;
|
|
1538
|
+
select(ids: string[]): PickerSeat[];
|
|
1517
1539
|
/**
|
|
1518
1540
|
* Hold the current selection (or a given label set). The controller does the
|
|
1519
1541
|
* renderer/hold side (409 → deselect + repaint the taken seats held) and then
|
|
@@ -1614,6 +1636,10 @@ declare class PickerController {
|
|
|
1614
1636
|
/** Price-band filter (F4): dim free seats whose category is outside `keys`
|
|
1615
1637
|
* (null clears). The widget resolves which categories fall in the band. */
|
|
1616
1638
|
setCategoryFilter(keys: string[] | null): void;
|
|
1639
|
+
/** An explicit buyer price-filter action should not only dim the rest of the
|
|
1640
|
+
* chart: clear any older section drill-in and guide the camera to the seats
|
|
1641
|
+
* that remain relevant. Clearing the filter glides back to the full chart. */
|
|
1642
|
+
focusCategoryFilter(keys: string[] | null): void;
|
|
1617
1643
|
/** World-space rect currently visible + full chart bounds (F3 minimap frame). */
|
|
1618
1644
|
getViewport(): {
|
|
1619
1645
|
visible: {
|
package/dist/index.js
CHANGED
|
@@ -644,6 +644,7 @@ var ISO_ANGLE_DEG = -11.5;
|
|
|
644
644
|
var ISO_SQUASH = 0.58;
|
|
645
645
|
var LIFT_PER_STEP = 58;
|
|
646
646
|
var ISO_TWEEN_MS = 320;
|
|
647
|
+
var CAMERA_GLIDE_MS = 650;
|
|
647
648
|
var BLOCK_FILL_ALPHA = 0.85;
|
|
648
649
|
var SOLD_DARKEN = 0.5;
|
|
649
650
|
var SECTION_LABEL_PX = 20;
|
|
@@ -1270,6 +1271,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1270
1271
|
for (const id of ids) this.setSelected(id, false, true);
|
|
1271
1272
|
this.overlayLayer.batchDraw();
|
|
1272
1273
|
}
|
|
1274
|
+
setMaxSelection(maxSelection) {
|
|
1275
|
+
this.opts.maxSelection = Math.max(0, Math.floor(maxSelection));
|
|
1276
|
+
}
|
|
1277
|
+
select(seatIds) {
|
|
1278
|
+
const added = [];
|
|
1279
|
+
for (const id of seatIds) {
|
|
1280
|
+
if (this.selection.has(id) || !this.isSelectable(id)) continue;
|
|
1281
|
+
if (this.selection.size >= this.opts.maxSelection) {
|
|
1282
|
+
this.opts.onSelectionLimit?.(this.opts.maxSelection);
|
|
1283
|
+
break;
|
|
1284
|
+
}
|
|
1285
|
+
this.setSelected(id, true, true);
|
|
1286
|
+
const seat = this.seatById.get(id);
|
|
1287
|
+
if (seat) added.push(seat);
|
|
1288
|
+
}
|
|
1289
|
+
if (added.length) this.overlayLayer.batchDraw();
|
|
1290
|
+
return added;
|
|
1291
|
+
}
|
|
1273
1292
|
/** Switch organizer interaction in place so the host preserves camera, LOD,
|
|
1274
1293
|
* focus and live status state while moving between Monitor and Block. */
|
|
1275
1294
|
setManageInteraction(options) {
|
|
@@ -1598,6 +1617,40 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1598
1617
|
this.seatLayer.batchDraw();
|
|
1599
1618
|
}
|
|
1600
1619
|
}
|
|
1620
|
+
/** Frame the currently available inventory that survived a buyer price
|
|
1621
|
+
* filter. Clearing the filter glides back to the full venue. */
|
|
1622
|
+
focusCategories(keys) {
|
|
1623
|
+
if (!keys?.length) {
|
|
1624
|
+
this.focusRegion(this.bounds, { durationMs: CAMERA_GLIDE_MS });
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const wanted = new Set(keys);
|
|
1628
|
+
const matching = this.seats.filter((seat) => {
|
|
1629
|
+
if (!wanted.has(seat.categoryKey) || this.seatInClosedSection(seat.id)) return false;
|
|
1630
|
+
const status = this.statusById.get(seat.id) ?? "free";
|
|
1631
|
+
return status === "free" || this.ownedHold.has(seat.id);
|
|
1632
|
+
});
|
|
1633
|
+
if (!matching.length) return;
|
|
1634
|
+
let minX = Infinity;
|
|
1635
|
+
let maxX = -Infinity;
|
|
1636
|
+
let minY = Infinity;
|
|
1637
|
+
let maxY = -Infinity;
|
|
1638
|
+
for (const seat of matching) {
|
|
1639
|
+
minX = Math.min(minX, seat.x);
|
|
1640
|
+
maxX = Math.max(maxX, seat.x);
|
|
1641
|
+
minY = Math.min(minY, seat.y);
|
|
1642
|
+
maxY = Math.max(maxY, seat.y);
|
|
1643
|
+
}
|
|
1644
|
+
const pad = Math.max(24, this.seatR * 3);
|
|
1645
|
+
minX -= pad;
|
|
1646
|
+
maxX += pad;
|
|
1647
|
+
minY -= pad;
|
|
1648
|
+
maxY += pad;
|
|
1649
|
+
this.focusRegion(
|
|
1650
|
+
{ x: minX, y: minY, width: Math.max(40, maxX - minX), height: Math.max(40, maxY - minY) },
|
|
1651
|
+
{ durationMs: CAMERA_GLIDE_MS }
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1601
1654
|
// ---- isometric ("3D") view mode -------------------------------------------
|
|
1602
1655
|
/**
|
|
1603
1656
|
* Switch the projection between flat top-down and the isometric "3D" view
|
|
@@ -2786,7 +2839,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2786
2839
|
if (seat) this.opts.onDeselect?.(seat);
|
|
2787
2840
|
} else {
|
|
2788
2841
|
if (!this.isSelectable(id)) return;
|
|
2789
|
-
if (this.selection.size >= this.opts.maxSelection)
|
|
2842
|
+
if (this.selection.size >= this.opts.maxSelection) {
|
|
2843
|
+
this.opts.onSelectionLimit?.(this.opts.maxSelection);
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2790
2846
|
this.setSelected(id, true);
|
|
2791
2847
|
const seat = this.seatById.get(id);
|
|
2792
2848
|
if (seat) this.opts.onSelect?.(seat);
|
|
@@ -3124,7 +3180,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3124
3180
|
}
|
|
3125
3181
|
/**
|
|
3126
3182
|
* Smoothly glide the camera to frame a section (by id) or a world-space bounds
|
|
3127
|
-
* rect — the Slice 5 "glide in". Pan+zoom tween over
|
|
3183
|
+
* rect — the Slice 5 "glide in". Pan+zoom tween over a calm easeInOutCubic; the
|
|
3128
3184
|
* melt/LOD rides the camera every frame. `prefers-reduced-motion` (or
|
|
3129
3185
|
* `opts.animate === false`) snaps via zoomToBounds. A grab (pointer-down) or a
|
|
3130
3186
|
* newer glide cancels an in-flight one.
|
|
@@ -3156,13 +3212,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3156
3212
|
return;
|
|
3157
3213
|
}
|
|
3158
3214
|
const start = performance.now();
|
|
3159
|
-
const
|
|
3215
|
+
const duration = Math.max(180, Math.min(1200, opts?.durationMs ?? CAMERA_GLIDE_MS));
|
|
3160
3216
|
const step = (now) => {
|
|
3161
3217
|
if (this.destroyed) {
|
|
3162
3218
|
this.glideRaf = 0;
|
|
3163
3219
|
return;
|
|
3164
3220
|
}
|
|
3165
|
-
const raw = Math.min(1, (now - start) /
|
|
3221
|
+
const raw = Math.min(1, (now - start) / duration);
|
|
3166
3222
|
const e = raw < 0.5 ? 4 * raw * raw * raw : 1 - Math.pow(-2 * raw + 2, 3) / 2;
|
|
3167
3223
|
const sc = fromScale + (toScale - fromScale) * e;
|
|
3168
3224
|
this.stage.scale({ x: sc, y: sc });
|
|
@@ -3287,15 +3343,49 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3287
3343
|
if (seat.kind === "booth") continue;
|
|
3288
3344
|
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
3289
3345
|
const status = this.statusById.get(seat.id) ?? "free";
|
|
3290
|
-
const
|
|
3346
|
+
const unavailable = status === "booked" || status === "held" && !this.ownedHold.has(seat.id);
|
|
3347
|
+
if (unavailable) {
|
|
3348
|
+
const cue = new Group({ x: seat.x, y: seat.y, listening: false });
|
|
3349
|
+
if (status === "held") {
|
|
3350
|
+
cue.add(new Rect({
|
|
3351
|
+
x: -4.2,
|
|
3352
|
+
y: -0.7,
|
|
3353
|
+
width: 8.4,
|
|
3354
|
+
height: 6.5,
|
|
3355
|
+
cornerRadius: 1.3,
|
|
3356
|
+
stroke: "#ffffff",
|
|
3357
|
+
strokeWidth: 1.25,
|
|
3358
|
+
listening: false
|
|
3359
|
+
}));
|
|
3360
|
+
cue.add(new Line({
|
|
3361
|
+
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],
|
|
3362
|
+
stroke: "#ffffff",
|
|
3363
|
+
strokeWidth: 1.2,
|
|
3364
|
+
lineCap: "round",
|
|
3365
|
+
lineJoin: "round",
|
|
3366
|
+
listening: false
|
|
3367
|
+
}));
|
|
3368
|
+
} else {
|
|
3369
|
+
cue.add(new Line({
|
|
3370
|
+
points: [-4.2, 4.2, 4.2, -4.2],
|
|
3371
|
+
stroke: "#ffffff",
|
|
3372
|
+
strokeWidth: 1.8,
|
|
3373
|
+
lineCap: "round",
|
|
3374
|
+
listening: false
|
|
3375
|
+
}));
|
|
3376
|
+
}
|
|
3377
|
+
this.labelGroup.add(cue);
|
|
3378
|
+
if (++count >= MAX_LABELS) break;
|
|
3379
|
+
continue;
|
|
3380
|
+
}
|
|
3291
3381
|
const t2 = new Text({
|
|
3292
3382
|
x: seat.x,
|
|
3293
3383
|
y: seat.y,
|
|
3294
|
-
text:
|
|
3295
|
-
fontSize:
|
|
3296
|
-
fontStyle:
|
|
3384
|
+
text: seat.label,
|
|
3385
|
+
fontSize: 7,
|
|
3386
|
+
fontStyle: "600",
|
|
3297
3387
|
fontFamily: this.labelFont(),
|
|
3298
|
-
fill:
|
|
3388
|
+
fill: this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
3299
3389
|
listening: false,
|
|
3300
3390
|
perfectDrawEnabled: false
|
|
3301
3391
|
});
|
|
@@ -3519,6 +3609,7 @@ var PickerController = class {
|
|
|
3519
3609
|
this.opts.onDeselect?.(seat);
|
|
3520
3610
|
this.emitSelectionChange();
|
|
3521
3611
|
},
|
|
3612
|
+
onSelectionLimit: this.opts.onSelectionLimit,
|
|
3522
3613
|
onHover: (seat) => {
|
|
3523
3614
|
this.opts.onHover?.(seat);
|
|
3524
3615
|
if (this.opts.onSeatHover) this.opts.onSeatHover(seat ? this.describeSeat(seat) : null);
|
|
@@ -3586,6 +3677,15 @@ var PickerController = class {
|
|
|
3586
3677
|
this.renderer?.deselect(ids);
|
|
3587
3678
|
this.emitSelectionChange();
|
|
3588
3679
|
}
|
|
3680
|
+
setMaxSelection(maxSelection) {
|
|
3681
|
+
this.maxSelection = Math.max(0, Math.floor(maxSelection));
|
|
3682
|
+
this.renderer?.setMaxSelection?.(this.maxSelection);
|
|
3683
|
+
}
|
|
3684
|
+
select(ids) {
|
|
3685
|
+
const added = this.renderer?.select?.(ids) ?? [];
|
|
3686
|
+
if (added.length) this.emitSelectionChange();
|
|
3687
|
+
return added.map((seat) => this.toSeat(seat));
|
|
3688
|
+
}
|
|
3589
3689
|
// ---- booking machine ------------------------------------------------------
|
|
3590
3690
|
/**
|
|
3591
3691
|
* Hold the current selection (or a given label set). The controller does the
|
|
@@ -3952,6 +4052,17 @@ var PickerController = class {
|
|
|
3952
4052
|
setCategoryFilter(keys) {
|
|
3953
4053
|
this.renderer?.setCategoryFilter?.(keys);
|
|
3954
4054
|
}
|
|
4055
|
+
/** An explicit buyer price-filter action should not only dim the rest of the
|
|
4056
|
+
* chart: clear any older section drill-in and guide the camera to the seats
|
|
4057
|
+
* that remain relevant. Clearing the filter glides back to the full chart. */
|
|
4058
|
+
focusCategoryFilter(keys) {
|
|
4059
|
+
const renderer = this.renderer;
|
|
4060
|
+
if (!renderer) return;
|
|
4061
|
+
renderer.clearSectionFocus?.();
|
|
4062
|
+
this.opts.onSectionFocus?.(null);
|
|
4063
|
+
if (renderer.focusCategories) renderer.focusCategories(keys);
|
|
4064
|
+
else renderer.zoomToFit();
|
|
4065
|
+
}
|
|
3955
4066
|
/** World-space rect currently visible + full chart bounds (F3 minimap frame). */
|
|
3956
4067
|
getViewport() {
|
|
3957
4068
|
const r = this.renderer;
|