UNPKG

ngx-venue-map

Version:

Interactive venue seating map component for Angular applications

1,908 lines 87 kB
import * as i0 from '@angular/core';
import { input, model, Component, Injectable, inject, signal, computed, effect, output, ViewChild, ViewEncapsulation } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { throttle, debounce, cloneDeep, merge } from 'lodash-es';
import { v4 } from 'uuid';
import { BehaviorSubject } from 'rxjs';
import { fabric } from 'fabric';

// ============================================================================
// ENUMS
// ============================================================================
var VenueMapType;
(function (VenueMapType) {
    VenueMapType["GRADE"] = "grade";
    VenueMapType["GRADE_PRICE"] = "gradePrice";
})(VenueMapType || (VenueMapType = {}));
// =====s=======================================================================

const DEFAULT_SIZE_SHAPE = {
    width: 0,
    height: 0,
    padding: 0,
    stroke: 0,
    offsetLeft: 0,
    offsetTop: 0,
    cornerRadius: 0,
};
const DEFAULT_MAP_CONFIG = {
    circle: {
        ...DEFAULT_SIZE_SHAPE,
        width: 50,
        height: 50,
        stroke: 2,
        padding: 10,
    },
    fontSize: 20,
    bubble: {
        ...DEFAULT_SIZE_SHAPE,
        width: 30,
        height: 20,
    },
    corner: {
        ...DEFAULT_SIZE_SHAPE,
        width: 20,
    },
    arrow: {
        ...DEFAULT_SIZE_SHAPE,
        width: 15,
        height: 10,
        padding: 12,
    },
    offsetHeight: 106,
    minimap: {
        scale: 0.12,
        opacity: 0.5,
    },
    zoom: {
        min: 1,
        max: 2.5,
        step: 0.2,
        showMiniMapAtLevel: 1.2,
    }
};
// Default theme colors that match the use cases
const DEFAULT_VENUE_MAP_THEME = {
    // Canvas and background colors
    canvasBackgroundColor: '#ffffff',
    // Grade visualization colors
    gradeStrokeColor: '#ffffff',
    gradeTextColor: '#ffffff',
    // Price visualization colors
    priceBackgroundColor: '#ffffff',
    priceTextColor: '#000000',
    priceBubbleStrokeColor: '#ffffff',
    // Interactive elements
    miniMapViewportColor: '#EE2039',
    // Seat colors
    availableSeatColor: '#ffffff',
    unavailableSeatColor: '#969696',
    selectedSeatColor: '#0071EAB2',
    selectedSeatBorderColor: '#0071EA',
    availableSeatBorderColor: '#0071EA',
    disabledSeatColor: '#2F2F2F',
    // Special seat types
    premiumSeatColor: '#B60000',
    premiumSeatHoverColor: '#B60000B2',
    goldSeatColor: '#8F7400',
    // UI colors
    shadowColor: 'rgba(0,0,0,0.22)',
    darkTextColor: '#767676',
};
const MULTIPLIER_GRADE_ICON = 1.5;

class SwitchComponent {
    handleOnChange = input();
    className = input('');
    checked = model(false);
    onChange(event) {
        const target = event.target;
        this.checked.set(target.checked);
        this.handleOnChange()?.(target.checked);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: SwitchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.5", type: SwitchComponent, isStandalone: true, selector: "app-switch", inputs: { handleOnChange: { classPropertyName: "handleOnChange", publicName: "handleOnChange", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, ngImport: i0, template: "<label class=\"switch\">\n  <input\n    type=\"checkbox\"\n    [checked]=\"checked()\"\n    (change)=\"onChange($event)\"\n    class=\"sr-only switch-input\"\n  />\n  <div class=\"switch-indicator\" [ngClass]=\"className()\"></div>\n</label>\n", styles: [".switch{display:flex;align-items:center;cursor:pointer}.switch-input:checked~.switch-indicator{background-color:#707086}.switch-input:checked~.switch-indicator:after{transform:translate(100%) rotate(0) skew(0) skewY(0) scaleX(1) scaleY(1);background-image:url(\"data:image/svg+xml,%3Csvg%20width%3D'10'%20height%3D'8'%20viewBox%3D'0%200%2010%208'%20fill%3D'none'%20xmlns%3D'http%3A//www.w3.org/2000/svg'%3E%3Cpath%20fill-rule%3D'evenodd'%20clip-rule%3D'evenodd'%20d%3D'M8.3589%200.298767L3.54253%205.03255L1.64707%203.16783L0.285767%204.49762L3.54236%207.7014L9.71297%201.63087L8.3589%200.298767Z'%20fill%3D'%23112'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:10px 8px;inset-inline-start:6px}.switch-input:checked~.switch-indicator.bg-black{background-color:#112}.switch-input:checked~.switch-indicator.bg-black:after{background-image:url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2210%22%20viewBox%3D%220%200%2012%2010%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20d%3D%22M4.0001%207.7799L1.2201%204.9999L0.273438%205.9399L4.0001%209.66656L12.0001%201.66656L11.0601%200.726562L4.0001%207.7799Z%22%20fill%3D%22%23111122%22/%3E%3C/svg%3E)}.switch-indicator{position:relative;border-radius:1rem;transition:transform;background-color:#969696;width:3rem;height:1.5rem}.switch-indicator:after{content:\"\";position:absolute;transition:transform .15s cubic-bezier(.4,0,.2,1);background-color:#fff;border-width:1px;border-color:#fff;width:1.25rem;height:1.25rem;border-radius:100%;top:.125rem;inset-inline-start:2px}.switch-indicator-medium{width:3.25rem;height:2rem}.switch-indicator-medium:after{width:1.5rem;height:1.5rem;top:.25rem;left:.25rem}.switch-input:checked~.switch-indicator-medium:after{transform:translate(calc(100% - 6px)) rotate(0) skew(0) skewY(0) scaleX(1) scaleY(1)}.switch .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: SwitchComponent, decorators: [{
            type: Component,
            args: [{ selector: 'app-switch', standalone: true, imports: [FormsModule, CommonModule], template: "<label class=\"switch\">\n  <input\n    type=\"checkbox\"\n    [checked]=\"checked()\"\n    (change)=\"onChange($event)\"\n    class=\"sr-only switch-input\"\n  />\n  <div class=\"switch-indicator\" [ngClass]=\"className()\"></div>\n</label>\n", styles: [".switch{display:flex;align-items:center;cursor:pointer}.switch-input:checked~.switch-indicator{background-color:#707086}.switch-input:checked~.switch-indicator:after{transform:translate(100%) rotate(0) skew(0) skewY(0) scaleX(1) scaleY(1);background-image:url(\"data:image/svg+xml,%3Csvg%20width%3D'10'%20height%3D'8'%20viewBox%3D'0%200%2010%208'%20fill%3D'none'%20xmlns%3D'http%3A//www.w3.org/2000/svg'%3E%3Cpath%20fill-rule%3D'evenodd'%20clip-rule%3D'evenodd'%20d%3D'M8.3589%200.298767L3.54253%205.03255L1.64707%203.16783L0.285767%204.49762L3.54236%207.7014L9.71297%201.63087L8.3589%200.298767Z'%20fill%3D'%23112'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:10px 8px;inset-inline-start:6px}.switch-input:checked~.switch-indicator.bg-black{background-color:#112}.switch-input:checked~.switch-indicator.bg-black:after{background-image:url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2210%22%20viewBox%3D%220%200%2012%2010%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20d%3D%22M4.0001%207.7799L1.2201%204.9999L0.273438%205.9399L4.0001%209.66656L12.0001%201.66656L11.0601%200.726562L4.0001%207.7799Z%22%20fill%3D%22%23111122%22/%3E%3C/svg%3E)}.switch-indicator{position:relative;border-radius:1rem;transition:transform;background-color:#969696;width:3rem;height:1.5rem}.switch-indicator:after{content:\"\";position:absolute;transition:transform .15s cubic-bezier(.4,0,.2,1);background-color:#fff;border-width:1px;border-color:#fff;width:1.25rem;height:1.25rem;border-radius:100%;top:.125rem;inset-inline-start:2px}.switch-indicator-medium{width:3.25rem;height:2rem}.switch-indicator-medium:after{width:1.5rem;height:1.5rem;top:.25rem;left:.25rem}.switch-input:checked~.switch-indicator-medium:after{transform:translate(calc(100% - 6px)) rotate(0) skew(0) skewY(0) scaleX(1) scaleY(1)}.switch .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}\n"] }]
        }] });

/**
 * Service responsible for rendering venue map images and backgrounds.
 * Handles both single images and multi-part image loading.
 * For multi-part images, follows a file naming pattern: col_row.ext
 * Example: 0_0.png, 0_1.png, 1_0.png, 1_1.png
 * Also manages background image loading and canvas setup for venue maps.
 */
