ticket-selector
Version:
A professional stadium seat selection widget with multi-language support
531 lines (415 loc) ⢠18.6 kB
Markdown
# TicketSelector Widget
[](https://github.com/IBP-LLC/ticket-selector/releases)
[](https://opensource.org/licenses/MIT)
[](https://github.com/IBP-LLC/ticket-selector)
[](https://github.com/IBP-LLC/ticket-selector)
[](https://github.com/IBP-LLC/ticket-selector)
A professional stadium seat selection widget with multi-language support and comprehensive functionality. Perfect for event ticketing systems, stadium booking platforms, and venue management applications.
## ⨠Features
- šÆ **Interactive seat selection** with real-time feedback and visual indicators
- š **Multi-language support** (English, Azerbaijani, and more) with automatic browser detection
- š **Pan and zoom controls** with smooth animations and fullscreen mode
- š± **Responsive design** that works flawlessly on all devices and screen sizes
- šØ **Customizable styling** with BEM CSS methodology and SCSS variables
- ā” **Professional build system** with minification, tree-shaking, and optimization
- š§© **Modular architecture** with ES6 modules and bundled dependencies
- š§ **Event-driven API** for seamless integration with any backend system
- šŖ **Dynamic sector loading** with loading states and error handling
- š±ļø **Advanced tooltips** with seat information and availability status
## š Installation
### Via Local Files (Recommended)
Download and include the built files from the dist directory:
```html
<!-- Include CSS -->
<link rel="stylesheet" href="../dist/ticket-selector.min.css">
<!-- Include JavaScript (Panzoom is bundled) -->
<script src="../dist/ticket-selector.js"></script>
```
### Alternative Installation
1. Clone the repository and build the project:
```bash
git clone https://github.com/IBP-LLC/ticket-selector.git
cd ticket-selector
npm install
npm run build
```
2. Include the CSS and JS files in your project:
```html
<link rel="stylesheet" href="path/to/dist/ticket-selector.min.css">
<script src="path/to/dist/ticket-selector.js"></script>
```
## š Quick Start
### 1. HTML Structure
```html
<div id="ticket-selector" class="ticket-select">
<div class="ticket-select__container">
<div class="ticket-select__wrapper">
<div class="ticket-select__viewport">
<div class="ticket-select__content">
<div class="ticket-select__stadium">
<img src="stadium.jpg" alt="Stadium" class="ticket-select__stadium-image">
<div class="ticket-select__stadium-overlay">
<svg class="ticket-select__stadium-svg" viewBox="0 0 1000 600">
<!-- Define your stadium sectors -->
<path class="ticket-select__sector"
data-sector-id="1"
data-sector-color="blue"
d="M100,100 L300,100 L300,200 L100,200 Z"/>
<path class="ticket-select__sector"
data-sector-id="2"
data-sector-color="red"
d="M100,400 L300,400 L300,500 L100,500 Z"/>
<path class="ticket-select__sector"
data-sector-id="3"
data-sector-color="green"
d="M400,100 L600,100 L600,200 L400,200 Z"/>
<!-- Add more sectors... -->
</svg>
</div>
</div>
</div>
</div>
<!-- Info bar with seat counter -->
<div class="ticket-select__info">
<span class="ticket-select__selected-count">No seats selected</span>
<a href="#" disabled class="ticket-select__info-btn">Buy <span data-ticket-selector-count style="display: none;"></span></a>
</div>
</div>
</div>
</div>
```
### 2. Initialize the Widget
```javascript
const ticketSelector = new TicketSelector('#ticket-selector', {
lang: 'en', // 'en', 'az' or auto-detect
showControls: true,
showInfo: true,
maxSeat: Infinity // Maximum number of seats that can be selected (default: Infinity)
});
// Initialize sector summaries for dynamic loading
ticketSelector.addEventListener('ready', (event) => {
console.log(`TicketSelector v${event.detail.version} is ready!`);
// Load sector summaries to update HTML attributes dynamically
const sectorSummaries = {
'1': {
name: 'North Stand',
price: '100 USD',
availability: { available: 45, total: 50 },
disabled: false
},
'2': {
name: 'South Stand',
price: '80 USD',
availability: { available: 0, total: 30 },
disabled: true
}
};
ticketSelector.loadSectorSummaries(sectorSummaries);
});
// Listen for sector clicks
ticketSelector.addEventListener('sectorClick', async (event) => {
const { sectorId, sectorName, element } = event.detail;
// Show loading state
ticketSelector.showLoading('Loading seats...');
try {
// Load sector data from your API
const response = await fetch(`/api/sectors/${sectorId}/seats`);
const sectorData = await response.json();
// Load seats into the widget
ticketSelector.setSectorData(sectorData, {
sectorId,
sectorName,
sectorColor: element.dataset.sectorColor
});
} catch (error) {
console.error('Failed to load sector:', error);
alert('Failed to load seat information. Please try again.');
}
});
// Get selected seats when user is ready to proceed
document.querySelector('.ticket-select__info-btn').addEventListener('click', () => {
const selectedSeats = ticketSelector.getSelectedSeats();
console.log('User selected:', selectedSeats);
// Process the selection...
processTicketSelection(selectedSeats);
});
```
## š API Reference
### Constructor Options
```javascript
new TicketSelector(container, options)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `lang` | string | auto-detect | Language code ('en', 'az', 'tr') |
| `showControls` | boolean | `true` | Show zoom/pan/fullscreen controls |
| `showInfo` | boolean | `true` | Show selected seats counter |
| `maxSeat` | number | `Infinity` | Maximum number of seats that can be selected |
### Methods
#### `setSectorData(sectorData, sectorInfo?)`
Load seats for a specific sector with detailed configuration.
```javascript
const sectorData = {
id: 'north-stand',
name: 'North Stand',
price: '100 USD',
availability: { available: 45, total: 50 },
disabled: false,
seats: [
{
id: 1,
name: 'Row 1',
data: [
{ id: 1001, available: true },
{ id: 1002, available: false },
{ id: 1003, available: true, skipLeft: 2 }, // Gap before seat
{ id: 1004, available: true, skipRight: 1 }, // Gap after seat
{ id: 1005, available: true, number: 15 } // Manual seat number
]
},
{
id: 2,
name: 'Row 2',
data: [
{ id: 2001, available: true },
{ id: 2002, available: true }
]
}
]
};
ticketSelector.setSectorData(sectorData, {
sectorId: '1',
sectorName: 'North Stand',
sectorColor: 'blue' // Applies color theme to seats
});
```
**Seat Data Properties:**
| Property | Type | Description |
|----------|------|-------------|
| `id` | number | Unique seat identifier (required) |
| `available` | boolean | Whether seat is available for selection |
| `skipLeft` | number | Number of empty spaces before this seat |
| `skipRight` | number | Number of empty spaces after this seat |
| `number` | number | Manual seat number (optional - if not provided, auto-numbered) |
#### `getSelectedSeats()`
Returns array of selected seats with comprehensive information.
```javascript
const selectedSeats = ticketSelector.getSelectedSeats();
// Returns:
// [
// {
// sector: "North Stand",
// row: 1,
// seat: 3,
// seatId: "seat-1003",
// price: "50 USD",
// category: "Standard"
// }
// ]
```
#### `setLanguage(lang)`
Change language dynamically with instant UI updates.
```javascript
ticketSelector.setLanguage('az'); // Switch to Azerbaijani
ticketSelector.setLanguage('en'); // Switch to English
```
#### `showLoading(message?)`
Display loading state with optional custom message.
```javascript
ticketSelector.showLoading('Loading seat data...');
```
#### `destroy()`
Clean up the widget and remove all event listeners.
```javascript
ticketSelector.destroy();
```
### Events
#### `sectorClick`
Triggered when a sector/zone is clicked in the stadium view.
```javascript
ticketSelector.addEventListener('sectorClick', (event) => {
const { sectorId, sectorName, element } = event.detail;
console.log(`User clicked on ${sectorName} (ID: ${sectorId})`);
});
```
## š ļø Development
### Prerequisites
- Node.js 16+
- npm, yarn, or pnpm
### Setup
```bash
git clone https://github.com/IBP-LLC/ticket-selector.git
cd ticket-selector
npm install # or yarn install / pnpm install
```
### Development Server
```bash
npm run dev
```
This will start:
- š¦ **Rollup** in watch mode (JavaScript bundling)
- šØ **Sass** in watch mode (CSS compilation)
- š **PostCSS** processor (CSS optimization)
- š **Live server** at `http://localhost:8080` with hot reload
### Build Commands
```bash
npm run build # Production build
npm run clean # Clean dist folder
npm run lint # Run ESLint
npm run lint:fix # Fix linting issues
npm run format # Format code with Prettier
npm run test # Run tests (when available)
```
### Package Manager Support
The project works with any modern package manager:
```bash
# npm
npm install && npm run dev
# yarn
yarn install && yarn dev
# pnpm
pnpm install && pnpm dev
```
## š Project Structure
```
src/
āāā TicketSelector.js # Main widget class
āāā i18n/ # Internationalization
ā āāā index.js # I18n system core
ā āāā translations.js # All language translations
āāā styles/ # SCSS styles
āāā main.scss # Main stylesheet
āāā _variables.scss # SCSS variables & theming
dist/ # Built files (generated)
āāā ticket-selector.js # UMD bundle
āāā ticket-selector.esm.js # ES module bundle
āāā ticket-selector.min.css # Minified CSS
āāā *.map # Source maps
examples/ # Usage examples
āāā index.html # Basic implementation example
āāā stadium.jpg # Sample stadium image
docs/ # Documentation (generated)
tests/ # Test files (when available)
```
## š Browser Support
| Browser | Version |
|---------|---------|
| Chrome/Chromium | 60+ |
| Firefox | 55+ |
| Safari | 12+ |
| Edge | 79+ |
| iOS Safari | 12+ |
| Android WebView | 60+ |
## šØ Customization
### CSS Variables
```css
:root {
--ticket-select-primary: #2175bf;
--ticket-select-secondary: #f8ad02;
--ticket-select-success: #0a8837;
--ticket-select-danger: #bb2932;
--ticket-select-bg: #f1f1f1;
--ticket-select-white: #ffffff;
}
```
### Theming
The widget supports custom color schemes through SCSS variables and CSS custom properties. See `src/styles/_variables.scss` for all available options.
## š Performance
- **Bundle size**: ~56KB minified (includes Panzoom)
- **CSS size**: ~8KB minified
- **Zero external dependencies** - everything is bundled
- **Tree-shakeable** ES modules
- **Optimized rendering** with efficient DOM updates
- **Smooth initialization** with loading states
- **Mobile optimized** with touch event support
## š Changelog
### v1.0.8 (Latest)
- šÆ **Maximum Seat Selection**: New `maxSeat` option to limit the number of seats that can be selected
- ā ļø **Seat Limit Warnings**: Informative tooltip notifications when maximum seat limit is reached
- š **Multi-language Seat Limits**: Added translations for seat limit messages in English, Azerbaijani, and Turkish
- š§ **Sector Drag Fix**: Fixed click event triggering after pan/zoom drag on sector paths
- š±ļø **Improved Drag Detection**: Separate drag handling for sectors and seats to prevent false clicks
- ⨠**Better UX for Limits**: Visual feedback with cursor changes and informative tooltips
- š **Drag Threshold Optimization**: Consistent 5px drag threshold for both sectors and seats
### v1.0.7
- š¢ **Manual Seat Numbering**: Added optional `number` property for custom seat numbering from backend
- š§ **Flexible Numbering System**: Backend can now override automatic seat numbers
- š **Enhanced Data Structure**: Seats can now have custom numbers from backend
- š **Documentation Updates**: Added seat data properties table with `number` parameter
- š” **Example Implementation**: Updated examples to show manual seat numbering usage
- š **Backward Compatibility**: Automatic numbering still works when `number` is not provided
### v1.0.6
- š¼ļø **Stadium Image Support**: Added stadium image support in the widget
- ⨠**Enhanced Seat Styling**: Visual improvements and seat styling enhancements
- š **Bug Fixes**: Various minor bugs and performance optimizations
### v1.0.5
- šÆ **Dynamic Seat Counter**: Purchase button now shows selected seat count (e.g., "Buy (3)")
- š **Enhanced Button States**: Purchase button automatically enables/disables based on seat selection
- š« **Improved Disabled Sectors**: Better visual feedback for unavailable sectors
- šØ **Button UX Improvements**: Disabled by default when no seats selected, dynamic count display
- ⨠**Sector Interaction Refinements**: Better hover states and cursor feedback
- š ļø **CSS & JavaScript Enhancements**: Improved styling consistency and responsive behavior
### v1.0.4
- š **Dynamic Sector Loading**: Backend/mock data integration for sector information
- š« **Disabled Sector Functionality**: Visual feedback and proper state management
- š **Custom HTML Preservation**: Info section content maintained during updates
- š¤ **Automated Version Updates**: Husky hooks for version management
- š·ļø **Enhanced Tooltips**: Improved sector tooltip system with price and availability
- š **Better Internationalization**: Enhanced multi-language support
### v1.0.3
- š **Multi-language Support**: Added English, Azerbaijani, and Turkish language support
- š§ **Dynamic Internationalization**: Professional i18n system implementation
- ā” **Professional Build System**: Automated workflows and build optimizations
- šØ **Code Formatting**: Improved style consistency and formatting
- š ļø **Error Handling**: Better user feedback and error management
- š **Enhanced Documentation**: Improved examples and documentation
### v1.0.2
- š± **Mobile Touch Support**: Added touchend events for perfect mobile interaction
- ā” **Instant UI Feedback**: Viewport class applied immediately on sector click for responsive UI
- š **Improved Loading States**: CSS-based loading control with instant visual feedback
- š« **Touch/Click Conflict Fix**: Prevented event conflicts with preventDefault for smooth mobile experience
- šÆ **Enhanced Mobile UX**: Complete mobile user experience optimization
### v1.0.1
- š¦ **Bundled Panzoom**: No more external CDN dependencies - panzoom is now included in the bundle
- š **Initial Loading**: Added professional loading screen during widget initialization
- šÆ **Version Tracking**: Added version property to constructor for debugging and tracking
- š **Ready Event**: Widget now emits 'ready' event when fully initialized
- š ļø **Improved Error Handling**: Better error messages and loading states
- ā” **Self-contained**: Widget is now completely standalone with no external dependencies
### v1.0.0
- ⨠Initial release with full functionality
- š Multi-language support (EN, AZ)
- š¼ļø Fullscreen mode with cross-browser compatibility
- š± Responsive design for all screen sizes
- ā” Professional build system with hot reload
- šÆ Interactive seat selection with visual feedback
- š§ Event-driven API for easy integration
- š Comprehensive documentation and examples
## š¤ Contributing
We welcome contributions! Please follow these steps:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes and add tests if applicable
4. Run `npm run lint` and `npm run build` to ensure code quality
5. Commit your changes (`git commit -m 'Add amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
### Development Guidelines
- Follow the existing code style and use Prettier for formatting
- Add JSDoc comments for new public methods
- Ensure backward compatibility when possible
- Update documentation for any API changes
## š License
MIT License - see [LICENSE](LICENSE) file for details.
## š Support
- š§ Email: support@ibp-llc.com
- š Bug reports: [GitHub Issues](https://github.com/IBP-LLC/ticket-selector/issues)
- š¬ Discussions: [GitHub Discussions](https://github.com/IBP-LLC/ticket-selector/discussions)
- š Documentation: [GitHub Wiki](https://github.com/IBP-LLC/ticket-selector/wiki)
## š Acknowledgments
- [Panzoom](https://github.com/timmywil/panzoom) for zoom and pan functionality
- All contributors who help improve this project
---
<div align="center">
<strong>Made with ā¤ļø by IBP LLC</strong>
</div>