npm package: @metrixlabs/seatmap-renderer
Description: Framework-agnostic seat map renderer with pixel-perfect positioning
Author: TicketStock
Latest version: 1.1.1

# @metrixlabs/seatmap-renderer

A framework-agnostic library for rendering pixel-perfect seat maps and venue layouts. This library provides the core rendering capabilities used by the TicketStock Constructor tool.

## Features

- **Framework-agnostic** - Works with React, Vue, Angular, or vanilla JavaScript
- **Pixel-perfect positioning** - Same layout accuracy as the preview mode
- **Interactive seat maps** - Click, hover, and keyboard navigation
- **Customizable styling** - Colors, sizes, accessibility labels
- **Data conversion** - Convert editor data to renderable format
- **TypeScript support** - Full type safety

## Installation

```bash
npm install @metrixlabs/seatmap-renderer
```

## Quick Start

### Basic SVG Generation

```javascript
import { toSvgString, flattenSeatMap } from '@metrixlabs/seatmap-renderer'
```

// Convert your editor data to renderable format
const seatMapData = flattenSeatMap(editorJson)

// Generate SVG string
const svgString = toSvgString({
  data: seatMapData,
  seatRadius: 8,
  width: '100%',
  height: '600px',
  padding: 24
})

// Use the SVG string
document.getElementById('seat-map').innerHTML = svgString
```

### Interactive Seat Map

```javascript
import { mountVanillaSeatMap, flattenSeatMap } from '@metrixlabs/seatmap-renderer'
```

const seatMapData = flattenSeatMap(editorJson)

const { el, destroy } = mountVanillaSeatMap({
  target: document.getElementById('seat-map'),
  data: seatMapData,
  onSeatClick: (seatId, meta, event) => {
    console.log('Seat clicked:', seatId)
  },
  onSeatHover: (seatId, meta, event) => {
    console.log('Seat hovered:', seatId)
  }
})

// Clean up when done
destroy()
```

### Custom Styling

```javascript
const svgString = toSvgString({
  data: seatMapData,
  getSeatFill: (seat, sector) => {
    // Custom color logic
    if (seat.status === 'occupied') return '#ff0000'
    if (sector?.color) return sector.color
    return '#999999'
  },
  classNameForSeat: (seat) => {
    // Custom CSS classes
    return `seat seat-${seat.status}`
  },
  a11yLabel: (seat, sector) => {
    // Custom accessibility labels
    return `Seat ${seat.rowTitle} ${seat.localIndex + 1}, ${sector?.price || 'Free'}`
  }
})
```

## React Usage Examples

### Simple React Component

```tsx
import React, { useEffect, useRef } from 'react'
import { mountVanillaSeatMap, type SeatMapData } from '@metrixlabs/seatmap-renderer'

const SeatMapComponent: React.FC = () => {
  const containerRef = useRef<HTMLDivElement>(null)

  const seatMapData: SeatMapData = {
    seats: [
      { id: '1', x: 100, y: 100, r: 8, rowId: 'row1', localIndex: 0, entityId: 'sector1', sectorId: 'vip' },
      { id: '2', x: 120, y: 100, r: 8, rowId: 'row1', localIndex: 1, entityId: 'sector1', sectorId: 'vip' },
    ],
    sectors: [
      { id: 'vip', color: '#ff6b6b', price: 100 }
    ]
  }

  useEffect(() => {
    if (containerRef.current) {
      const cleanup = mountVanillaSeatMap(containerRef.current, seatMapData, {
        onSeatClick: (seatId) => console.log('Seat clicked:', seatId),
        onSeatHover: (seatId) => console.log('Seat hovered:', seatId)
      })

      return cleanup
    }
  }, [])

  return (
    <div 
      ref={containerRef} 
      style={{ width: '600px', height: '400px', border: '1px solid #ccc' }} 
    />
  )
}
```

### Constructor Data Integration

