@seatlayer/core 0.10.2 → 0.12.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 +222 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +222 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -383,6 +383,13 @@ interface RendererCallbacks {
|
|
|
383
383
|
onDeckTap?: (floorId: string) => void;
|
|
384
384
|
/** Fired after any pan/zoom/resize settles — re-anchor screen-space overlays. */
|
|
385
385
|
onViewChange?: () => void;
|
|
386
|
+
/**
|
|
387
|
+
* Organizer manage-mode only (`manageMode` + `marqueeSelect`): fired on
|
|
388
|
+
* pointer-UP after a rubber-band marquee drag, or a ⌘A/Escape bulk shortcut,
|
|
389
|
+
* with the FULL current selection (selectable seats only). The host toolbar
|
|
390
|
+
* reads this to drive bulk block/unblock. Never fires when manageMode is off.
|
|
391
|
+
*/
|
|
392
|
+
onMarquee?: (seats: ExpandedSeat[]) => void;
|
|
386
393
|
}
|
|
387
394
|
/** Far-zoom level-of-detail rung: whole zones → section blocks → individual seats. */
|
|
388
395
|
type LodRung = 'zones' | 'sections' | 'seats';
|
|
@@ -402,6 +409,21 @@ interface RendererOptions extends RendererCallbacks {
|
|
|
402
409
|
/** ISO 4217 currency for on-map prices ("FROM …"); defaults to money.DEFAULT_CURRENCY.
|
|
403
410
|
* Locale for grouping/symbol placement comes from the active i18n locale. */
|
|
404
411
|
currency?: string;
|
|
412
|
+
/**
|
|
413
|
+
* Organizer manage surface (SDK SeatManager). Opt-in — enables the manage-mode
|
|
414
|
+
* gestures (marquee, ⌘A/Escape) and the bulk-selection helpers. Buyer pan /
|
|
415
|
+
* pinch / tap and every existing code path are byte-identical when this is
|
|
416
|
+
* false (every manage branch is gated on it). Default false.
|
|
417
|
+
*/
|
|
418
|
+
manageMode?: boolean;
|
|
419
|
+
/**
|
|
420
|
+
* When `manageMode` is on, a mouse/pen primary-button drag at the seats rung
|
|
421
|
+
* draws a rubber-band marquee that bulk-selects the seats it covers (emitting
|
|
422
|
+
* `onMarquee` on pointer-up) instead of panning. Touch keeps single-finger
|
|
423
|
+
* pan (pinch to zoom); a middle-button drag pans with a mouse. Disabled below
|
|
424
|
+
* the seats rung (zoom in first). No effect unless `manageMode` is also set.
|
|
425
|
+
*/
|
|
426
|
+
marqueeSelect?: boolean;
|
|
405
427
|
}
|
|
406
428
|
interface ISeatmapRenderer {
|
|
407
429
|
/** Replace the chart. Resets selection and statuses, zooms to fit.
|
|
@@ -426,6 +448,30 @@ interface ISeatmapRenderer {
|
|
|
426
448
|
getStatus(seatId: string): SeatStatus;
|
|
427
449
|
getSelection(): ExpandedSeat[];
|
|
428
450
|
clearSelection(): void;
|
|
451
|
+
/**
|
|
452
|
+
* Dynamically update organizer-only interaction without rebuilding the
|
|
453
|
+
* renderer. No buyer surface calls this; every behavior remains gated by
|
|
454
|
+
* `manageMode` exactly as it is at construction time.
|
|
455
|
+
*/
|
|
456
|
+
setManageInteraction?(options: {
|
|
457
|
+
manageMode: boolean;
|
|
458
|
+
marqueeSelect: boolean;
|
|
459
|
+
selectableStatuses: SeatStatus[];
|
|
460
|
+
maxSelection?: number;
|
|
461
|
+
}): void;
|
|
462
|
+
/** Organizer-only section heat overlay. Values are normalized 0..1. */
|
|
463
|
+
setSectionHeat?(scores: Record<string, number> | null): void;
|
|
464
|
+
/**
|
|
465
|
+
* Manage-mode bulk selection helpers (no-op / empty unless `manageMode`).
|
|
466
|
+
* They select the matching SELECTABLE seats (respecting `selectableStatuses`
|
|
467
|
+
* + closed sections), union with the current selection, and return the seats
|
|
468
|
+
* they added — the SDK SeatManager expands category/row/section picks to
|
|
469
|
+
* labels and drives one batched block/unblock from them.
|
|
470
|
+
*/
|
|
471
|
+
selectAllSelectable?(): ExpandedSeat[];
|
|
472
|
+
selectByLabels?(labels: string[]): ExpandedSeat[];
|
|
473
|
+
/** Selectable seats belonging to a section OR zone id (no selection side-effect). */
|
|
474
|
+
getSelectableInSection?(sectionId: string): ExpandedSeat[];
|
|
429
475
|
/** Programmatic deselect of specific seats (e.g. chip × in the cart). */
|
|
430
476
|
deselect(seatIds: string[]): void;
|
|
431
477
|
/**
|
|
@@ -760,6 +806,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
760
806
|
private currency;
|
|
761
807
|
/** Section/zone ids to render dimmed (organizer manager: held-back inventory). */
|
|
762
808
|
private dimmedSections;
|
|
809
|
+
/** Organizer control-room velocity overlay; absent on buyer surfaces. */
|
|
810
|
+
private sectionHeat;
|
|
763
811
|
/** Phase 2: section/zone ids in the event-level `closed` state — flat grey
|
|
764
812
|
* block, seats greyed + not pickable, but the section stays rendered. */
|
|
765
813
|
private closedSections;
|
|
@@ -806,6 +854,13 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
806
854
|
private panLast;
|
|
807
855
|
/** Cumulative gesture movement in px — clicks are suppressed after a real pan/pinch. */
|
|
808
856
|
private moved;
|
|
857
|
+
/**
|
|
858
|
+
* Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
|
|
859
|
+
* points (overlayLayer rides the stage transform); `rect` is the on-canvas
|
|
860
|
+
* selection band. Null except during an active manage-mode drag.
|
|
861
|
+
*/
|
|
862
|
+
private marquee;
|
|
863
|
+
private marqueeCur;
|
|
809
864
|
constructor(container: HTMLDivElement, options?: RendererOptions);
|
|
810
865
|
setChart(doc: ChartDoc, opts?: {
|
|
811
866
|
floorId?: string;
|
|
@@ -829,7 +884,42 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
829
884
|
getStatus(seatId: string): SeatStatus;
|
|
830
885
|
getSelection(): ExpandedSeat[];
|
|
831
886
|
clearSelection(): void;
|
|
887
|
+
/** Switch organizer interaction in place so the host preserves camera, LOD,
|
|
888
|
+
* focus and live status state while moving between Monitor and Block. */
|
|
889
|
+
setManageInteraction(options: {
|
|
890
|
+
manageMode: boolean;
|
|
891
|
+
marqueeSelect: boolean;
|
|
892
|
+
selectableStatuses: SeatStatus[];
|
|
893
|
+
maxSelection?: number;
|
|
894
|
+
}): void;
|
|
895
|
+
/** Apply a non-destructive velocity treatment to section outlines. Seat
|
|
896
|
+
* category/status fills remain untouched so heat never changes seat meaning. */
|
|
897
|
+
setSectionHeat(scores: Record<string, number> | null): void;
|
|
832
898
|
deselect(seatIds: string[]): void;
|
|
899
|
+
/** Select every selectable seat on the chart (⌘A). Returns the added seats. */
|
|
900
|
+
selectAllSelectable(): ExpandedSeat[];
|
|
901
|
+
/** Select the selectable seats matching these public labels (category/row/
|
|
902
|
+
* section bulk resolves to labels host-side). Returns the newly added seats. */
|
|
903
|
+
selectByLabels(labels: string[]): ExpandedSeat[];
|
|
904
|
+
/** Selectable seats in a section OR zone id — pure read (no selection change). */
|
|
905
|
+
getSelectableInSection(sectionId: string): ExpandedSeat[];
|
|
906
|
+
/**
|
|
907
|
+
* Union `ids` into the selection in one batched pass. Beyond MARQUEE_RING_CAP
|
|
908
|
+
* total selected we skip the per-seat ring nodes (fill paint still marks the
|
|
909
|
+
* seats) so a whole-arena select doesn't spawn thousands of Konva shapes.
|
|
910
|
+
* Returns the full current selection.
|
|
911
|
+
*/
|
|
912
|
+
private selectMany;
|
|
913
|
+
private beginMarquee;
|
|
914
|
+
private updateMarquee;
|
|
915
|
+
private cancelMarquee;
|
|
916
|
+
/**
|
|
917
|
+
* Pointer-up: hit-test the marquee world-rect against the in-memory seat
|
|
918
|
+
* centres (NOT the Konva hit-graph — that's a cached bitmap when zoomed and
|
|
919
|
+
* far slower), keep only SELECTABLE seats, union them into the selection and
|
|
920
|
+
* fire `onMarquee`. A near-zero drag reads as a click: clear the selection.
|
|
921
|
+
*/
|
|
922
|
+
private finishMarquee;
|
|
833
923
|
flashSeat(seatId: string, color?: string): void;
|
|
834
924
|
private onKeyDown;
|
|
835
925
|
/** Nearest seat from `fromId` in a cardinal direction (aligned + close wins). */
|
|
@@ -983,6 +1073,7 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
983
1073
|
* availability count are precomputed here (once), not per frame.
|
|
984
1074
|
*/
|
|
985
1075
|
private renderSection;
|
|
1076
|
+
private refreshSectionHeat;
|
|
986
1077
|
/** Recompute a section's availability-tinted fill + "N LEFT" (cheap; on status change). */
|
|
987
1078
|
private refreshSectionFill;
|
|
988
1079
|
/** True when a section/zone is currently in the `closed` event-state. */
|
package/dist/index.d.ts
CHANGED
|
@@ -383,6 +383,13 @@ interface RendererCallbacks {
|
|
|
383
383
|
onDeckTap?: (floorId: string) => void;
|
|
384
384
|
/** Fired after any pan/zoom/resize settles — re-anchor screen-space overlays. */
|
|
385
385
|
onViewChange?: () => void;
|
|
386
|
+
/**
|
|
387
|
+
* Organizer manage-mode only (`manageMode` + `marqueeSelect`): fired on
|
|
388
|
+
* pointer-UP after a rubber-band marquee drag, or a ⌘A/Escape bulk shortcut,
|
|
389
|
+
* with the FULL current selection (selectable seats only). The host toolbar
|
|
390
|
+
* reads this to drive bulk block/unblock. Never fires when manageMode is off.
|
|
391
|
+
*/
|
|
392
|
+
onMarquee?: (seats: ExpandedSeat[]) => void;
|
|
386
393
|
}
|
|
387
394
|
/** Far-zoom level-of-detail rung: whole zones → section blocks → individual seats. */
|
|
388
395
|
type LodRung = 'zones' | 'sections' | 'seats';
|
|
@@ -402,6 +409,21 @@ interface RendererOptions extends RendererCallbacks {
|
|
|
402
409
|
/** ISO 4217 currency for on-map prices ("FROM …"); defaults to money.DEFAULT_CURRENCY.
|
|
403
410
|
* Locale for grouping/symbol placement comes from the active i18n locale. */
|
|
404
411
|
currency?: string;
|
|
412
|
+
/**
|
|
413
|
+
* Organizer manage surface (SDK SeatManager). Opt-in — enables the manage-mode
|
|
414
|
+
* gestures (marquee, ⌘A/Escape) and the bulk-selection helpers. Buyer pan /
|
|
415
|
+
* pinch / tap and every existing code path are byte-identical when this is
|
|
416
|
+
* false (every manage branch is gated on it). Default false.
|
|
417
|
+
*/
|
|
418
|
+
manageMode?: boolean;
|
|
419
|
+
/**
|
|
420
|
+
* When `manageMode` is on, a mouse/pen primary-button drag at the seats rung
|
|
421
|
+
* draws a rubber-band marquee that bulk-selects the seats it covers (emitting
|
|
422
|
+
* `onMarquee` on pointer-up) instead of panning. Touch keeps single-finger
|
|
423
|
+
* pan (pinch to zoom); a middle-button drag pans with a mouse. Disabled below
|
|
424
|
+
* the seats rung (zoom in first). No effect unless `manageMode` is also set.
|
|
425
|
+
*/
|
|
426
|
+
marqueeSelect?: boolean;
|
|
405
427
|
}
|
|
406
428
|
interface ISeatmapRenderer {
|
|
407
429
|
/** Replace the chart. Resets selection and statuses, zooms to fit.
|
|
@@ -426,6 +448,30 @@ interface ISeatmapRenderer {
|
|
|
426
448
|
getStatus(seatId: string): SeatStatus;
|
|
427
449
|
getSelection(): ExpandedSeat[];
|
|
428
450
|
clearSelection(): void;
|
|
451
|
+
/**
|
|
452
|
+
* Dynamically update organizer-only interaction without rebuilding the
|
|
453
|
+
* renderer. No buyer surface calls this; every behavior remains gated by
|
|
454
|
+
* `manageMode` exactly as it is at construction time.
|
|
455
|
+
*/
|
|
456
|
+
setManageInteraction?(options: {
|
|
457
|
+
manageMode: boolean;
|
|
458
|
+
marqueeSelect: boolean;
|
|
459
|
+
selectableStatuses: SeatStatus[];
|
|
460
|
+
maxSelection?: number;
|
|
461
|
+
}): void;
|
|
462
|
+
/** Organizer-only section heat overlay. Values are normalized 0..1. */
|
|
463
|
+
setSectionHeat?(scores: Record<string, number> | null): void;
|
|
464
|
+
/**
|
|
465
|
+
* Manage-mode bulk selection helpers (no-op / empty unless `manageMode`).
|
|
466
|
+
* They select the matching SELECTABLE seats (respecting `selectableStatuses`
|
|
467
|
+
* + closed sections), union with the current selection, and return the seats
|
|
468
|
+
* they added — the SDK SeatManager expands category/row/section picks to
|
|
469
|
+
* labels and drives one batched block/unblock from them.
|
|
470
|
+
*/
|
|
471
|
+
selectAllSelectable?(): ExpandedSeat[];
|
|
472
|
+
selectByLabels?(labels: string[]): ExpandedSeat[];
|
|
473
|
+
/** Selectable seats belonging to a section OR zone id (no selection side-effect). */
|
|
474
|
+
getSelectableInSection?(sectionId: string): ExpandedSeat[];
|
|
429
475
|
/** Programmatic deselect of specific seats (e.g. chip × in the cart). */
|
|
430
476
|
deselect(seatIds: string[]): void;
|
|
431
477
|
/**
|
|
@@ -760,6 +806,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
760
806
|
private currency;
|
|
761
807
|
/** Section/zone ids to render dimmed (organizer manager: held-back inventory). */
|
|
762
808
|
private dimmedSections;
|
|
809
|
+
/** Organizer control-room velocity overlay; absent on buyer surfaces. */
|
|
810
|
+
private sectionHeat;
|
|
763
811
|
/** Phase 2: section/zone ids in the event-level `closed` state — flat grey
|
|
764
812
|
* block, seats greyed + not pickable, but the section stays rendered. */
|
|
765
813
|
private closedSections;
|
|
@@ -806,6 +854,13 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
806
854
|
private panLast;
|
|
807
855
|
/** Cumulative gesture movement in px — clicks are suppressed after a real pan/pinch. */
|
|
808
856
|
private moved;
|
|
857
|
+
/**
|
|
858
|
+
* Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
|
|
859
|
+
* points (overlayLayer rides the stage transform); `rect` is the on-canvas
|
|
860
|
+
* selection band. Null except during an active manage-mode drag.
|
|
861
|
+
*/
|
|
862
|
+
private marquee;
|
|
863
|
+
private marqueeCur;
|
|
809
864
|
constructor(container: HTMLDivElement, options?: RendererOptions);
|
|
810
865
|
setChart(doc: ChartDoc, opts?: {
|
|
811
866
|
floorId?: string;
|
|
@@ -829,7 +884,42 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
829
884
|
getStatus(seatId: string): SeatStatus;
|
|
830
885
|
getSelection(): ExpandedSeat[];
|
|
831
886
|
clearSelection(): void;
|
|
887
|
+
/** Switch organizer interaction in place so the host preserves camera, LOD,
|
|
888
|
+
* focus and live status state while moving between Monitor and Block. */
|
|
889
|
+
setManageInteraction(options: {
|
|
890
|
+
manageMode: boolean;
|
|
891
|
+
marqueeSelect: boolean;
|
|
892
|
+
selectableStatuses: SeatStatus[];
|
|
893
|
+
maxSelection?: number;
|
|
894
|
+
}): void;
|
|
895
|
+
/** Apply a non-destructive velocity treatment to section outlines. Seat
|
|
896
|
+
* category/status fills remain untouched so heat never changes seat meaning. */
|
|
897
|
+
setSectionHeat(scores: Record<string, number> | null): void;
|
|
832
898
|
deselect(seatIds: string[]): void;
|
|
899
|
+
/** Select every selectable seat on the chart (⌘A). Returns the added seats. */
|
|
900
|
+
selectAllSelectable(): ExpandedSeat[];
|
|
901
|
+
/** Select the selectable seats matching these public labels (category/row/
|
|
902
|
+
* section bulk resolves to labels host-side). Returns the newly added seats. */
|
|
903
|
+
selectByLabels(labels: string[]): ExpandedSeat[];
|
|
904
|
+
/** Selectable seats in a section OR zone id — pure read (no selection change). */
|
|
905
|
+
getSelectableInSection(sectionId: string): ExpandedSeat[];
|
|
906
|
+
/**
|
|
907
|
+
* Union `ids` into the selection in one batched pass. Beyond MARQUEE_RING_CAP
|
|
908
|
+
* total selected we skip the per-seat ring nodes (fill paint still marks the
|
|
909
|
+
* seats) so a whole-arena select doesn't spawn thousands of Konva shapes.
|
|
910
|
+
* Returns the full current selection.
|
|
911
|
+
*/
|
|
912
|
+
private selectMany;
|
|
913
|
+
private beginMarquee;
|
|
914
|
+
private updateMarquee;
|
|
915
|
+
private cancelMarquee;
|
|
916
|
+
/**
|
|
917
|
+
* Pointer-up: hit-test the marquee world-rect against the in-memory seat
|
|
918
|
+
* centres (NOT the Konva hit-graph — that's a cached bitmap when zoomed and
|
|
919
|
+
* far slower), keep only SELECTABLE seats, union them into the selection and
|
|
920
|
+
* fire `onMarquee`. A near-zero drag reads as a click: clear the selection.
|
|
921
|
+
*/
|
|
922
|
+
private finishMarquee;
|
|
833
923
|
flashSeat(seatId: string, color?: string): void;
|
|
834
924
|
private onKeyDown;
|
|
835
925
|
/** Nearest seat from `fromId` in a cardinal direction (aligned + close wins). */
|
|
@@ -983,6 +1073,7 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
|
|
|
983
1073
|
* availability count are precomputed here (once), not per frame.
|
|
984
1074
|
*/
|
|
985
1075
|
private renderSection;
|
|
1076
|
+
private refreshSectionHeat;
|
|
986
1077
|
/** Recompute a section's availability-tinted fill + "N LEFT" (cheap; on status change). */
|
|
987
1078
|
private refreshSectionFill;
|
|
988
1079
|
/** True when a section/zone is currently in the `closed` event-state. */
|
package/dist/index.js
CHANGED
|
@@ -637,6 +637,7 @@ var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
|
637
637
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
638
638
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
639
639
|
var MAX_LABELS = 700;
|
|
640
|
+
var MARQUEE_RING_CAP = 2500;
|
|
640
641
|
var ISO_ANGLE_DEG = -11.5;
|
|
641
642
|
var ISO_SQUASH = 0.58;
|
|
642
643
|
var LIFT_PER_STEP = 58;
|
|
@@ -813,6 +814,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
813
814
|
this.catPrice = /* @__PURE__ */ new Map();
|
|
814
815
|
/** Section/zone ids to render dimmed (organizer manager: held-back inventory). */
|
|
815
816
|
this.dimmedSections = /* @__PURE__ */ new Set();
|
|
817
|
+
/** Organizer control-room velocity overlay; absent on buyer surfaces. */
|
|
818
|
+
this.sectionHeat = /* @__PURE__ */ new Map();
|
|
816
819
|
/** Phase 2: section/zone ids in the event-level `closed` state — flat grey
|
|
817
820
|
* block, seats greyed + not pickable, but the section stays rendered. */
|
|
818
821
|
this.closedSections = /* @__PURE__ */ new Set();
|
|
@@ -863,8 +866,28 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
863
866
|
this.panLast = null;
|
|
864
867
|
/** Cumulative gesture movement in px — clicks are suppressed after a real pan/pinch. */
|
|
865
868
|
this.moved = 0;
|
|
869
|
+
/**
|
|
870
|
+
* Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
|
|
871
|
+
* points (overlayLayer rides the stage transform); `rect` is the on-canvas
|
|
872
|
+
* selection band. Null except during an active manage-mode drag.
|
|
873
|
+
*/
|
|
874
|
+
this.marquee = null;
|
|
875
|
+
this.marqueeCur = null;
|
|
866
876
|
// ---- keyboard navigation (accessibility) ----------------------------------
|
|
867
877
|
this.onKeyDown = (e) => {
|
|
878
|
+
if (this.opts.manageMode) {
|
|
879
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === "a" || e.key === "A")) {
|
|
880
|
+
e.preventDefault();
|
|
881
|
+
this.opts.onMarquee?.(this.selectAllSelectable());
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (e.key === "Escape" && this.selection.size) {
|
|
885
|
+
e.preventDefault();
|
|
886
|
+
this.clearSelection();
|
|
887
|
+
this.opts.onMarquee?.([]);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
868
891
|
const dir = e.key === "ArrowLeft" ? { x: -1, y: 0 } : e.key === "ArrowRight" ? { x: 1, y: 0 } : e.key === "ArrowUp" ? { x: 0, y: -1 } : e.key === "ArrowDown" ? { x: 0, y: 1 } : null;
|
|
869
892
|
if (dir) {
|
|
870
893
|
e.preventDefault();
|
|
@@ -887,9 +910,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
887
910
|
this.pointers.set(e.pointerId, this.toLocal(e));
|
|
888
911
|
if (this.pointers.size === 1) {
|
|
889
912
|
this.moved = 0;
|
|
890
|
-
this.panLast = this.toLocal(e);
|
|
891
913
|
this.pinch = null;
|
|
914
|
+
if (this.opts.manageMode && this.opts.marqueeSelect && e.pointerType !== "touch" && e.button === 0 && this.getRung() === "seats") {
|
|
915
|
+
this.beginMarquee(this.toLocal(e));
|
|
916
|
+
this.panLast = null;
|
|
917
|
+
} else {
|
|
918
|
+
this.panLast = this.toLocal(e);
|
|
919
|
+
}
|
|
892
920
|
} else if (this.pointers.size === 2) {
|
|
921
|
+
if (this.marquee) this.cancelMarquee();
|
|
893
922
|
const [a, b] = [...this.pointers.values()];
|
|
894
923
|
const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
895
924
|
const s = this.stage.scaleX();
|
|
@@ -908,6 +937,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
908
937
|
const prev = this.pointers.get(e.pointerId);
|
|
909
938
|
this.moved += Math.hypot(p.x - prev.x, p.y - prev.y);
|
|
910
939
|
this.pointers.set(e.pointerId, p);
|
|
940
|
+
if (this.marquee) {
|
|
941
|
+
this.updateMarquee(p);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
911
944
|
if (this.pinch && this.pointers.size >= 2) {
|
|
912
945
|
const [a, b] = [...this.pointers.values()];
|
|
913
946
|
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
@@ -933,6 +966,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
933
966
|
}
|
|
934
967
|
};
|
|
935
968
|
this.onPointerEnd = (e) => {
|
|
969
|
+
if (this.marquee) {
|
|
970
|
+
this.finishMarquee();
|
|
971
|
+
this.pointers.delete(e.pointerId);
|
|
972
|
+
if (this.pointers.size === 0) this.panLast = null;
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
936
975
|
this.pointers.delete(e.pointerId);
|
|
937
976
|
if (this.pointers.size < 2) this.pinch = null;
|
|
938
977
|
if (this.pointers.size === 1) {
|
|
@@ -1158,6 +1197,31 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1158
1197
|
for (const id of ids) this.setSelected(id, false, true);
|
|
1159
1198
|
this.overlayLayer.batchDraw();
|
|
1160
1199
|
}
|
|
1200
|
+
/** Switch organizer interaction in place so the host preserves camera, LOD,
|
|
1201
|
+
* focus and live status state while moving between Monitor and Block. */
|
|
1202
|
+
setManageInteraction(options) {
|
|
1203
|
+
if (this.marquee) this.cancelMarquee();
|
|
1204
|
+
this.panLast = null;
|
|
1205
|
+
this.opts.manageMode = options.manageMode;
|
|
1206
|
+
this.opts.marqueeSelect = options.marqueeSelect;
|
|
1207
|
+
this.opts.selectableStatuses = [...options.selectableStatuses];
|
|
1208
|
+
if (options.maxSelection != null) this.opts.maxSelection = options.maxSelection;
|
|
1209
|
+
const invalid = [...this.selection].filter((id) => !this.isSelectable(id));
|
|
1210
|
+
if (invalid.length) this.deselect(invalid);
|
|
1211
|
+
if (!options.manageMode && this.selection.size) this.clearSelection();
|
|
1212
|
+
this.container.style.cursor = "default";
|
|
1213
|
+
this.overlayLayer.batchDraw();
|
|
1214
|
+
}
|
|
1215
|
+
/** Apply a non-destructive velocity treatment to section outlines. Seat
|
|
1216
|
+
* category/status fills remain untouched so heat never changes seat meaning. */
|
|
1217
|
+
setSectionHeat(scores) {
|
|
1218
|
+
this.sectionHeat.clear();
|
|
1219
|
+
for (const [sectionId, raw] of Object.entries(scores ?? {})) {
|
|
1220
|
+
if (Number.isFinite(raw)) this.sectionHeat.set(sectionId, Math.max(0, Math.min(1, raw)));
|
|
1221
|
+
}
|
|
1222
|
+
for (const section of this.sections) this.refreshSectionHeat(section);
|
|
1223
|
+
this.bgLayer.batchDraw();
|
|
1224
|
+
}
|
|
1161
1225
|
deselect(seatIds) {
|
|
1162
1226
|
let changed = false;
|
|
1163
1227
|
for (const id of seatIds) {
|
|
@@ -1168,6 +1232,144 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1168
1232
|
}
|
|
1169
1233
|
if (changed) this.overlayLayer.batchDraw();
|
|
1170
1234
|
}
|
|
1235
|
+
// ---- manage-mode bulk selection (SDK SeatManager) -------------------------
|
|
1236
|
+
// All option-gated: no-ops (or empty) unless `manageMode` is set, so buyer
|
|
1237
|
+
// surfaces that never pass the flag can't reach any of this.
|
|
1238
|
+
/** Select every selectable seat on the chart (⌘A). Returns the added seats. */
|
|
1239
|
+
selectAllSelectable() {
|
|
1240
|
+
if (!this.opts.manageMode) return [];
|
|
1241
|
+
const ids = [];
|
|
1242
|
+
for (const seat of this.seats) if (this.isSelectable(seat.id)) ids.push(seat.id);
|
|
1243
|
+
return this.selectMany(ids);
|
|
1244
|
+
}
|
|
1245
|
+
/** Select the selectable seats matching these public labels (category/row/
|
|
1246
|
+
* section bulk resolves to labels host-side). Returns the newly added seats. */
|
|
1247
|
+
selectByLabels(labels) {
|
|
1248
|
+
if (!this.opts.manageMode) return [];
|
|
1249
|
+
const want = new Set(labels);
|
|
1250
|
+
const ids = [];
|
|
1251
|
+
for (const seat of this.seats) {
|
|
1252
|
+
if (want.has(seat.label) && this.isSelectable(seat.id)) ids.push(seat.id);
|
|
1253
|
+
}
|
|
1254
|
+
return this.selectMany(ids);
|
|
1255
|
+
}
|
|
1256
|
+
/** Selectable seats in a section OR zone id — pure read (no selection change). */
|
|
1257
|
+
getSelectableInSection(sectionId) {
|
|
1258
|
+
const out = [];
|
|
1259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1260
|
+
for (const sec of this.sections) {
|
|
1261
|
+
if (sec.id !== sectionId && sec.zone !== sectionId) continue;
|
|
1262
|
+
for (const id of sec.memberIds) {
|
|
1263
|
+
if (seen.has(id)) continue;
|
|
1264
|
+
seen.add(id);
|
|
1265
|
+
if (!this.isSelectable(id)) continue;
|
|
1266
|
+
const s = this.seatById.get(id);
|
|
1267
|
+
if (s) out.push(s);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return out;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Union `ids` into the selection in one batched pass. Beyond MARQUEE_RING_CAP
|
|
1274
|
+
* total selected we skip the per-seat ring nodes (fill paint still marks the
|
|
1275
|
+
* seats) so a whole-arena select doesn't spawn thousands of Konva shapes.
|
|
1276
|
+
* Returns the full current selection.
|
|
1277
|
+
*/
|
|
1278
|
+
selectMany(ids) {
|
|
1279
|
+
const fresh = ids.filter((id) => !this.selection.has(id));
|
|
1280
|
+
if (!fresh.length) return this.getSelection();
|
|
1281
|
+
const drawRings = this.selection.size + fresh.length <= MARQUEE_RING_CAP;
|
|
1282
|
+
for (const id of fresh) {
|
|
1283
|
+
if (drawRings) {
|
|
1284
|
+
this.setSelected(id, true, true);
|
|
1285
|
+
} else {
|
|
1286
|
+
this.selection.add(id);
|
|
1287
|
+
const c = this.circleById.get(id);
|
|
1288
|
+
if (c) this.paintSeat(c, id);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (!this.cached) this.seatLayer.batchDraw();
|
|
1292
|
+
this.overlayLayer.batchDraw();
|
|
1293
|
+
return this.getSelection();
|
|
1294
|
+
}
|
|
1295
|
+
// ---- manage-mode marquee gesture ------------------------------------------
|
|
1296
|
+
beginMarquee(clientPt) {
|
|
1297
|
+
const w = this.screenToWorld(clientPt);
|
|
1298
|
+
const s = this.stage.scaleX() || 1;
|
|
1299
|
+
const rect = new Rect({
|
|
1300
|
+
x: w.x,
|
|
1301
|
+
y: w.y,
|
|
1302
|
+
width: 0,
|
|
1303
|
+
height: 0,
|
|
1304
|
+
stroke: this.effSelection,
|
|
1305
|
+
strokeWidth: 1.5 / s,
|
|
1306
|
+
// world units → ~constant on-screen px under the stage scale
|
|
1307
|
+
dash: [5 / s, 4 / s],
|
|
1308
|
+
fill: "rgba(110,123,255,0.10)",
|
|
1309
|
+
listening: false,
|
|
1310
|
+
perfectDrawEnabled: false,
|
|
1311
|
+
shadowForStrokeEnabled: false
|
|
1312
|
+
});
|
|
1313
|
+
this.overlayLayer.add(rect);
|
|
1314
|
+
this.marquee = { start: w, rect };
|
|
1315
|
+
this.marqueeCur = w;
|
|
1316
|
+
this.overlayLayer.batchDraw();
|
|
1317
|
+
}
|
|
1318
|
+
updateMarquee(clientPt) {
|
|
1319
|
+
if (!this.marquee) return;
|
|
1320
|
+
const w = this.screenToWorld(clientPt);
|
|
1321
|
+
const { start, rect } = this.marquee;
|
|
1322
|
+
rect.setAttrs({
|
|
1323
|
+
x: Math.min(start.x, w.x),
|
|
1324
|
+
y: Math.min(start.y, w.y),
|
|
1325
|
+
width: Math.abs(w.x - start.x),
|
|
1326
|
+
height: Math.abs(w.y - start.y)
|
|
1327
|
+
});
|
|
1328
|
+
this.marqueeCur = w;
|
|
1329
|
+
this.overlayLayer.batchDraw();
|
|
1330
|
+
}
|
|
1331
|
+
cancelMarquee() {
|
|
1332
|
+
if (!this.marquee) return;
|
|
1333
|
+
this.marquee.rect.destroy();
|
|
1334
|
+
this.marquee = null;
|
|
1335
|
+
this.marqueeCur = null;
|
|
1336
|
+
this.overlayLayer.batchDraw();
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Pointer-up: hit-test the marquee world-rect against the in-memory seat
|
|
1340
|
+
* centres (NOT the Konva hit-graph — that's a cached bitmap when zoomed and
|
|
1341
|
+
* far slower), keep only SELECTABLE seats, union them into the selection and
|
|
1342
|
+
* fire `onMarquee`. A near-zero drag reads as a click: clear the selection.
|
|
1343
|
+
*/
|
|
1344
|
+
finishMarquee() {
|
|
1345
|
+
const m = this.marquee;
|
|
1346
|
+
this.marquee = null;
|
|
1347
|
+
if (!m) return;
|
|
1348
|
+
m.rect.destroy();
|
|
1349
|
+
this.overlayLayer.batchDraw();
|
|
1350
|
+
const cur = this.marqueeCur ?? m.start;
|
|
1351
|
+
this.marqueeCur = null;
|
|
1352
|
+
const x0 = Math.min(m.start.x, cur.x);
|
|
1353
|
+
const x1 = Math.max(m.start.x, cur.x);
|
|
1354
|
+
const y0 = Math.min(m.start.y, cur.y);
|
|
1355
|
+
const y1 = Math.max(m.start.y, cur.y);
|
|
1356
|
+
const s = this.stage.scaleX() || 1;
|
|
1357
|
+
if ((x1 - x0) * s < 4 && (y1 - y0) * s < 4) {
|
|
1358
|
+
if (this.selection.size) {
|
|
1359
|
+
this.clearSelection();
|
|
1360
|
+
this.opts.onMarquee?.([]);
|
|
1361
|
+
}
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
const ids = [];
|
|
1365
|
+
for (const seat of this.seats) {
|
|
1366
|
+
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
1367
|
+
if (!this.isSelectable(seat.id)) continue;
|
|
1368
|
+
ids.push(seat.id);
|
|
1369
|
+
}
|
|
1370
|
+
this.selectMany(ids);
|
|
1371
|
+
this.opts.onMarquee?.(this.getSelection());
|
|
1372
|
+
}
|
|
1171
1373
|
flashSeat(seatId, color = "#f43f5e") {
|
|
1172
1374
|
const seat = this.seatById.get(seatId);
|
|
1173
1375
|
if (!seat) return;
|
|
@@ -2216,6 +2418,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2216
2418
|
total: memberIds.length,
|
|
2217
2419
|
free,
|
|
2218
2420
|
baseFill,
|
|
2421
|
+
outlineTint,
|
|
2219
2422
|
outlinePoly,
|
|
2220
2423
|
blockPoly,
|
|
2221
2424
|
rowLines,
|
|
@@ -2228,8 +2431,26 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2228
2431
|
};
|
|
2229
2432
|
for (const id of memberIds) this.seatSection.set(id, sec);
|
|
2230
2433
|
this.refreshSectionFill(sec);
|
|
2434
|
+
this.refreshSectionHeat(sec);
|
|
2231
2435
|
this.sections.push(sec);
|
|
2232
2436
|
}
|
|
2437
|
+
refreshSectionHeat(sec) {
|
|
2438
|
+
const raw = this.sectionHeat.get(sec.id) ?? (sec.zone ? this.sectionHeat.get(sec.zone) : void 0);
|
|
2439
|
+
if (raw == null || raw <= 0) {
|
|
2440
|
+
sec.outlinePoly.stroke(rgba(sec.outlineTint, 0.5));
|
|
2441
|
+
sec.outlinePoly.fill(rgba(sec.outlineTint, 0.08));
|
|
2442
|
+
sec.outlinePoly.strokeWidth(1.75);
|
|
2443
|
+
sec.outlinePoly.shadowOpacity(0);
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
const color = lerpColor("#f4b740", "#ef4444", raw);
|
|
2447
|
+
sec.outlinePoly.stroke(rgba(color, 0.72 + raw * 0.25));
|
|
2448
|
+
sec.outlinePoly.fill(rgba(color, 0.08 + raw * 0.13));
|
|
2449
|
+
sec.outlinePoly.strokeWidth(2 + raw * 3.5);
|
|
2450
|
+
sec.outlinePoly.shadowColor(color);
|
|
2451
|
+
sec.outlinePoly.shadowBlur(4 + raw * 12);
|
|
2452
|
+
sec.outlinePoly.shadowOpacity(0.25 + raw * 0.45);
|
|
2453
|
+
}
|
|
2233
2454
|
/** Recompute a section's availability-tinted fill + "N LEFT" (cheap; on status change). */
|
|
2234
2455
|
refreshSectionFill(sec) {
|
|
2235
2456
|
sec.blockPoly.fill(this.sectionBlockFill(sec));
|