class VenueMapRenderService {
    // ============================================================================
    // MULTI-PART IMAGE METHODS
    // ============================================================================
    /**
     * Determines if the path represents multiple image parts
     * @param path - Single path string or array of paths
     * @returns true if multiple parts should be rendered
     */
    isMultiPartImage(path) {
        return Array.isArray(path);
    }
    /**
     * Generates image paths based on pattern col_row.ext
     * @param basePath - Base path template (e.g., '/images/map/parts/{col}_{row}.png')
     * @param cols - Number of columns
     * @param rows - Number of rows
     * @returns Array of image paths
     */
    generateImagePaths(basePath, cols, rows) {
        const paths = [];
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < cols; col++) {
                const path = basePath
                    .replace('{col}', col.toString())
                    .replace('{row}', row.toString());
                paths.push(path);
            }
        }
        return paths;
    }
    /**
     * Parses column and row from filename pattern col_row.ext
     * @param filename - Filename to parse (e.g., '0_1.png')
     * @returns Object with col and row numbers, or null if pattern doesn't match
     */
    parseImageCoordinates(filename) {
        // Extract filename without path
        const name = filename.split('/').pop() || filename;
        // Remove extension
        const nameWithoutExt = name.split('.')[0];
        // Match pattern: col_row
        const match = nameWithoutExt.match(/^(\d+)_(\d+)$/);
        if (match) {
            return {
                col: parseInt(match[1], 10),
                row: parseInt(match[2], 10)
            };
        }
        return null;
    }
    /**
     * Calculates grid dimensions from array of image paths
     * @param paths - Array of image paths
     * @returns Object with cols and rows dimensions
     */
    calculateGridDimensions(paths) {
        let maxCol = 0;
        let maxRow = 0;
        for (const path of paths) {
            const coords = this.parseImageCoordinates(path);
            if (coords) {
                maxCol = Math.max(maxCol, coords.col);
                maxRow = Math.max(maxRow, coords.row);
            }
        }
        return {
            cols: maxCol + 1,
            rows: maxRow + 1
        };
    }
    /**
     * Loads multiple images and creates a composite fabric.Image
     * @param paths - Array of image paths
     * @param totalWidth - Total width of the composite image
     * @param totalHeight - Total height of the composite image
     * @param callback - Callback function with the composite image
     */
    loadMultiPartImage(paths, totalWidth, totalHeight, callback) {
        const { cols, rows } = this.calculateGridDimensions(paths);
        if (cols === 0 || rows === 0) {
            console.error('Invalid grid dimensions calculated from paths');
            return;
        }
        const partWidth = totalWidth / cols;
        const partHeight = totalHeight / rows;
        // Create canvas for compositing
        const compositeCanvas = new fabric.StaticCanvas(null, {
            width: totalWidth,
            height: totalHeight
        });
        let loadedCount = 0;
        const totalParts = paths.length;
        // Load each image part
        paths.forEach(path => {
            fabric.Image.fromURL(path, (img) => {
                if (!img) {
                    console.error(`Failed to load image: ${path}`);
                    loadedCount++;
                    if (loadedCount === totalParts) {
                        this.finalizeCompositeImage(compositeCanvas, callback);
                    }
                    return;
                }
                const coords = this.parseImageCoordinates(path);
                if (!coords) {
                    console.error(`Could not parse coordinates from path: ${path}`);
                    loadedCount++;
                    if (loadedCount === totalParts) {
                        this.finalizeCompositeImage(compositeCanvas, callback);
                    }
                    return;
                }
                // Position the image part
                const x = coords.col * partWidth;
                const y = coords.row * partHeight;
                img.set({
                    left: x,
                    top: y,
                    scaleX: partWidth / (img.width || 1),
                    scaleY: partHeight / (img.height || 1),
                    selectable: false,
                    evented: false
                });
                compositeCanvas.add(img);
                loadedCount++;
                if (loadedCount === totalParts) {
                    this.finalizeCompositeImage(compositeCanvas, callback);
                }
            }, { crossOrigin: 'anonymous' });
        });
    }
    /**
     * Finalizes the composite image and returns it via callback
     * @param compositeCanvas - Canvas containing all image parts
     * @param callback - Callback function with the final composite image
     */
    finalizeCompositeImage(compositeCanvas, callback) {
        // Render the canvas and create a new fabric.Image from it
        const dataURL = compositeCanvas.toDataURL({
            format: 'png',
            quality: 1.0
        });
        fabric.Image.fromURL(dataURL, (compositeImage) => {
            if (compositeImage) {
                compositeImage.set({
                    selectable: false,
                    evented: false,
                    objectCaching: false
                });
                callback(compositeImage);
            }
        }, { crossOrigin: 'anonymous' });
    }
    /**
     * Loads a single image
     * @param path - Image path
     * @param callback - Callback function with the loaded image
     */
    loadSingleImage(path, callback) {
        fabric.Image.fromURL(path, callback, { crossOrigin: 'anonymous' });
    }
    // ============================================================================
    // VENUE IMAGE RENDERING METHODS
    // ============================================================================
    /**
     * Loads background image for the venue map canvas
     * @param canvas - Fabric canvas instance
     * @param mapConfig - Map configuration
     * @param viewDimensions - View dimensions
     * @param pathImg - Image path (single or array)
     * @param callback - Callback function with mapWidth
     */
    loadBackgroundImage(canvas, mapConfig, viewDimensions, pathImg, callback) {
        if (!canvas)
            return;
        const { path } = mapConfig;
        const targetPath = pathImg ?? path;
        const backgroundImage = canvas.backgroundImage;
        if (backgroundImage && backgroundImage instanceof fabric.Image) {
            canvas.remove(backgroundImage);
        }
        // Check if it's a multi-part image
        if (this.isMultiPartImage(targetPath)) {
            this.loadMultiPartBackgroundImage(canvas, mapConfig, viewDimensions, targetPath, callback);
        }
        else {
            this.loadSingleBackgroundImage(canvas, mapConfig, viewDimensions, targetPath, callback);
        }
    }
    /**
     * Loads a single background image
     */
    loadSingleBackgroundImage(canvas, mapConfig, viewDimensions, path, callback) {
        fabric.Image.fromURL(path, img => {
            if (!img)
                return;
            const result = this.setDataImageToCanvas(canvas, mapConfig, viewDimensions, img);
            callback?.(result.mapWidth);
        }, { crossOrigin: 'anonymous' });
    }
    /**
     * Loads multiple image parts and creates a composite background
     */
    loadMultiPartBackgroundImage(canvas, mapConfig, viewDimensions, paths, callback) {
        const { width, height } = mapConfig;
        this.loadMultiPartImage(paths, width, height, (compositeImage) => {
            if (!compositeImage)
                return;
            const result = this.setDataImageToCanvas(canvas, mapConfig, viewDimensions, compositeImage);
            callback?.(result.mapWidth);
        });
    }
    /**
     * Sets image data to canvas and configures canvas properties
     */
    setDataImageToCanvas(canvas, mapConfig, viewDimensions, img) {
        mapConfig.width = img.width;
        mapConfig.height = img.height;
        viewDimensions.scale = viewDimensions.height / img.height;
        const mapWidth = Math.max(this.scaleValue(img.width, viewDimensions.scale), viewDimensions.width);
        img.scaleToWidth(this.scaleValue(img.width, viewDimensions.scale));
        img.scaleToHeight(this.scaleValue(img.height, viewDimensions.scale));
        img.set({ selectable: false, evented: false, objectCaching: false });
        canvas.setWidth(viewDimensions.width);
        canvas.setHeight(viewDimensions.height);
        canvas.setBackgroundImage(img, () => { });
        return { mapWidth };
    }
    /**
     * Scales a value based on the current scale factor
     */
    scaleValue(value, scale) {
        return value * scale;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapRenderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapRenderService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapRenderService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

/**
 * Pure utility functions for venue map operations that don't require service dependencies.
 * These functions are used across different services for scaling operations.
 */
/**
 * Scales a single value based on the provided scale factor.
 */
function scaleValue(value, scale) {
    return value * scale;
}
/**
 * Scales a point coordinate based on the provided scale factor.
 */
function scalePoint(point, scale) {
    return {
        x: point.x * scale,
        y: point.y * scale,
    };
}
/**
 * Scales an array of points based on the provided scale factor.
 */
function scalePoints(points, scale) {
    return points.map(point => scalePoint(point, scale));
}

class VenueMapMiniMapService {
    // ============================================================================
    // PROPERTIES
    // ============================================================================
    miniMapCanvas;
    miniMapRect;
    // ============================================================================
    // PUBLIC METHODS - MINIMAP INITIALIZATION
    // ============================================================================
    /**
     * Initializes the mini map canvas with the specified element
     */
    initializeMiniMap(mapWidth, viewDimensions, mapConfig) {
        const miniMapScale = mapConfig.minimap.scale;
        const containerHeight = viewDimensions.height;
        this.miniMapCanvas = new fabric.StaticCanvas('miniMap', {
            width: scaleValue(mapWidth, viewDimensions.scale) * miniMapScale,
            height: containerHeight * miniMapScale,
        });
        return this.miniMapCanvas;
    }
    /**
     * Renders the mini map using data from the main canvas
     */
    renderMiniMap(mainCanvas) {
        if (!this.miniMapCanvas || !mainCanvas)
            return;
        const mainCanvasImage = mainCanvas.toDataURL({
            format: 'png',
            quality: 1.0,
        });
        fabric.Image.fromURL(mainCanvasImage, (img) => {
            if (!img)
                return;
            img.scaleToWidth(this.miniMapCanvas.getWidth());
            img.scaleToHeight(this.miniMapCanvas.getHeight());
            this.miniMapCanvas.setBackgroundImage(img, () => { });
        }, { crossOrigin: 'anonymous' });
        this.miniMapCanvas.renderAll();
    }
    /**
     * Displays the viewport rectangle on the mini map
     */
    showViewportRect(viewportTransform, canvasWidth, canvasHeight, currentZoom, viewDimensions, mapConfig, theme) {
        if (this.miniMapRect)
            this.miniMapCanvas?.remove(this.miniMapRect);
        const minimapConfig = mapConfig.minimap;
        const scale = minimapConfig.scale;
        const position = this.findPositionViewport(viewportTransform, currentZoom, canvasWidth, canvasHeight, viewDimensions, scale);
        this.miniMapRect = new fabric.Rect({
            ...position,
            fill: theme.miniMapViewportColor,
            selectable: false,
            opacity: minimapConfig.opacity,
        });
        this.miniMapRect.set('id', 'viewportZoom');
        this.miniMapCanvas?.add(this.miniMapRect);
    }
    /**
     * Finds the position and dimensions of the viewport rectangle on the mini map
     */
    findPositionViewport(viewportTransform, currentZoom, canvasWidth, canvasHeight, viewDimensions, scale = 1) {
        const canvasWidthScaled = (canvasWidth * scale) / currentZoom;
        const canvasHeightScaled = (canvasHeight * scale) / currentZoom;
        const width = (viewDimensions.width * scale) / currentZoom;
        const height = canvasHeightScaled;
        const left = (-(viewportTransform?.[4] ?? 0) * scale) / currentZoom +
            (canvasWidthScaled - width) / 2;
        const top = (-(viewportTransform?.[5] ?? 0) * scale) / currentZoom;
        return { top, left, width, height };
    }
    // ============================================================================
    // UTILITY METHODS
    // ============================================================================
    /**
     * Cleans up resources when service is no longer needed
     */
    cleanup() {
        if (this.miniMapCanvas) {
            this.miniMapCanvas.clear();
            this.miniMapCanvas.dispose();
            this.miniMapCanvas = undefined;
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapMiniMapService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapMiniMapService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapMiniMapService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

// Constants
const LONG_TOUCH_DURATION = 200;
const ZOOM_THRESHOLD = 1.2;
const CLICK_THRESHOLD = 5;
const ZOOM_ANIMATION_DURATION = 500;
const DEBOUNCE_DELAY$1 = 250;
const THROTTLE_DELAY = 100;
class VenueMapCanvasService {
    // ============================================================================
    // INJECTED SERVICES
    // ============================================================================
    renderService = inject(VenueMapRenderService);
    miniMapService = inject(VenueMapMiniMapService);
    // ============================================================================
    // SIGNAL-BASED STATE MANAGEMENT (Angular 20)
    // ============================================================================
    // Canvas state signals
    canvasReady$ = new BehaviorSubject(false);
    // Public signal state
    zoomLevel = signal(1);
    isZooming = signal(false);
    canvasReady = signal(false);
    // Computed signals for getters
    disableZoomIn = computed(() => this.zoomLevel() >= this.mapConfig?.zoom.max);
    disableZoomOut = computed(() => this.zoomLevel() <= this.mapConfig?.zoom.min);
    showMiniMap = computed(() => this.zoomLevel() >= this.mapConfig?.zoom.showMiniMapAtLevel);
    // ============================================================================
    // PRIVATE PROPERTIES
    // ============================================================================
    // Touch and interaction state
    touchState = {
        isTouching: false,
        isDragging: false,
        isPinching: false,
        isZooming: false,
        isMoving: false,
        pausePanning: false,
    };
    touchTimeout;
    animationFrameId = null;
    currentPosition = { x: 0, y: 0 };
    lastPosition = { x: 0, y: 0 };
    lastTouchEventTimestamp = 0;
    zoomStartScale = 0;
    lastScale = 0;
    mapWidth = 0;
    // Event handlers
    debouncedClickHandler;
    debounceClick;
    workerRenderValue;
    // ============================================================================
    // PUBLIC PROPERTIES
    // ============================================================================
    clientPopupPosition;
    miniMapCanvas;
    viewDimensions;
    canvas;
    mapConfig;
    theme;
    constructor() {
        // Effect to sync zoom level with canvas zoom
        effect(() => {
            if (this.canvas) {
                const currentZoom = this.canvas.getZoom();
                if (currentZoom !== this.zoomLevel()) {
                    this.zoomLevel.set(currentZoom);
                }
            }
        });
        // Effect to sync canvas ready state
        effect(() => {
            this.canvasReady$.next(this.canvasReady());
        });
    }
    // ============================================================================
    // PRIVATE HELPER METHODS
    // ============================================================================
    /**
     * Updates the zoom level signal and triggers reactive updates
     */
    updateZoomLevel(zoom) {
        this.zoomLevel.set(zoom);
    }
    // ============================================================================
    // PUBLIC METHODS - INITIALIZATION
    // ============================================================================
    getCanvasReadyObservable() {
        return this.canvasReady$.asObservable();
    }
    initialize(viewDimensions, config, theme) {
        this.viewDimensions = viewDimensions;
        this.mapConfig = config;
        this.theme = theme;
    }
    setupCanvas(canvasElement) {
        this.canvas = new fabric.Canvas(canvasElement, {
            selection: false,
            backgroundColor: this.theme.canvasBackgroundColor,
            renderOnAddRemove: false,
            stateful: false,
        });
        this.hiddenMap();
        this.setupEventListeners();
    }
    hiddenMap() {
        if (!this.canvas)
            return;
        this.canvas.getElement().style.visibility = 'hidden';
        this.canvas.renderOnAddRemove = false;
    }
    // ============================================================================
    // PRIVATE METHODS - EVENT SETUP
    // ============================================================================
    setupEventListeners() {
        this.canvas.on('mouse:wheel', throttle(e => this.handleZoom(e), THROTTLE_DELAY));
        this.canvas.on('touch:gesture', throttle(e => this.handleGesture(e), THROTTLE_DELAY));
        this.canvas.on('selection:created', () => (this.touchState.pausePanning = true));
        this.canvas.on('selection:cleared', () => (this.touchState.pausePanning = false));
        this.canvas.on('touch:drag', e => this.handleDrag(e));
        this.canvas.on('mouse:down', options => this.handleMouseDown(options));
        this.canvas.on('mouse:move', throttle(options => this.handleMouseMove(options), 50));
        this.canvas.on('mouse:up', options => this.handleMouseUp(options));
        const wrapperEl = this.canvas.wrapperEl;
        if (wrapperEl) {
            wrapperEl.addEventListener('touchstart', this.handleTouchStart.bind(this), {
                passive: false,
            });
            wrapperEl.addEventListener('touchmove', this.handleTouchMove.bind(this), {
                passive: false,
            });
            wrapperEl.addEventListener('touchend', this.handleTouchEnd.bind(this), {
                passive: false,
            });
        }
    }
    // ============================================================================
    // PRIVATE METHODS - TOUCH EVENT HANDLERS
    // ============================================================================
    handleTouchStart(e) {
        e.preventDefault();
        this.cancelDebounceClick?.();
        this.touchState.isTouching = true;
        if (e.touches.length >= 2) {
            this.touchState.pausePanning = true;
            this.touchState.isZooming = true;
            this.touchState.isPinching = true;
            this.isZooming.set(true);
            this.touchState.isDragging = false;
            this.zoomStartScale = this.getDistance(e.touches[0], e.touches[1]);
            return;
        }
        this.lastPosition = {
            x: e.touches[0].clientX,
            y: e.touches[0].clientY,
        };
        this.touchTimeout = setTimeout(() => {
            if (this.touchState.isTouching && !this.touchState.isPinching) {
                this.touchState.isDragging = true;
                this.cancelDebounceClick?.();
            }
        }, LONG_TOUCH_DURATION);
    }
    handleTouchMove(e) {
        e.preventDefault();
        if (e.touches.length >= 2) {
            this.handlePinchZoom(e);
            return;
        }
        this.handleSingleTouchMove(e);
    }
    handlePinchZoom(e) {
        const zoomFactor = 0.5;
        const smoothingFactor = 0.5;
        this.cancelDebounceClick?.();
        if (!this.touchState.isPinching) {
            this.touchState.isPinching = true;
            this.zoomStartScale = this.getDistance(e.touches[0], e.touches[1]);
            return;
        }
        this.touchState.isDragging = false;
        const now = Date.now();
        if (now - this.lastTouchEventTimestamp < 50)
            return;
        this.lastTouchEventTimestamp = now;
        const distance = this.getDistance(e.touches[0], e.touches[1]);
        let scale = distance / (this.zoomStartScale || distance);
        scale = 1 + (scale - 1) * zoomFactor;
        const zoomConfig = this.mapConfig.zoom;
        scale = Math.max(zoomConfig.min, Math.min(zoomConfig.max, scale));
        if (this.lastScale) {
            scale = this.lastScale + (scale - this.lastScale) * smoothingFactor;
        }
        this.lastScale = scale;
        const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
        const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
        const rect = this.canvas.wrapperEl.getBoundingClientRect();
        const point = new fabric.Point(centerX - rect.left, centerY - rect.top);
        let zoom = this.canvas.getZoom() * scale;
        zoom = Math.max(zoomConfig.min, Math.min(zoomConfig.max, zoom));
        this.canvas.zoomToPoint(point, zoom);
        this.updateZoomLevel(zoom);
        this.showDisplayViewportMap();
        if (zoom > zoomConfig.min && zoom < zoomConfig.max) {
            this.zoomStartScale = distance;
        }
    }
    handleSingleTouchMove(e) {
        if (!this.touchState.pausePanning && !this.touchState.isPinching) {
            this.cancelDebounceClick?.();
            if (this.touchTimeout)
                clearTimeout(this.touchTimeout);
            const touch = e.touches[0];
            const xChange = touch.clientX - this.lastPosition.x;
            const yChange = touch.clientY - this.lastPosition.y;
            if (Math.abs(xChange) > 5 || Math.abs(yChange) > 5) {
                this.touchState.isDragging = true;
                const delta = new fabric.Point(xChange, yChange);
                this.canvas.relativePan(delta);
                this.lastPosition = { x: touch.clientX, y: touch.clientY };
                this.showDisplayViewportMap();
            }
        }
    }
    handleTouchEnd(e) {
        e.preventDefault();
        this.cancelDebounceClick?.();
        if (this.touchTimeout)
            clearTimeout(this.touchTimeout);
        this.touchState.isTouching = false;
        if (this.touchState.isPinching && e.touches.length < 2) {
            const touch = e.touches[0];
            this.lastPosition = { x: touch.clientX, y: touch.clientY };
        }
        if (!this.touchState.isDragging &&
            !this.touchState.isPinching &&
            !this.touchState.isZooming &&
            e.changedTouches.length === 1) {
            const touch = e.changedTouches[0];
            const target = document.elementFromPoint(touch.clientX, touch.clientY);
            if (target instanceof HTMLElement) {
                const fabricObject = this.canvas.findTarget(e, false);
                this.debounceClick?.(fabricObject, touch);
            }
        }
        this.resetActionEvent();
    }
    resetActionEvent() {
        this.zoomStartScale = 0;
        this.touchState.isMoving = false;
        this.touchState.isDragging = false;
        if (this.touchState.isPinching ||
            this.isZooming() ||
            this.touchState.pausePanning ||
            this.touchState.isDragging) {
            setTimeout(() => {
                this.touchState.pausePanning = false;
                this.isZooming.set(false);
                this.touchState.isPinching = false;
            }, 150);
        }
    }
    // ============================================================================
    // PRIVATE METHODS - MOUSE EVENT HANDLERS
    // ============================================================================
    handleMouseDown(options) {
        this.cancelDebounceClick?.();
        this.touchState.isTouching = true;
        const mouseEvent = options.e;
        this.lastPosition = { x: mouseEvent.clientX, y: mouseEvent.clientY };
        this.touchTimeout = setTimeout(() => {
            if (this.touchState.isTouching) {
                this.touchState.isMoving = true;
                this.cancelDebounceClick?.();
            }
        }, LONG_TOUCH_DURATION);
    }
    handleMouseUp(options) {
        const mouseEvent = options.e;
        this.touchState.isTouching = false;
        if (Math.abs(mouseEvent.movementX) <= CLICK_THRESHOLD &&
            Math.abs(mouseEvent.movementY) <= CLICK_THRESHOLD &&
            !this.touchState.isMoving &&
            !this.touchState.isPinching &&
            !this.touchState.isDragging) {
            const selectedObject = options.target;
            this.cancelDebounceClick?.();
            this.debounceClick?.(selectedObject, options.e);
        }
        this.resetActionEvent();
    }
    handleMouseMove(options) {
        if (!this.touchState.isMoving ||
            this.touchState.isPinching ||
            this.isZooming())
            return;
        const client = this.getClientPosition(options);
        const xChange = client.x - this.lastPosition.x;
        const yChange = client.y - this.lastPosition.y;
        if (Math.abs(xChange) > 5 || Math.abs(yChange) > 5) {
            const delta = new fabric.Point(xChange, yChange);
            this.canvas.relativePan(delta);
            this.lastPosition = client;
            this.showDisplayViewportMap();
        }
    }
    handleZoom(event) {
        this.cancelDebounceClick?.();
        if (this.isZooming())
            return;
        const e = event.e;
        let zoom = this.canvas.getZoom();
        zoom *= 0.999 ** e.deltaY;
        const zoomConfig = this.mapConfig.zoom;
        zoom = Math.max(zoomConfig.min, Math.min(zoomConfig.max, zoom));
        this.canvas.zoomToPoint(new fabric.Point(e.offsetX, e.offsetY), zoom);
        this.updateZoomLevel(zoom);
        this.showDisplayViewportMap();
        e.preventDefault();
        e.stopPropagation();
    }
    handleGesture(event) {
        const e = event.e;
        const zoomFactor = 0.5;
        if (!e.touches || e.touches.length !== 2)
            return;
        this.touchState.pausePanning = true;
        const touch1 = e.touches[0];
        const touch2 = e.touches[1];
        const distance = Math.sqrt(Math.pow(touch2.clientX - touch1.clientX, 2) +
            Math.pow(touch2.clientY - touch1.clientY, 2));
        if (!this.zoomStartScale) {
            this.zoomStartScale = distance;
        }
        else {
            const centerX = (touch1.clientX + touch2.clientX) / 2;
            const centerY = (touch1.clientY + touch2.clientY) / 2;
            const point = new fabric.Point(centerX, centerY);
            let scale = distance / this.zoomStartScale;
            scale = 1 + (scale - 1) * zoomFactor;
            this.zoomStartScale = distance;
            let zoom = this.canvas.getZoom() * scale;
            const zoomConfig = this.mapConfig.zoom;
            zoom = Math.max(zoomConfig.min, Math.min(zoomConfig.max, zoom));
            this.canvas.zoomToPoint(point, zoom);
        }
        e.preventDefault();
        e.stopPropagation();
    }
    handleDrag(event) {
        const e = event.e;
        if (this.touchState.pausePanning ||
            !e.touches ||
            e.touches.length === 0 ||
            this.isZooming())
            return;
        e.preventDefault();
        const touch = e.touches[0];
        this.currentPosition = { x: touch.clientX, y: touch.clientY };
        const xChange = this.currentPosition.x - this.lastPosition.x;
        const yChange = this.currentPosition.y - this.lastPosition.y;
        if (Math.abs(xChange) <= 50 && Math.abs(yChange) <= 50) {
            const delta = new fabric.Point(xChange, yChange);
            this.canvas.relativePan(delta);
        }
        this.lastPosition = this.currentPosition;
    }
    // ============================================================================
    // PUBLIC METHODS - IMAGE MANAGEMENT
    // ============================================================================
    loadBackgroundImage(pathImg, callback) {
        if (!this.canvas)
            return;
        this.renderService.loadBackgroundImage(this.canvas, this.mapConfig, this.viewDimensions, pathImg, (mapWidth) => {
            this.mapWidth = mapWidth;
            this.renderFrame();
            this.canvasReady.set(true);
            callback?.();
        });
    }
    // ============================================================================
    // PUBLIC METHODS - RENDERING
    // ============================================================================
    renderFrame() {
        this.workerRenderValue?.() ?? this.canvas?.renderAll();
        this.animationFrameId = requestAnimationFrame(() => this.renderFrame());
    }
    stopRenderFrame() {
        if (this.animationFrameId !== null) {
            cancelAnimationFrame(this.animationFrameId);
            this.animationFrameId = null;
        }
    }
    showMap() {
        this.canvas.getElement().style.visibility = 'visible';
        this.canvas.renderOnAddRemove = true;
    }
    // ============================================================================
    // PUBLIC METHODS - MINIMAP
    // ============================================================================
    initializeMiniMap() {
        if (!this.canvas)
            return;
        this.miniMapCanvas = this.miniMapService.initializeMiniMap(this.mapWidth, {
            width: this.viewDimensions.width,
            height: this.viewDimensions.height,
            scale: this.viewDimensions.scale || 1
        }, this.mapConfig);
        this.miniMapService.renderMiniMap(this.canvas);
        this.showDisplayViewportMap();
    }
    showDisplayViewportMap() {
        if (!this.canvas || !this.miniMapCanvas)
            return;
        this.miniMapService.showViewportRect(this.canvas.viewportTransform, this.canvas.getWidth(), this.canvas.getHeight(), this.canvas.getZoom(), {
            width: this.viewDimensions.width,
            height: this.viewDimensions.height,
            scale: this.viewDimensions.scale || 1
        }, this.mapConfig, this.theme);
    }
    // ============================================================================
    // PUBLIC METHODS - ZOOM & NAVIGATION
    // ============================================================================
    zoomAnimated(point, zoomLevel, duration = ZOOM_ANIMATION_DURATION, callback) {
        if (this.isZooming() || !this.canvas)
            return;
        this.isZooming.set(true);
        const canvas = this.canvas;
        const startZoom = canvas.getZoom();
        const endZoom = zoomLevel;
        const startPoint = canvas.getVpCenter();
        const endPoint = new fabric.Point(point.x, point.y);
        const vpw = canvas.getWidth();
        const vph = canvas.getHeight();
        const throttledUpdate = throttle((value) => {
            const zoom = startZoom + (endZoom - startZoom) * value;
            canvas.setZoom(zoom);
            this.updateZoomLevel(zoom);
            const newX = startPoint.x + (endPoint.x - startPoint.x) * value;
            const newY = startPoint.y + (endPoint.y - startPoint.y) * value;
            const currentZoom = zoom;
            const viewportWidth = vpw / currentZoom;
            const viewportHeight = vph / currentZoom;
            const x = newX - viewportWidth / 2;
            const y = newY - viewportHeight / 2;
            canvas.setViewportTransform([zoom, 0, 0, zoom, -x * zoom, -y * zoom]);
            this.showDisplayViewportMap();
        }, 16);
        fabric.util.animate({
            startValue: 0,
            endValue: 1,
            duration: duration,
            onChange: value => {
                throttledUpdate(value);
            },
            onComplete: () => {
                this.isZooming.set(false);
                callback?.();
            },
        });
    }
    adjustViewToCenter(callback, widthPercentage = 0.9) {
        const { width, height } = this.viewDimensions;
        const centerPoint = this.centerPointCanvas();
        const zoomLevel = width < height
            ? width / this.mapWidth
            : height / this.scaleValue(this.mapConfig.height ?? 0);
        this.zoomAnimated(centerPoint, zoomLevel * widthPercentage);
        setTimeout(() => {
            callback?.();
        }, 550);
    }
    resetZoomCanvas() {
        this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
        this.canvas.setZoom(1);
        this.updateZoomLevel(1);
        this.showDisplayViewportMap();
    }
    handleZoomBtn(type) {
        if (!this.canvas)
            return;
        const currentZoom = this.canvas.getZoom();
        const viewportTransform = this.canvas.viewportTransform || [1, 0, 0, 1, 0, 0];
        // Calculate the center of the current viewport
        const canvasWidth = this.canvas.getWidth();
        const canvasHeight = this.canvas.getHeight();
        const viewportCenterX = -viewportTransform[4] / currentZoom + canvasWidth / (2 * currentZoom);
        const viewportCenterY = -viewportTransform[5] / currentZoom + canvasHeight / (2 * currentZoom);
        const centerPoint = new fabric.Point(viewportCenterX, viewportCenterY);
        const zoomStepValue = this.mapConfig.zoom.step;
        const zoomStep = zoomStepValue * currentZoom;
        const targetZoom = type === 'zoomIn' ? currentZoom + zoomStep : currentZoom - zoomStep;
        this.zoomAnimated(centerPoint, targetZoom);
    }
    zoomAndPanToObject(object, zoom = ZOOM_THRESHOLD, callback) {
        const targetObject = object;
        const objectCenter = targetObject.getCenterPoint();
        this.zoomAnimated(objectCenter, zoom, ZOOM_ANIMATION_DURATION, callback);
    }
    zoomToObjectBufferHeight(object, zoom = ZOOM_THRESHOLD, heightPrice = 0, callback) {
        const targetObject = object;
        const objectCenter = targetObject.getCenterPoint();
        const heightBuffer = this.viewDimensions.height * 0.3;
        objectCenter.setY(objectCenter.y + (heightBuffer + heightPrice) / 2);
        this.zoomAnimated(objectCenter, zoom, ZOOM_ANIMATION_DURATION, callback);
    }
    // ============================================================================
    // PUBLIC METHODS - CANVAS MANAGEMENT
    // ============================================================================
    resetHeightCanvas() {
        this.canvas.setHeight(this.viewDimensions.height);
    }
    setHeightCanvas(heightBuffer = 0) {
        this.canvas.setHeight(this.viewDimensions.height + heightBuffer);
    }
    centerPointCanvas() {
        const { width, height } = this.mapConfig;
        return new fabric.Point(this.scaleValue(width / 2), this.scaleValue(height / 2));
    }
    removeGroupCanvas() {
        const objects = this.canvas.getObjects();
        const miniMapObjects = this.miniMapCanvas?.getObjects() ?? [];
        for (let i = objects.length - 1; i >= 0; i--) {
            this.canvas.remove(objects[i]);
        }
        for (let i = miniMapObjects.length - 1; i >= 0; i--) {
            this.miniMapCanvas.remove(miniMapObjects[i]);
        }
    }
    createObjectFromPoint(point) {
        const selectedObject = new fabric.Object({
            left: point.x,
            top: point.y,
            width: 1,
            height: 1,
        });
        return selectedObject;
    }
    findObjectByGrade(id, shapeIndex) {
        if (!id || typeof shapeIndex !== 'number')
            return;
        const selectedObject = this.canvas
            .getObjects()
            .find((obj) => obj.id === id && obj.shapeIndex === shapeIndex);
        return selectedObject;
    }
    removeObjectCaching() {
        this.canvas?.getObjects().forEach(obj => {
            obj.set('objectCaching', false);
        });
    }
    // ============================================================================
    // PUBLIC METHODS - EVENT HANDLERS
    // ============================================================================
    setDebounceClickHandler(handler) {
        this.debouncedClickHandler = debounce(handler, DEBOUNCE_DELAY$1);
        this.debounceClick = this.debouncedClickHandler;
    }
    setWorkerRenderValue(handler) {
        this.workerRenderValue = handler;
    }
    cancelDebounceClick() {
        this.debouncedClickHandler?.cancel();
    }
    // ============================================================================
    // PUBLIC METHODS - UTILITY
    // ============================================================================
    scaleValue(value) {
        return scaleValue(value, this.viewDimensions.scale);
    }
    scalePoint(point) {
        return scalePoint(point, this.viewDimensions.scale);
    }
    handleScalePoint(points) {
        return scalePoints(points, this.viewDimensions.scale);
    }
    scalePointGrade(point, mapConfigGrade) {
        const scaleMap = this.mapConfig.width / mapConfigGrade.width;
        return {
            x: point.x * scaleMap * this.viewDimensions.scale,
            y: point.y * scaleMap * this.viewDimensions.scale,
        };
    }
    calculateFramesDimensions(points) {
        if (!points || points.length < 3) {
            return {
                width: 1,
                height: 1,
                left: 1,
                top: 1,
                right: 1,
                bottom: 1,
                centerX: 1,
                centerY: 1,
            };
        }
        const minX = Math.min(...points.map(point => point.x));
        const maxX = Math.max(...points.map(point => point.x));
        const minY = Math.min(...points.map(point => point.y));
        const maxY = Math.max(...points.map(point => point.y));
        const width = maxX - minX;
        const height = maxY - minY;
        const centerX = minX + width / 2;
        const centerY = minY + height / 2;
        return {
            width,
            height,
            left: minX,
            top: minY,
            right: maxX,
            bottom: maxY,
            centerX,
            centerY,
        };
    }
    getVisibleBounds(buffer = 0) {
        if (!this.canvas) {
            return { left: 0, top: 0, right: 0, bottom: 0, zoom: 1 };
        }
        const zoom = this.canvas.getZoom();
        const vpt = this.canvas.viewportTransform;
        return {
            left: -(vpt?.[4] ?? 0) / zoom - buffer,
            top: -(vpt?.[5] ?? 0) / zoom - buffer,
            right: (this.canvas.width ?? 0) / zoom - (vpt?.[4] ?? 0) / zoom + buffer,
            bottom: (this.canvas.height ?? 0) / zoom - (vpt?.[5] ?? 0) / zoom + buffer,
            zoom,
        };
    }
    isInBounds(seatPoint, bounds) {
        return (seatPoint.left >= bounds.left &&
            seatPoint.left <= bounds.right &&
            seatPoint.top >= bounds.top &&
            seatPoint.top <= bounds.bottom);
    }
    getClientPosition(options) {
        let clientX, clientY;
        if (options.e.type.startsWith('touch')) {
            const touch = options.e.touches[0] ||
                options.e.changedTouches[0];
            clientX = touch.clientX;
            clientY = touch.clientY;
        }
        else {
            const mouseEvent = options.e;
            clientX = mouseEvent.clientX;
            clientY = mouseEvent.clientY;
        }
        return { x: clientX, y: clientY };
    }
    getDistance(touch1, touch2) {
        return Math.sqrt(Math.pow(touch2.clientX - touch1.clientX, 2) +
            Math.pow(touch2.clientY - touch1.clientY, 2));
    }
    // ============================================================================
    // CLEANUP METHODS
    // ============================================================================
    removeEventListeners() {
        if (!this.canvas)
            return;
        this.canvas.off('mouse:wheel');
        this.canvas.off('touch:gesture');
        this.canvas.off('selection:created');
        this.canvas.off('selection:cleared');
        this.canvas.off('touch:drag');
        this.canvas.off('mouse:down');
        this.canvas.off('mouse:move');
        this.canvas.off('mouse:up');
        if (this.canvas.wrapperEl) {
            this.canvas.wrapperEl.removeEventListener('touchstart', this.handleTouchStart);
            this.canvas.wrapperEl.removeEventListener('touchmove', this.handleTouchMove);
            this.canvas.wrapperEl.removeEventListener('touchend', this.handleTouchEnd);
        }
    }
    removeCanvasMap() {
        if (this.canvas) {
            this.canvas.clear();
            this.canvas.dispose();
            this.canvas = undefined;
        }
        if (this.miniMapCanvas) {
            this.miniMapCanvas.clear();
            this.miniMapCanvas.dispose();
            this.miniMapCanvas = undefined;
        }
        this.stopRenderFrame();
    }
    clearMapService() {
        this.removeEventListeners();
        this.removeCanvasMap();
        this.stopRenderFrame();
        this.miniMapService.cleanup();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapCanvasService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapCanvasService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapCanvasService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: () => [] });

/**
 * Service responsible for creating and managing price displays on venue maps.
 * Handles the creation of sticky price elements and price-related rendering logic.
 */
class VenueMapPriceService {
    /**
     * Finds the circle element within a grade shape.
     */
    findCircleInGradeShape(gradeShape) {
        return gradeShape
            .getObjects()
            .find(obj => obj.type === 'group' && obj.subType === 'circle');
    }
    /**
     * Calculates the height offset for circle elements.
     */
    calculateCircleHeight(subId, gradeShape, mapType, circle) {
        return subId === gradeShape?.subId && mapType !== VenueMapType.GRADE
            ? (circle?.getScaledHeight() ?? 0) / 2
            : 0;
    }
    /**
     * Finds the text bubble group within a shape.
     */
    findTextBubbleGroup(shape) {
        return shape.getObjects().find(obj => obj.type === 'group');
    }
    /**
     * Calculates the position for sticky price elements.
     */
    calculateStickyPosition(circle, circleHeight = 0, textBubbleGroup) {
        return {
            left: circle?.left,
            top: (circle?.top ?? 0) -
                circleHeight -
                (textBubbleGroup?.getScaledHeight() ?? 0),
        };
    }
    /**
     * Updates the content of text bubble elements.
     */
    updateTextBubbleContent(grade, shape) {
        shape.getObjects().forEach(obj => {
            const objGroup = obj;
            if (objGroup.subType === 'textBubbleGroup') {
                this.updateTextContent(objGroup, grade);
            }
        });
    }
    /**
     * Updates the text content within a text bubble group.
     */
    updateTextContent(textBubbleGroup, grade) {
        textBubbleGroup.getObjects().forEach(textBubble => {
            if (textBubble.type === 'text') {
                textBubble.set('text', Number(grade.price).toLocaleString());
            }
        });
    }
    /**
     * Creates a sticky price element for grade display.
     */
    createStickyPrice(grade, gradeSetting, mapInfo, scale, canvas, theme) {
        const { fontSize, bubble, corner, arrow } = mapInfo;
        const { circle } = gradeSetting;
        const leftCircle = scaleValue(circle.x, scale);
        const topCircle = scaleValue(circle.y, scale);
        const price = Number(grade.price);
        const priceText = new fabric.Text(price.toLocaleString(), {
            fontSize: scaleValue(fontSize, scale),
            fontFamily: 'Arial',
            fontWeight: 'bold',
            fill: theme.priceTextColor,
            textAlign: 'center',
            originX: 'center',
            originY: 'center',
        });
        priceText.set('type', 'text');
        canvas.add(priceText);
        priceText.setCoords();
        const bubbleRect = new fabric.Rect({
            width: priceText.getScaledWidth() +
                scaleValue(bubble.width, scale),
            height: priceText.getScaledHeight() +
                scaleValue(bubble.height, scale),
            fill: grade.disabled ? undefined : theme.priceBackgroundColor,
            stroke: theme.priceBubbleStrokeColor,
            rx: scaleValue(corner.width, scale),
            ry: scaleValue(corner.width, scale),
            originX: 'center',
            originY: 'center',
        });
        canvas.remove(priceText);
        const arrowRect = new fabric.Triangle({
            width: scaleValue(arrow.width, scale),
            height: scaleValue(arrow.height, scale),
            fill: grade.disabled ? undefined : theme.priceBackgroundColor,
            stroke: theme.priceBubbleStrokeColor,
            originX: 'center',
            originY: 'top',
            left: bubbleRect.left,
            top: (bubbleRect.top ?? 0) +
                (bubbleRect.height ?? 0) / 2 +
                scaleValue(arrow.padding, scale),
            angle: 180,
        });
        const textBubbleGroup = new fabric.Group([bubbleRect, priceText], {
            originX: 'center',
            originY: 'center',
        });
        textBubbleGroup.set('subType', 'textBubbleGroup');
        const positionStick = {
            left: leftCircle,
            top: topCircle - textBubbleGroup.getScaledHeight(),
        };
        const stickyPrice = new fabric.Group([textBubbleGroup, arrowRect], {
            ...positionStick,
            originX: 'center',
            originY: 'top',
            opacity: 0,
        });
        const shadowOptions = {
            color: theme.shadowColor,
            blur: 2,
            offsetX: 0.5,
            offsetY: 0.5,
        };
        stickyPrice.set('shadow', new fabric.Shadow(shadowOptions));
        stickyPrice.set('subType', 'stickyPrice');
        return stickyPrice;
    }
    /**
     * Updates the position of sticky price elements.
     */
    updateStickyPricePosition(grade, shape, gradeShape, subId, mapType) {
        const circle = this.findCircleInGradeShape(gradeShape);
        const circleHeight = this.calculateCircleHeight(subId, gradeShape, mapType, circle);
        const textBubbleGroup = this.findTextBubbleGroup(shape);
        const position = this.calculateStickyPosition(circle, circleHeight, textBubbleGroup);
        this.updateTextBubbleContent(grade, shape);
        shape.set('top', position.top);
    }
    /**
     * Renders sticky price elements with proper visibility control.
     */
    renderStickyPrice(grade, shape, isVisible, gradeShape, subId, mapType) {
        if (shape.subType === 'stickyPrice') {
            // Only render (make visible) when show price mode is on
            shape.set('opacity', isVisible && mapType === VenueMapType.GRADE_PRICE ? 1 : 0);
            if (isVisible && mapType === VenueMapType.GRADE_PRICE) {
                this.updateStickyPricePosition(grade, shape, gradeShape, subId, mapType);
            }
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapPriceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapPriceService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapPriceService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

/**
 * Service responsible for creating and managing venue grade visualizations.
 * Handles the creation of polygons, circles, and price sticky elements for grades.
 */
class VenueMapGradeService {
    gradeIconMultiplier = MULTIPLIER_GRADE_ICON;
    canvasService = inject(VenueMapCanvasService);
    priceService = inject(VenueMapPriceService);
    /**
     * Creates a complete grade group containing polygon, circle, and sticky price elements.
     */
    createGrade(polygon, circle, stickyPrice, gradeInfo) {
        const gradeGroup = new fabric.Group([polygon, circle, stickyPrice], {
            originX: 'center',
            originY: 'center',
            hoverCursor: 'pointer',
            selectable: false,
            hasBorders: false,
            perPixelTargetFind: true,
            evented: !gradeInfo['disabled'],
        });
        gradeGroup.set({
            id: gradeInfo['id'].toString(),
            subId: v4(),
            shapeIndex: Number(gradeInfo['shapeIndex']),
        });
        return gradeGroup;
    }
    /**
     * Creates and configures sticky elements for grade display based on map type.
     */
    createGradeSticky(grade, gradeShape, subId, mapType) {
        if (!grade)
            return;
        gradeShape.getObjects().forEach(child => {
            if (child.type !== 'group')
                return;
            const targetChild = child;
            this.renderStickyType(grade, targetChild, gradeShape, subId, mapType);
            if (subId === gradeShape.subId && mapType !== VenueMapType.GRADE) {
                this.renderShape(grade, targetChild, 'circle', true, gradeShape, subId, mapType);
                this.renderShape(grade, targetChild, 'stickyPrice', true, gradeShape, subId, mapType);
            }
        });
    }
    /**
     * Renders sticky elements based on map type.
     */
    renderStickyType(grade, shape, gradeShape, subId, mapType) {
        if (mapType === VenueMapType.GRADE) {
            this.renderShape(grade, shape, 'circle', true, gradeShape, subId, mapType);
            this.renderShape(grade, shape, 'stickyPrice', false, gradeShape, subId, mapType);
            return;
        }
        this.renderShape(grade, shape, 'circle', false, gradeShape, subId, mapType);
        this.renderShape(grade, shape, 'stickyPrice', true, gradeShape, subId, mapType);
    }
    /**
     * Renders individual shape elements with visibility control.
     */
    renderShape(grade, shape, type, isVisible, gradeShape, subId, mapType) {
        if (type === shape.subType) {
            if (shape.subType === 'stickyPrice') {
                // Delegate price rendering to price service
                this.priceService.renderStickyPrice(grade, shape, isVisible, gradeShape, subId, mapType);
            }
            else {
                // Handle other shape types (like circles)
                shape.set('opacity', isVisible ? 1 : 0);
            }
        }
    }
    /**
     * Creates a sticky price element for grade display.
     */
    createStickyPrice(grade, gradeSetting, mapInfo, theme) {
        return this.priceService.createStickyPrice(grade, gradeSetting, mapInfo, this.canvasService.viewDimensions.scale, this.canvasService.canvas, theme);
    }
    /**
     * Creates a polygon shape for the grade area.
     */
    createPolygon(gradeSetting, disabled = false) {
        const { points, color } = gradeSetting;
        const scaledPoints = scalePoints(points ?? [], this.canvasService.viewDimensions.scale);
        this.renderGrade(points, color, disabled);
        return new fabric.Polygon(scaledPoints, {
            fill: color,
            stroke: undefined,
            strokeWidth: 0,
            opacity: 0.01,
            selectable: false,
            evented: !disabled,
            hoverCursor: 'pointer',
            originX: 'center',
            originY: 'center',
        });
    }
    /**
     * Renders a polygon with image color instead of rendering the actual image.
     */
    renderGrade(points, color, disabled = false) {
        if (!points?.length)
            return;
        const imagePoints = scalePoints(points, this.canvasService.viewDimensions.scale);
        const polygon = new fabric.Polygon(imagePoints, {
            fill: color,
            stroke: undefined,
            strokeWidth: 0,
            opacity: disabled ? 0.5 : 1,
            selectable: false,
            evented: false,
            originX: 'center',
            originY: 'center',
            globalCompositeOperation: disabled ? 'soft-light' : 'hard-light',
        });
        this.canvasService.canvas.add(polygon);
        this.canvasService.canvas.sendToBack(polygon);
    }
    /**
     * Creates a circle element for grade display.
     */
    createCircle(grade, gradeSetting, mapInfo, theme) {
        const resolvedTheme = theme;
        const { color, circle } = gradeSetting;
        const { circle: mapCircle, fontSize } = mapInfo;
        const circleShape = new fabric.Rect({
            fill: grade.disabled ? undefined : color,
            stroke: resolvedTheme.gradeStrokeColor,
            strokeWidth: this.canvasService.scaleValue(mapCircle.stroke),
            originX: 'center',
            originY: 'center',
            height: this.canvasService.scaleValue(mapCircle.height * this.gradeIconMultiplier),
            width: this.canvasService.scaleValue(mapCircle.width * this.gradeIconMultiplier),
            rx: this.canvasService.scaleValue(mapCircle.height),
            ry: this.canvasService.scaleValue(mapCircle.height),
        });
        const text = new fabric.Text(grade.abbreviation.toString(), {
            fontSize: this.canvasService.scaleValue(fontSize) * this.gradeIconMultiplier,
            fontFamily: 'Roboto',
            fontWeight: 700,
            top: 0,
            fill: circle.textColor,
            textAlign: 'center',
            originX: 'center',
            originY: 'center',
        });
        const leftCircle = this.canvasService.scaleValue(circle.x);
        const topCircle = this.canvasService.scaleValue(circle.y);
        const groupCircle = new fabric.Group(text ? [circleShape, text] : [circleShape], {
            left: leftCircle,
            top: topCircle,
            originX: 'center',
            originY: 'center',
            selectable: false,
            hoverCursor: 'pointer',
        });
        groupCircle.set('subType', 'circle');
        return groupCircle;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapGradeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapGradeService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapGradeService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

const DEBOUNCE_DELAY = 250;
const MAP_FOCUS_DELAY = 1500;
class VenueMapComponent {
    grades = input([]);
    reloadMap = input(true);
    focusedGradePart = input();
    mapConfiguration = input();
    theme = input();
    // Signal Outputs (Angular 20 feature)
    selectGrade = output();
    // Template References
    canvasRef;
    mapContainer;
    // Public Properties
    VenueMapType = VenueMapType;
    // Signal-based State Management
    gradeService = inject(VenueMapGradeService);
    canvasService = inject(VenueMapCanvasService);
    // Computed Signals
    resolvedMapConfiguration = computed(() => {
        const config = this.mapConfiguration();
        const defaultMapConfiguration = cloneDeep(DEFAULT_MAP_CONFIG);
        return merge(defaultMapConfiguration, config ?? {});
    });
    resolvedTheme = computed(() => {
        const themeConfig = this.theme();
        return merge(cloneDeep(DEFAULT_VENUE_MAP_THEME), themeConfig ?? {});
    });
    viewDimensions = computed(() => {
        const mapHeight = window.innerHeight;
        const config = this.resolvedMapConfiguration();
        return {
            width: this.mapWidth(),
            height: mapHeight,
            scale: mapHeight / config.height,
        };
    });
    // Signal State
    venueMapType = signal(VenueMapType.GRADE);
    isLoading = signal(false);
    mapWidth = signal(0);
    gradeSelectCanvas = signal(undefined);
    gradeSelect = signal(undefined);
    // Private Properties
    pendingMapWidth = 0;
    isRendering = false;
    // Debounced Methods
    debounceGradeMapShow = debounce(() => this.renderGradeSticky(), DEBOUNCE_DELAY);
    debounceUpdateMapWidth = debounce(() => this.updateMapWidth(), DEBOUNCE_DELAY);
    constructor() {
        // Effect for grades changes
        effect(() => {
            const gradesValue = this.grades();
            const mapWidthValue = this.mapWidth();
            if (gradesValue.length > 0 && mapWidthValue > 0) {
                this.debounceUpdateMapWidth();
            }
        });
        // Effect for map configuration changes
        effect(() => {
            this.mapConfiguration(); // Track changes
            this.debounceUpdateMapWidth();
        });
        // Effect for theme changes
        effect(() => {
            this.theme(); // Track changes
            this.debounceUpdateMapWidth();
        });
    }
    // ============================================================================
    // LIFECYCLE METHODS
    // ============================================================================
    ngOnInit() {
        this.initializeCanvasHandlers();
    }
    ngOnDestroy() {
        this.cleanupResources();
    }
    ngAfterViewInit() {
        this.debounceUpdateMapWidth();
    }
    // ============================================================================
    // PRIVATE INITIALIZATION METHODS
    // ============================================================================
    /**
     * Initializes canvas event handlers and observables.
     */
    initializeCanvasHandlers() {
        this.canvasService.getCanvasReadyObservable().subscribe();
        this.canvasService.setDebounceClickHandler(this.onGradeClick.bind(this));
    }
    /**
     * Cleans up resources when component is destroyed.
     */
    cleanupResources() {
        this.canvasService.clearMapService();
        this.debounceGradeMapShow.cancel();
        this.debounceUpdateMapWidth.cancel();
    }
    // ============================================================================
    // MAP RENDERING AND MANAGEMENT METHODS
    // ============================================================================
    /**
     * Updates the map width and triggers re-rendering if needed.
     */
    updateMapWidth() {
        if (!this.mapContainer?.nativeElement)
            return;
        const newMapWidth = this.mapContainer.nativeElement.offsetWidth;
        if (!newMapWidth)
            return;
        if (this.isRendering) {
            this.pendingMapWidth = newMapWidth;
            return;
        }
        this.startRenderProcess(newMapWidth);
    }
    /**
     * Starts the rendering process with the new map width.
     */
    startRenderProcess(mapWidth) {
        this.isRendering = true;
        this.mapWidth.set(mapWidth);
        this.processRenderQueue();
    }
    /**
     * Processes the render queue and handles pending width changes.
     */
    async processRenderQueue() {
        await this.handleMapSettingsChange();
        if (this.pendingMapWidth) {
            const nextMapWidth = this.pendingMapWidth;
            this.pendingMapWidth = 0;
            this.mapWidth.set(nextMapWidth);
            return this.processRenderQueue();
        }
        this.isRendering = false;
    }
    /**
     * Handles changes to map settings and triggers re-initialization.
     */
    async handleMapSettingsChange() {
        if (this.reloadMap()) {
            this.canvasService.removeCanvasMap();
        }
        this.initializeMapData();
        this.canvasService.initialize(this.viewDimensions(), this.resolvedMapConfiguration(), this.resolvedTheme());
        this.setupCanvas();
        await this.resetAndRenderGrades();
    }
    /**
     * Initializes map data and view dimensions.
     */
    initializeMapData() {
        // Data is now handled by computed signals
        // resolvedMapConfiguration and resolvedTheme are computed automatically
    }
    /**
     * Sets up the canvas and loads background image.
     */
    setupCanvas() {
        if (this.reloadMap() || !this.canvasService.canvas) {
            this.canvasService.setupCanvas(this.canvasRef.nativeElement);
        }
        const config = this.resolvedMapConfiguration();
        this.canvasService.loadBackgroundImage(config.path);
    }
    /**
     * Resets canvas state and renders all grades.
     */
    async resetAndRenderGrades() {
        this.isLoading.set(true);
        this.resetCanvasState();
        this.renderGrades();
        this.renderGradeSticky();
        await this.showCenterFocusMap();
        this.canvasService.showMap();
        this.isLoading.set(false);
    }
    /**
     * Resets the canvas state and clears selections.
     */
    resetCanvasState() {
        this.canvasService.resetZoomCanvas();
        this.canvasService.resetHeightCanvas();
        this.canvasService.removeGroupCanvas();
        this.clearGradeSelection();
    }
    /**
     * Clears the current grade selection.
     */
    clearGradeSelection() {
        this.gradeSelectCanvas.set(undefined);
        this.gradeSelect.set(undefined);
    }
    /**
     * Renders all grades on the canvas.
     */
    renderGrades() {
        const grades = this.grades();
        grades.forEach(grade => grade.settings.forEach((shape, index) => {
            const gradeGroup = this.createGradeGroup(grade, shape, index);
            this.canvasService.canvas.add(gradeGroup);
        }));
    }
    /**
     * Creates a grade group with all its components.
     */
    createGradeGroup(grade, shape, index) {
        const gradeInfo = this.buildGradeInfo(grade, index);
        return this.gradeService.createGrade(this.gradeService.createPolygon(shape, grade.disabled), this.gradeService.createCircle(grade, shape, this.resolvedMapConfiguration(), this.resolvedTheme()), this.gradeService.createStickyPrice(grade, shape, this.resolvedMapConfiguration(), this.resolvedTheme()), gradeInfo);
    }
    /**
     * Builds grade information object for grade creation.
     */
    buildGradeInfo(grade, index) {
        return {
            id: grade.id,
            subId: v4(),
            shapeIndex: index.toString(),
            disabled: !!grade.disabled,
        };
    }
    /**
     * Renders sticky elements for all grades based on current map type.
     */
    renderGradeSticky() {
        this.canvasService.canvas.getObjects().forEach(gradeShape => {
            const gradeShapeTyped = gradeShape;
            const gradeId = gradeShapeTyped.id;
            if (gradeId) {
                const grade = this.findGradeById(gradeId);
                if (grade) {
                    this.gradeService.createGradeSticky(grade, gradeShapeTyped, this.gradeSelectCanvas()?.subId ?? '', this.venueMapType());
                }
            }
        });
    }
    /**
     * Finds a grade by its ID.
     */
    findGradeById(gradeId) {
        const grades = this.grades();
        return grades.find(grade => grade.id === gradeId);
    }
    // ============================================================================
    // MAP FOCUS AND INTERACTION METHODS
    // ============================================================================
    /**
     * Shows the center focus map with animation delay.
     */
    async showCenterFocusMap() {
        return new Promise(resolve => {
            setTimeout(() => {
                this.renderMapFocus(resolve);
            }, MAP_FOCUS_DELAY);
        });
    }
    /**
     * Renders the map focus and initializes mini map.
     */
    renderMapFocus(resolve) {
        this.canvasService.initializeMiniMap();
        this.canvasService.adjustViewToCenter(() => {
            resolve();
        });
    }
    /**
     * Handles grade click events.
     */
    onGradeClick(selectedObject) {
        if (selectedObject?.type === 'group') {
            this.handleGradeSelection(selectedObject);
        }
    }
    /**
     * Handles the selection of a grade.
     */
    handleGradeSelection(selectedGrade) {
        if (this.isGradeAlreadySelected(selectedGrade))
            return;
        this.selectGradeInternal(selectedGrade);
        this.updateGradeDisplay();
    }
    /**
     * Updates the grade display after selection.
     */
    updateGradeDisplay() {
        this.renderGradeSticky();
        this.canvasService.showDisplayViewportMap();
    }
    /**
     * Checks if the grade is already selected.
     */
    isGradeAlreadySelected(selectedGrade) {
        const currentSelection = this.gradeSelectCanvas();
        return selectedGrade.subId === currentSelection?.subId;
    }
    /**
     * Selects a grade and updates the UI state.
     */
    selectGradeInternal(selectedGrade) {
        this.canvasService.zoomToObjectBufferHeight(selectedGrade);
        const grade = this.findGradeById(selectedGrade.id ?? '');
        if (grade) {
            this.gradeSelect.set(cloneDeep(grade));
            this.gradeSelectCanvas.set(selectedGrade);
        }
    }
    // ============================================================================
    // PUBLIC METHODS
    // ============================================================================
    /**
     * Handles changes to grade map display.
     */
    onChangeGradeMapShow() {
        this.cancelDebounceGradeMapShow();
        this.triggerDebounceGradeMapShow();
    }
    /**
     * Handles grade selection from external components.
     */
    onGradeSelected(selectedObject) {
        const grade = this.findGradeById(selectedObject.id ?? '');
        if (!grade)
            return;
        const shapeIndex = selectedObject.shapeIndex;
        this.selectGrade.emit({ grade, shapeIndex });
    }
    /**
     * Handles seat grade selection.
     */
    onSelectSeatGrade() {
        const currentGrade = this.gradeSelect();
        const currentCanvas = this.gradeSelectCanvas();
        if (!currentGrade || !currentCanvas)
            return;
        const shapeIndex = currentCanvas.shapeIndex;
        this.selectGrade.emit({ grade: currentGrade, shapeIndex });
    }
    /**
     * Handles changes to grade map type (grade view vs price view).
     */
    onChangeGradeMapType(isPrice) {
        this.venueMapType.set(isPrice
            ? VenueMapType.GRADE_PRICE
            : VenueMapType.GRADE);
        this.onChangeGradeMapShow();
    }
    // ============================================================================
    // PRIVATE HELPER METHODS
    // ============================================================================
    /**
     * Cancels the debounced grade map show operation.
     */
    cancelDebounceGradeMapShow() {
        this.debounceGradeMapShow.cancel();
    }
    /**
     * Triggers the debounced grade map show operation.
     */
    triggerDebounceGradeMapShow() {
        this.debounceGradeMapShow();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.5", type: VenueMapComponent, isStandalone: true, selector: "ngx-venue-map", inputs: { grades: { classPropertyName: "grades", publicName: "grades", isSignal: true, isRequired: false, transformFunction: null }, reloadMap: { classPropertyName: "reloadMap", publicName: "reloadMap", isSignal: true, isRequired: false, transformFunction: null }, focusedGradePart: { classPropertyName: "focusedGradePart", publicName: "focusedGradePart", isSignal: true, isRequired: false, transformFunction: null }, mapConfiguration: { classPropertyName: "mapConfiguration", publicName: "mapConfiguration", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectGrade: "selectGrade" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["htmlCanvas"], descendants: true, static: true }, { propertyName: "mapContainer", first: true, predicate: ["mapContainer"], descendants: true }], ngImport: i0, template: "<div #mapContainer class=\"venue__container\">\n  <div class=\"venue__grades\">\n    <canvas #htmlCanvas id=\"c\"></canvas>\n  </div>\n  <canvas\n    #miniMap\n    class=\"venue__minimap\"\n    id=\"miniMap\"\n    [class.hidden]=\"!canvasService.showMiniMap()\"\n  ></canvas>\n  <div\n    class=\"venue__switch-price\"\n    *ngIf=\"\n      !isLoading() && canvasService.canvas\n    \"\n  >\n    <span class=\"venue__switch-label\">Show Price</span>\n    <app-switch\n      [checked]=\"venueMapType() === VenueMapType.GRADE_PRICE\"\n      [handleOnChange]=\"onChangeGradeMapType.bind(this)\"\n      className=\"bg-black\"\n    ></app-switch>\n  </div>\n  <div\n    class=\"venue__zoom-controls\"\n  >\n    <button\n      [disabled]=\"canvasService.disableZoomIn()\"\n      (click)=\"canvasService.handleZoomBtn('zoomIn')\"\n    >\n      <svg\n        [class.venue__zoom-disabled]=\"canvasService.disableZoomIn()\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M9.6 0H6.4V6.4H0V9.6H6.4V16H9.6V9.6H16V6.4H9.6V0Z\"\n          fill=\"#707086\"\n        />\n      </svg>\n    </button>\n    <hr class=\"venue__zoom-divider\" />\n    <button\n      [disabled]=\"canvasService.disableZoomOut()\"\n      (click)=\"canvasService.handleZoomBtn('zoomOut')\"\n    >\n      <svg\n        [class.venue__zoom-disabled]=\"canvasService.disableZoomOut()\"\n        width=\"16\"\n        height=\"3\"\n        viewBox=\"0 0 16 3\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path d=\"M0 0V3H16V0H0Z\" fill=\"#707086\" />\n      </svg>\n    </button>\n  </div>\n</div>\n", styles: [".venue__container{width:100%}.venue__grades{position:relative}.venue__switch-price{position:absolute;bottom:1.25rem;left:1.25rem;display:flex;align-items:center;background-color:#fff;padding:.5rem;border-radius:1.5rem;box-shadow:0 .125rem .25rem #0000001a}.venue__switch-label{color:#707086;font-size:.875rem;margin-right:.5rem}.venue__minimap{width:6rem;height:5rem;position:absolute;top:.5rem;right:.5rem;padding:.5rem;background-color:#f8f8fa;border-radius:.5rem}.venue__minimap.hidden{display:none}.venue__zoom-controls{display:flex;flex-direction:column;height:82px;width:2.25rem;background-color:#fff;border-radius:9999px;position:absolute;bottom:1.25rem;right:1.25rem;box-shadow:0 .125rem .25rem #0000001a}.venue__zoom-controls button{display:flex;justify-content:center;align-items:center;height:2.5625rem;padding:0;margin:0;padding-block:0;cursor:pointer;border-color:transparent;background-color:transparent;background-image:none}.venue__zoom-controls .venue__zoom-divider{width:2.25rem;border:1px solid #f8f8fa}.venue__zoom-controls .venue__zoom-disabled{opacity:.25;cursor:none}.venue__switch-item{width:3.25rem;border-color:#707086;border:2px solid #707086}\n"], dependencies: [{ kind: "component", type: SwitchComponent, selector: "app-switch", inputs: ["handleOnChange", "className", "checked"], outputs: ["checkedChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }], encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: VenueMapComponent, decorators: [{
            type: Component,
            args: [{ selector: 'ngx-venue-map', standalone: true, imports: [SwitchComponent, CommonModule, FormsModule], encapsulation: ViewEncapsulation.None, template: "<div #mapContainer class=\"venue__container\">\n  <div class=\"venue__grades\">\n    <canvas #htmlCanvas id=\"c\"></canvas>\n  </div>\n  <canvas\n    #miniMap\n    class=\"venue__minimap\"\n    id=\"miniMap\"\n    [class.hidden]=\"!canvasService.showMiniMap()\"\n  ></canvas>\n  <div\n    class=\"venue__switch-price\"\n    *ngIf=\"\n      !isLoading() && canvasService.canvas\n    \"\n  >\n    <span class=\"venue__switch-label\">Show Price</span>\n    <app-switch\n      [checked]=\"venueMapType() === VenueMapType.GRADE_PRICE\"\n      [handleOnChange]=\"onChangeGradeMapType.bind(this)\"\n      className=\"bg-black\"\n    ></app-switch>\n  </div>\n  <div\n    class=\"venue__zoom-controls\"\n  >\n    <button\n      [disabled]=\"canvasService.disableZoomIn()\"\n      (click)=\"canvasService.handleZoomBtn('zoomIn')\"\n    >\n      <svg\n        [class.venue__zoom-disabled]=\"canvasService.disableZoomIn()\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M9.6 0H6.4V6.4H0V9.6H6.4V16H9.6V9.6H16V6.4H9.6V0Z\"\n          fill=\"#707086\"\n        />\n      </svg>\n    </button>\n    <hr class=\"venue__zoom-divider\" />\n    <button\n      [disabled]=\"canvasService.disableZoomOut()\"\n      (click)=\"canvasService.handleZoomBtn('zoomOut')\"\n    >\n      <svg\n        [class.venue__zoom-disabled]=\"canvasService.disableZoomOut()\"\n        width=\"16\"\n        height=\"3\"\n        viewBox=\"0 0 16 3\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path d=\"M0 0V3H16V0H0Z\" fill=\"#707086\" />\n      </svg>\n    </button>\n  </div>\n</div>\n", styles: [".venue__container{width:100%}.venue__grades{position:relative}.venue__switch-price{position:absolute;bottom:1.25rem;left:1.25rem;display:flex;align-items:center;background-color:#fff;padding:.5rem;border-radius:1.5rem;box-shadow:0 .125rem .25rem #0000001a}.venue__switch-label{color:#707086;font-size:.875rem;margin-right:.5rem}.venue__minimap{width:6rem;height:5rem;position:absolute;top:.5rem;right:.5rem;padding:.5rem;background-color:#f8f8fa;border-radius:.5rem}.venue__minimap.hidden{display:none}.venue__zoom-controls{display:flex;flex-direction:column;height:82px;width:2.25rem;background-color:#fff;border-radius:9999px;position:absolute;bottom:1.25rem;right:1.25rem;box-shadow:0 .125rem .25rem #0000001a}.venue__zoom-controls button{display:flex;justify-content:center;align-items:center;height:2.5625rem;padding:0;margin:0;padding-block:0;cursor:pointer;border-color:transparent;background-color:transparent;background-image:none}.venue__zoom-controls .venue__zoom-divider{width:2.25rem;border:1px solid #f8f8fa}.venue__zoom-controls .venue__zoom-disabled{opacity:.25;cursor:none}.venue__switch-item{width:3.25rem;border-color:#707086;border:2px solid #707086}\n"] }]
        }], ctorParameters: () => [], propDecorators: { canvasRef: [{
                type: ViewChild,
                args: ['htmlCanvas', { static: true }]
            }], mapContainer: [{
                type: ViewChild,
                args: ['mapContainer']
            }] } });

/*
 * Public API Surface of ngx-venue-map
 */

/**
 * Generated bundle index. Do not edit.
 */

export { DEFAULT_MAP_CONFIG, DEFAULT_VENUE_MAP_THEME, VenueMapComponent, VenueMapPriceService, VenueMapType, scalePoint, scalePoints, scaleValue };
//# sourceMappingURL=ngx-venue-map.mjs.map