```tsx
import React, { useState, useEffect } from 'react'
import { flattenSeatMap, toSvgString, mountVanillaSeatMap } from '@metrixlabs/seatmap-renderer'

const ConstructorIntegration: React.FC = () => {
  const [seatMapData, setSeatMapData] = useState(null)

  // Complex data structure from constructor
  const complexData = {
    entities: {
      'sector1': {
        id: 'sector1',
        entityKey: 'seatedSector',
        name: 'VIP Section',
        x: 100, y: 100,
        sector: 'vip',
        rowPitch: 30, seatPitch: 25,
        rowIds: ['row1', 'row2']
      }
    },
    rows: {
      'row1': { id: 'row1', title: 'Row A', seatCount: 5, index: 0 },
      'row2': { id: 'row2', title: 'Row B', seatCount: 5, index: 1 }
    },
    sectors: [
      { id: 'vip', name: 'VIP', color: '#ff6b6b', price: 100 }
    ]
  }

  useEffect(() => {
    const flattened = flattenSeatMap(complexData)
    setSeatMapData(flattened)
  }, [])

  const handleExport = () => {
    if (seatMapData) {
      const svg = toSvgString(seatMapData)
      // Download or display SVG
    }
  }

  return (
    <div>
      <button onClick={handleExport}>Export SVG</button>
      {/* Render seat map */}
    </div>
  )
}
```

**📖 For complete React examples, see [REACT_USAGE_EXAMPLES.md](./REACT_USAGE_EXAMPLES.md)**

## API Reference

### `toSvgString(options)`

Generates an SVG string representation of the seat map.

**Options:**
- `data: SeatMapData` - The seat map data
- `seatRadius?: number` - Seat circle radius (default: 8)
- `width?: string | number` - SVG width (default: '100%')
- `height?: string | number` - SVG height (default: '100%')
- `padding?: number` - ViewBox padding (default: 24)
- `classNameForSeat?: (seat: SeatGlyph) => string` - Custom CSS class function
- `getSeatFill?: (seat: SeatGlyph, sector?: Sector) => string` - Custom fill color function
- `a11yLabel?: (seat: SeatGlyph, sector?: Sector) => string` - Custom accessibility label function

### `mountVanillaSeatMap(options)`

Mounts an interactive seat map in a DOM element.

**Options:**
- `target: HTMLElement` - Target DOM element
- `data: SeatMapData` - The seat map data
- `seatRadius?: number` - Seat circle radius
- `padding?: number` - ViewBox padding
- `onSeatClick?: (seatId: string, meta: DOMStringMap, event: MouseEvent) => void` - Click handler
- `onSeatHover?: (seatId: string | null, meta: DOMStringMap | null, event: PointerEvent) => void` - Hover handler
- `a11y?: { enableKeyboard?: boolean }` - Accessibility options (default: true)
- `classNameForSeat?: (seat: SeatGlyph) => string` - Custom CSS class function
- `getSeatFill?: (seat: SeatGlyph, sector?: Sector) => string` - Custom fill color function

**Returns:** `{ el: SVGSVGElement, destroy(): void }`

### `flattenSeatMap(editorJson, options?)`

Converts editor JSON data to the renderable format.

**Parameters:**
- `editorJson: any` - The editor state JSON
- `options?: { seatRadius?: number }` - Conversion options

**Returns:** `SeatGlyph[]`

### `computeViewBox(seats, padding?)`

Computes the optimal SVG viewBox for the given seats.

**Parameters:**
- `seats: SeatGlyph[]` - Array of seat glyphs
- `padding?: number` - Padding around the seats (default: 24)

**Returns:** `{ minX: number, minY: number, width: number, height: number }`

## Types

### `SeatGlyph`

```typescript
type SeatGlyph = {
  id: string;          // Unique identifier
  x: number;           // X coordinate
  y: number;           // Y coordinate
  r?: number;          // Radius (default: 8)
  rowId: string;       // Row identifier
  rowTitle?: string;   // Row display name
  localIndex: number;  // Seat index within row
  entityId: string;    // Entity identifier
  sectorId?: string;   // Sector identifier
  price?: number;      // Seat price
  currency?: string;   // Price currency
  [k: string]: unknown; // Additional metadata
}
```

### `Sector`

```typescript
type Sector = {
  id: string;          // Sector identifier
  color?: string;      // CSS color
  price?: number;      // Default price
  currency?: string;   // Price currency
}
```

### `SeatMapData`

```typescript
type SeatMapData = {
  seats: SeatGlyph[];  // Array of seats
  sectors?: Sector[];  // Array of sectors
  meta?: Record<string, unknown>; // Additional metadata
}
```

## Browser Support

- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+

## License

MIT License - see LICENSE file for details.

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request