@zonetrix/viewer 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,8 +14,10 @@ Lightweight React component for rendering interactive seat maps in your booking
14
14
  - 🌐 **Flexible Config Loading** - Load from JSON files or API endpoints
15
15
  - 🔍 **Mouse Wheel Zoom** - Smooth zoom with configurable limits
16
16
  - 🎨 **Customizable Colors** - Override default colors to match your brand
17
+ - 🏢 **Multi-floor Support** - Filter and display seats by floor
18
+ - 👁️ **Hidden Seats** - Automatically filters out hidden seats
17
19
  - 📱 **Responsive** - Works seamlessly across all screen sizes
18
- - ⚡ **Lightweight** - Minimal dependencies (~95KB gzipped)
20
+ - ⚡ **Lightweight** - Minimal dependencies
19
21
  - 🔒 **Type-safe** - Full TypeScript support
20
22
 
21
23
  ## Installation
@@ -31,7 +33,30 @@ pnpm add @zonetrix/viewer
31
33
  ### Peer Dependencies
32
34
 
33
35
  ```bash
34
- npm install react react-dom
36
+ npm install react react-dom konva react-konva
37
+ ```
38
+
39
+ ## Exports
40
+
41
+ ```typescript
42
+ // Main component
43
+ import { SeatMapViewer } from '@zonetrix/viewer';
44
+ import type { SeatMapViewerProps } from '@zonetrix/viewer';
45
+
46
+ // Types
47
+ import type {
48
+ SeatState,
49
+ SeatShape,
50
+ SeatData,
51
+ SeatMapConfig,
52
+ ColorSettings
53
+ } from '@zonetrix/viewer';
54
+
55
+ // Hooks
56
+ import { useConfigFetcher } from '@zonetrix/viewer';
57
+
58
+ // Constants
59
+ import { DEFAULT_COLORS } from '@zonetrix/viewer';
35
60
  ```
36
61
 
37
62
  ## Quick Start
@@ -40,6 +65,7 @@ npm install react react-dom
40
65
 
41
66
  ```tsx
42
67
  import { SeatMapViewer } from '@zonetrix/viewer';
68
+ import type { SeatData, SeatMapConfig } from '@zonetrix/viewer';
43
69
  import venueConfig from './venue-config.json';
44
70
 
45
71
  function BookingApp() {
@@ -77,14 +103,19 @@ function BookingApp() {
77
103
  |------|------|----------|-------------|
78
104
  | `config` | `SeatMapConfig` | No* | Seat map configuration object |
79
105
  | `configUrl` | `string` | No* | URL to fetch configuration from |
106
+ | `floorId` | `string` | No | Filter seats/stages by floor ID |
107
+ | `onFloorChange` | `(floorId: string) => void` | No | Callback when floor changes |
80
108
  | `reservedSeats` | `string[]` | No | Array of seat IDs/numbers to mark as reserved |
81
109
  | `unavailableSeats` | `string[]` | No | Array of seat IDs/numbers to mark as unavailable |
82
110
  | `onSeatSelect` | `(seat: SeatData) => void` | No | Callback when a seat is selected |
83
111
  | `onSeatDeselect` | `(seat: SeatData) => void` | No | Callback when a seat is deselected |
84
112
  | `onSelectionChange` | `(seats: SeatData[]) => void` | No | Callback when selection changes |
85
113
  | `colorOverrides` | `Partial<ColorSettings>` | No | Custom colors for seat states |
114
+ | `showTooltip` | `boolean` | No | Show tooltips on hover (default: true) |
86
115
  | `zoomEnabled` | `boolean` | No | Enable/disable mouse wheel zoom (default: true) |
87
- | `showSelectedCount` | `boolean` | No | Show selected seats counter (default: true) |
116
+ | `className` | `string` | No | Custom CSS class for the container |
117
+ | `onConfigLoad` | `(config: SeatMapConfig) => void` | No | Callback when config is loaded |
118
+ | `onError` | `(error: Error) => void` | No | Callback when an error occurs |
88
119
 
89
120
  *Note: Either `config` or `configUrl` must be provided.
90
121
 
@@ -226,6 +257,68 @@ function MobileOptimized() {
226
257
  }
227
258
  ```
228
259
 
260
+ ### 6. Multi-floor Venue
261
+
262
+ ```tsx
263
+ import { useState } from 'react';
264
+ import { SeatMapViewer } from '@zonetrix/viewer';
265
+
266
+ function MultiFloorVenue() {
267
+ const [currentFloor, setCurrentFloor] = useState('floor_1');
268
+
269
+ return (
270
+ <div>
271
+ {/* Floor selector */}
272
+ <div className="floor-tabs">
273
+ {venueConfig.floors?.map((floor) => (
274
+ <button
275
+ key={floor.id}
276
+ onClick={() => setCurrentFloor(floor.id)}
277
+ className={currentFloor === floor.id ? 'active' : ''}
278
+ >
279
+ {floor.name}
280
+ </button>
281
+ ))}
282
+ </div>
283
+
284
+ <SeatMapViewer
285
+ config={venueConfig}
286
+ floorId={currentFloor}
287
+ onFloorChange={setCurrentFloor}
288
+ onSeatSelect={(seat) => handleSelection(seat)}
289
+ />
290
+ </div>
291
+ );
292
+ }
293
+ ```
294
+
295
+ ### 7. Error Handling
296
+
297
+ ```tsx
298
+ import { SeatMapViewer } from '@zonetrix/viewer';
299
+
300
+ function RobustViewer() {
301
+ const handleConfigLoad = (config) => {
302
+ console.log('Config loaded:', config.metadata.name);
303
+ console.log('Total seats:', config.seats.length);
304
+ };
305
+
306
+ const handleError = (error) => {
307
+ console.error('Failed to load seat map:', error.message);
308
+ // Show error notification to user
309
+ };
310
+
311
+ return (
312
+ <SeatMapViewer
313
+ configUrl="https://api.example.com/venues/123/config"
314
+ onConfigLoad={handleConfigLoad}
315
+ onError={handleError}
316
+ onSeatSelect={(seat) => console.log('Selected:', seat)}
317
+ />
318
+ );
319
+ }
320
+ ```
321
+
229
322
  ## Configuration Format
230
323
 
231
324
  The viewer accepts a `SeatMapConfig` object. You can create these configurations using our creator studio or build them programmatically.
@@ -289,15 +382,18 @@ interface SeatData {
289
382
  columnLabel?: string;
290
383
  price?: number;
291
384
  seatNumber?: string;
385
+ floorId?: string;
292
386
  }
293
387
  ```
294
388
 
295
389
  ### SeatState
296
390
 
297
391
  ```typescript
298
- type SeatState = 'available' | 'reserved' | 'selected' | 'unavailable';
392
+ type SeatState = 'available' | 'reserved' | 'selected' | 'unavailable' | 'hidden';
299
393
  ```
300
394
 
395
+ Note: Hidden seats are automatically filtered out from the viewer.
396
+
301
397
  ### SeatShape
302
398
 
303
399
  ```typescript
@@ -314,6 +410,7 @@ interface ColorSettings {
314
410
  seatReserved: string;
315
411
  seatSelected: string;
316
412
  seatUnavailable: string;
413
+ seatHidden: string;
317
414
  gridLines: string;
318
415
  currency: string;
319
416
  }
@@ -327,6 +424,7 @@ interface ColorSettings {
327
424
  | `reserved` | Seat is booked by another user | ❌ No | Yellow/Warning color |
328
425
  | `selected` | Seat is selected by current user | ✅ Yes (to deselect) | Primary/Blue color |
329
426
  | `unavailable` | Seat is blocked (maintenance, etc.) | ❌ No | Gray color |
427
+ | `hidden` | Seat exists but is not displayed | ❌ No | Not rendered |
330
428
 
331
429
  ## Events & Callbacks
332
430
 
@@ -4,6 +4,8 @@ import { SeatMapConfig, SeatData, ColorSettings } from '../types';
4
4
  export interface SeatMapViewerProps {
5
5
  config?: SeatMapConfig;
6
6
  configUrl?: string;
7
+ floorId?: string;
8
+ onFloorChange?: (floorId: string) => void;
7
9
  reservedSeats?: string[];
8
10
  unavailableSeats?: string[];
9
11
  onSeatSelect?: (seat: SeatData) => void;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),r=require("react"),x=require("react-konva");function P(s){const[o,d]=r.useState(null),[g,f]=r.useState(!1),[m,u]=r.useState(null),h=async()=>{if(s){f(!0),u(null);try{const n=await fetch(s);if(!n.ok)throw new Error(`Failed to fetch config: ${n.statusText}`);const c=await n.json();d(c)}catch(n){const c=n instanceof Error?n:new Error("Unknown error occurred");u(c),console.error("Failed to fetch seat map config:",c)}finally{f(!1)}}};return r.useEffect(()=>{h()},[s]),{config:o,loading:g,error:m,refetch:h}}const $={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",gridLines:"#404040",currency:"KD"},z=r.memo(({seat:s,state:o,colors:d,onClick:g})=>{const u={available:d.seatAvailable,reserved:d.seatReserved,selected:d.seatSelected,unavailable:d.seatUnavailable}[o],h=o==="available"||o==="selected",n=r.useCallback(()=>{h&&g(s)},[s,g,h]),c={x:s.position.x,y:s.position.y,fill:u,stroke:"#ffffff",strokeWidth:1,onClick:n,onTap:n};return s.shape==="circle"?t.jsx(x.Circle,{...c,radius:12}):t.jsx(x.Rect,{...c,width:12*2,height:12*2,offsetX:12,offsetY:12,cornerRadius:s.shape==="square"?0:4})});z.displayName="ViewerSeat";const V=r.memo(({stage:s,stageColor:o})=>t.jsxs(x.Group,{x:s.position.x,y:s.position.y,children:[t.jsx(x.Rect,{width:s.config.width,height:s.config.height,fill:o+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),t.jsx(x.Text,{text:s.config.label,x:0,y:0,width:s.config.width,height:s.config.height,fontSize:24,fontStyle:"bold",fill:"#ffffff",align:"center",verticalAlign:"middle"})]}));V.displayName="ViewerStage";const X=({config:s,configUrl:o,reservedSeats:d=[],unavailableSeats:g=[],onSeatSelect:f,onSeatDeselect:m,onSelectionChange:u,colorOverrides:h,showTooltip:n=!0,zoomEnabled:c=!0,className:C="",onConfigLoad:R,onError:E})=>{var A;const T=r.useRef(null),[p,q]=r.useState(new Set),[N,O]=r.useState(1),[b,W]=r.useState({x:0,y:0}),{config:I,loading:U,error:w}=P(o),i=s||I,M=r.useMemo(()=>i?{...i.colors,...h}:{...$,...h},[i,h]),j=r.useMemo(()=>{const e=new Set(d),a=new Set(g);return{reserved:e,unavailable:a}},[d,g]),F=r.useCallback(e=>{const a=e.id,l=e.seatNumber||"";return j.unavailable.has(a)||j.unavailable.has(l)?"unavailable":j.reserved.has(a)||j.reserved.has(l)?"reserved":p.has(a)?"selected":e.state},[j,p]);r.useEffect(()=>{i&&R&&R(i)},[i,R]),r.useEffect(()=>{w&&E&&E(w)},[w,E]);const Y=r.useCallback(e=>{const a=F(e);if(a!=="available"&&a!=="selected")return;const l=p.has(e.id);q(v=>{const y=new Set(v);return l?y.delete(e.id):y.add(e.id),y}),l?m==null||m(e):(f==null||f(e),f||console.log("Seat selected:",e))},[F,p,f,m]),S=r.useMemo(()=>i?i.seats.filter(e=>p.has(e.id)):[],[i,p]);r.useEffect(()=>{u==null||u(S)},[S,u]);const B=r.useCallback(e=>{if(!c)return;e.evt.preventDefault();const a=T.current;if(!a)return;const l=N,v=a.getPointerPosition();if(!v)return;const y={x:(v.x-b.x)/l,y:(v.y-b.y)/l},K=e.evt.deltaY>0?-1:1,L=1.05;let k=K>0?l*L:l/L;k=Math.max(.5,Math.min(5,k)),O(k),W({x:v.x-y.x*k,y:v.y-y.y*k})},[c,N,b]);return U?t.jsx("div",{className:`flex items-center justify-center h-full ${C}`,children:t.jsx("p",{children:"Loading seat map..."})}):w?t.jsx("div",{className:`flex items-center justify-center h-full ${C}`,children:t.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",w.message]})}):i?t.jsxs("div",{className:`relative ${C}`,children:[t.jsxs(x.Stage,{ref:T,width:i.canvas.width,height:i.canvas.height,scaleX:N,scaleY:N,x:b.x,y:b.y,onWheel:B,style:{backgroundColor:i.canvas.backgroundColor},children:[t.jsx(x.Layer,{listening:!1,children:(A=i.stages)==null?void 0:A.map(e=>t.jsx(V,{stage:e,stageColor:M.stageColor},e.id))}),t.jsx(x.Layer,{children:i.seats.map(e=>t.jsx(z,{seat:e,state:F(e),colors:M,onClick:Y},e.id))})]}),S.length>0&&t.jsxs("div",{className:"absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg",children:[t.jsxs("h3",{className:"font-semibold mb-2",children:["Selected Seats (",S.length,")"]}),t.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:S.map(e=>t.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${M.currency} ${e.price.toFixed(2)}`]},e.id))})]})]}):t.jsx("div",{className:`flex items-center justify-center h-full ${C}`,children:t.jsx("p",{children:"No configuration provided"})})};exports.DEFAULT_COLORS=$;exports.SeatMapViewer=X;exports.useConfigFetcher=P;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),t=require("react"),h=require("react-konva");function $(i){const[f,n]=t.useState(null),[v,x]=t.useState(!1),[p,d]=t.useState(null),u=async()=>{if(i){x(!0),d(null);try{const c=await fetch(i);if(!c.ok)throw new Error(`Failed to fetch config: ${c.statusText}`);const l=await c.json();n(l)}catch(c){const l=c instanceof Error?c:new Error("Unknown error occurred");d(l),console.error("Failed to fetch seat map config:",l)}finally{x(!1)}}};return t.useEffect(()=>{u()},[i]),{config:f,loading:v,error:p,refetch:u}}const z={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",seatHidden:"#4a4a4a",gridLines:"#404040",currency:"KD"},V=t.memo(({seat:i,state:f,colors:n,onClick:v})=>{const d={available:n.seatAvailable,reserved:n.seatReserved,selected:n.seatSelected,unavailable:n.seatUnavailable,hidden:n.seatHidden}[f],u=f==="available"||f==="selected",c=t.useCallback(()=>{u&&v(i)},[i,v,u]),l={x:i.position.x,y:i.position.y,fill:d,stroke:"#ffffff",strokeWidth:1,onClick:c,onTap:c};return i.shape==="circle"?s.jsx(h.Circle,{...l,radius:12}):s.jsx(h.Rect,{...l,width:24,height:24,offsetX:12,offsetY:12,cornerRadius:i.shape==="square"?0:4})});V.displayName="ViewerSeat";const q=t.memo(({stage:i,stageColor:f})=>s.jsxs(h.Group,{x:i.position.x,y:i.position.y,children:[s.jsx(h.Rect,{width:i.config.width,height:i.config.height,fill:f+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),s.jsx(h.Text,{text:i.config.label,x:0,y:0,width:i.config.width,height:i.config.height,fontSize:24,fontStyle:"bold",fill:"#ffffff",align:"center",verticalAlign:"middle"})]}));q.displayName="ViewerStage";const X=({config:i,configUrl:f,floorId:n,onFloorChange:v,reservedSeats:x=[],unavailableSeats:p=[],onSeatSelect:d,onSeatDeselect:u,onSelectionChange:c,colorOverrides:l,showTooltip:G=!0,zoomEnabled:A=!0,className:k="",onConfigLoad:N,onError:R})=>{const L=t.useRef(null),[S,D]=t.useState(new Set),[M,O]=t.useState(1),[y,W]=t.useState({x:0,y:0}),{config:_,loading:U,error:b}=$(f),r=i||_,E=t.useMemo(()=>r?{...r.colors,...l}:{...z,...l},[r,l]),F=t.useMemo(()=>{if(!r)return[];let e=r.seats.filter(a=>a.state!=="hidden");return n&&(e=e.filter(a=>a.floorId===n||!a.floorId&&n==="floor_default")),e},[r,n]),Y=t.useMemo(()=>r?.stages?n?r.stages.filter(e=>e.floorId===n||!e.floorId&&n==="floor_default"):r.stages:[],[r,n]),w=t.useMemo(()=>{const e=new Set(x),a=new Set(p);return{reserved:e,unavailable:a}},[x,p]),T=t.useCallback(e=>{const a=e.id,o=e.seatNumber||"";return w.unavailable.has(a)||w.unavailable.has(o)?"unavailable":w.reserved.has(a)||w.reserved.has(o)?"reserved":S.has(a)?"selected":e.state},[w,S]);t.useEffect(()=>{r&&N&&N(r)},[r,N]),t.useEffect(()=>{b&&R&&R(b)},[b,R]);const B=t.useCallback(e=>{const a=T(e);if(a!=="available"&&a!=="selected")return;const o=S.has(e.id);D(g=>{const m=new Set(g);return o?m.delete(e.id):m.add(e.id),m}),o?u?.(e):(d?.(e),d||console.log("Seat selected:",e))},[T,S,d,u]),j=t.useMemo(()=>r?F.filter(e=>S.has(e.id)):[],[F,S]);t.useEffect(()=>{c?.(j)},[j,c]);const H=t.useCallback(e=>{if(!A)return;e.evt.preventDefault();const a=L.current;if(!a)return;const o=M,g=a.getPointerPosition();if(!g)return;const m={x:(g.x-y.x)/o,y:(g.y-y.y)/o},K=e.evt.deltaY>0?-1:1,P=1.05;let C=K>0?o*P:o/P;C=Math.max(.5,Math.min(5,C)),O(C),W({x:g.x-m.x*C,y:g.y-m.y*C})},[A,M,y]);return U?s.jsx("div",{className:`flex items-center justify-center h-full ${k}`,children:s.jsx("p",{children:"Loading seat map..."})}):b?s.jsx("div",{className:`flex items-center justify-center h-full ${k}`,children:s.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",b.message]})}):r?s.jsxs("div",{className:`relative ${k}`,children:[s.jsxs(h.Stage,{ref:L,width:r.canvas.width,height:r.canvas.height,scaleX:M,scaleY:M,x:y.x,y:y.y,onWheel:H,style:{backgroundColor:r.canvas.backgroundColor},children:[s.jsx(h.Layer,{listening:!1,children:Y.map(e=>s.jsx(q,{stage:e,stageColor:E.stageColor},e.id))}),s.jsx(h.Layer,{children:F.map(e=>s.jsx(V,{seat:e,state:T(e),colors:E,onClick:B},e.id))})]}),j.length>0&&s.jsxs("div",{className:"absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg",children:[s.jsxs("h3",{className:"font-semibold mb-2",children:["Selected Seats (",j.length,")"]}),s.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:j.map(e=>s.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${E.currency} ${e.price.toFixed(2)}`]},e.id))})]})]}):s.jsx("div",{className:`flex items-center justify-center h-full ${k}`,children:s.jsx("p",{children:"No configuration provided"})})};exports.DEFAULT_COLORS=z;exports.SeatMapViewer=X;exports.useConfigFetcher=$;
package/dist/index.mjs CHANGED
@@ -1,92 +1,95 @@
1
- import { jsx as i, jsxs as p } from "react/jsx-runtime";
2
- import { useState as w, useEffect as $, useRef as J, useMemo as M, useCallback as j, memo as W } from "react";
3
- import { Stage as Q, Layer as L, Group as Z, Rect as I, Text as D, Circle as ee } from "react-konva";
4
- function te(t) {
5
- const [o, l] = w(null), [u, d] = w(!1), [g, f] = w(null), h = async () => {
1
+ import { jsx as s, jsxs as u } from "react/jsx-runtime";
2
+ import { useState as v, useEffect as $, useRef as Q, useMemo as N, useCallback as j, memo as D } from "react";
3
+ import { Stage as Z, Layer as W, Group as ee, Rect as Y, Text as te, Circle as ie } from "react-konva";
4
+ function re(t) {
5
+ const [c, r] = v(null), [S, g] = v(!1), [y, d] = v(null), f = async () => {
6
6
  if (t) {
7
- d(!0), f(null);
7
+ g(!0), d(null);
8
8
  try {
9
- const n = await fetch(t);
10
- if (!n.ok)
11
- throw new Error(`Failed to fetch config: ${n.statusText}`);
12
- const a = await n.json();
13
- l(a);
14
- } catch (n) {
15
- const a = n instanceof Error ? n : new Error("Unknown error occurred");
16
- f(a), console.error("Failed to fetch seat map config:", a);
9
+ const a = await fetch(t);
10
+ if (!a.ok)
11
+ throw new Error(`Failed to fetch config: ${a.statusText}`);
12
+ const o = await a.json();
13
+ r(o);
14
+ } catch (a) {
15
+ const o = a instanceof Error ? a : new Error("Unknown error occurred");
16
+ d(o), console.error("Failed to fetch seat map config:", o);
17
17
  } finally {
18
- d(!1);
18
+ g(!1);
19
19
  }
20
20
  }
21
21
  };
22
22
  return $(() => {
23
- h();
23
+ f();
24
24
  }, [t]), {
25
- config: o,
26
- loading: u,
27
- error: g,
28
- refetch: h
25
+ config: c,
26
+ loading: S,
27
+ error: y,
28
+ refetch: f
29
29
  };
30
30
  }
31
- const re = {
31
+ const ne = {
32
32
  canvasBackground: "#1a1a1a",
33
33
  stageColor: "#808080",
34
34
  seatAvailable: "#2C2B30",
35
35
  seatReserved: "#FCEA00",
36
36
  seatSelected: "#3A7DE5",
37
37
  seatUnavailable: "#6b7280",
38
+ seatHidden: "#4a4a4a",
38
39
  gridLines: "#404040",
39
40
  currency: "KD"
40
- }, Y = W(({ seat: t, state: o, colors: l, onClick: u }) => {
41
- const f = {
42
- available: l.seatAvailable,
43
- reserved: l.seatReserved,
44
- selected: l.seatSelected,
45
- unavailable: l.seatUnavailable
46
- }[o], h = o === "available" || o === "selected", n = j(() => {
47
- h && u(t);
48
- }, [t, u, h]), a = {
41
+ }, _ = D(({ seat: t, state: c, colors: r, onClick: S }) => {
42
+ const d = {
43
+ available: r.seatAvailable,
44
+ reserved: r.seatReserved,
45
+ selected: r.seatSelected,
46
+ unavailable: r.seatUnavailable,
47
+ hidden: r.seatHidden
48
+ // Hidden seats are filtered out, but included for type safety
49
+ }[c], f = c === "available" || c === "selected", a = j(() => {
50
+ f && S(t);
51
+ }, [t, S, f]), o = {
49
52
  x: t.position.x,
50
53
  y: t.position.y,
51
- fill: f,
54
+ fill: d,
52
55
  stroke: "#ffffff",
53
56
  strokeWidth: 1,
54
- onClick: n,
55
- onTap: n
57
+ onClick: a,
58
+ onTap: a
56
59
  };
57
- return t.shape === "circle" ? /* @__PURE__ */ i(
58
- ee,
60
+ return t.shape === "circle" ? /* @__PURE__ */ s(
61
+ ie,
59
62
  {
60
- ...a,
63
+ ...o,
61
64
  radius: 12
62
65
  }
63
- ) : /* @__PURE__ */ i(
64
- I,
66
+ ) : /* @__PURE__ */ s(
67
+ Y,
65
68
  {
66
- ...a,
67
- width: 12 * 2,
68
- height: 12 * 2,
69
+ ...o,
70
+ width: 24,
71
+ height: 24,
69
72
  offsetX: 12,
70
73
  offsetY: 12,
71
74
  cornerRadius: t.shape === "square" ? 0 : 4
72
75
  }
73
76
  );
74
77
  });
75
- Y.displayName = "ViewerSeat";
76
- const B = W(({ stage: t, stageColor: o }) => /* @__PURE__ */ p(Z, { x: t.position.x, y: t.position.y, children: [
77
- /* @__PURE__ */ i(
78
- I,
78
+ _.displayName = "ViewerSeat";
79
+ const B = D(({ stage: t, stageColor: c }) => /* @__PURE__ */ u(ee, { x: t.position.x, y: t.position.y, children: [
80
+ /* @__PURE__ */ s(
81
+ Y,
79
82
  {
80
83
  width: t.config.width,
81
84
  height: t.config.height,
82
- fill: o + "80",
85
+ fill: c + "80",
83
86
  stroke: "#ffffff",
84
87
  strokeWidth: 2,
85
88
  cornerRadius: 10
86
89
  }
87
90
  ),
88
- /* @__PURE__ */ i(
89
- D,
91
+ /* @__PURE__ */ s(
92
+ te,
90
93
  {
91
94
  text: t.config.label,
92
95
  x: 0,
@@ -102,85 +105,94 @@ const B = W(({ stage: t, stageColor: o }) => /* @__PURE__ */ p(Z, { x: t.positio
102
105
  )
103
106
  ] }));
104
107
  B.displayName = "ViewerStage";
105
- const ae = ({
108
+ const ce = ({
106
109
  config: t,
107
- configUrl: o,
108
- reservedSeats: l = [],
109
- unavailableSeats: u = [],
110
+ configUrl: c,
111
+ floorId: r,
112
+ onFloorChange: S,
113
+ reservedSeats: g = [],
114
+ unavailableSeats: y = [],
110
115
  onSeatSelect: d,
111
- onSeatDeselect: g,
112
- onSelectionChange: f,
113
- colorOverrides: h,
114
- showTooltip: n = !0,
115
- zoomEnabled: a = !0,
116
- className: N = "",
116
+ onSeatDeselect: f,
117
+ onSelectionChange: a,
118
+ colorOverrides: o,
119
+ showTooltip: se = !0,
120
+ zoomEnabled: T = !0,
121
+ className: R = "",
117
122
  onConfigLoad: z,
118
123
  onError: A
119
124
  }) => {
120
- var T;
121
- const P = J(null), [v, U] = w(/* @__PURE__ */ new Set()), [R, X] = w(1), [x, q] = w({ x: 0, y: 0 }), { config: G, loading: K, error: b } = te(o), r = t || G, E = M(
122
- () => r ? { ...r.colors, ...h } : { ...re, ...h },
123
- [r, h]
124
- ), S = M(() => {
125
- const e = new Set(l), s = new Set(u);
126
- return { reserved: e, unavailable: s };
127
- }, [l, u]), F = j((e) => {
128
- const s = e.id, c = e.seatNumber || "";
129
- return S.unavailable.has(s) || S.unavailable.has(c) ? "unavailable" : S.reserved.has(s) || S.reserved.has(c) ? "reserved" : v.has(s) ? "selected" : e.state;
130
- }, [S, v]);
125
+ const V = Q(null), [m, H] = v(/* @__PURE__ */ new Set()), [F, U] = v(1), [w, X] = v({ x: 0, y: 0 }), { config: q, loading: G, error: x } = re(c), i = t || q, E = N(
126
+ () => i ? { ...i.colors, ...o } : { ...ne, ...o },
127
+ [i, o]
128
+ ), M = N(() => {
129
+ if (!i) return [];
130
+ let e = i.seats.filter((n) => n.state !== "hidden");
131
+ return r && (e = e.filter(
132
+ (n) => n.floorId === r || !n.floorId && r === "floor_default"
133
+ )), e;
134
+ }, [i, r]), K = N(() => i?.stages ? r ? i.stages.filter(
135
+ (e) => e.floorId === r || !e.floorId && r === "floor_default"
136
+ ) : i.stages : [], [i, r]), b = N(() => {
137
+ const e = new Set(g), n = new Set(y);
138
+ return { reserved: e, unavailable: n };
139
+ }, [g, y]), P = j((e) => {
140
+ const n = e.id, l = e.seatNumber || "";
141
+ return b.unavailable.has(n) || b.unavailable.has(l) ? "unavailable" : b.reserved.has(n) || b.reserved.has(l) ? "reserved" : m.has(n) ? "selected" : e.state;
142
+ }, [b, m]);
131
143
  $(() => {
132
- r && z && z(r);
133
- }, [r, z]), $(() => {
134
- b && A && A(b);
135
- }, [b, A]);
144
+ i && z && z(i);
145
+ }, [i, z]), $(() => {
146
+ x && A && A(x);
147
+ }, [x, A]);
136
148
  const O = j((e) => {
137
- const s = F(e);
138
- if (s !== "available" && s !== "selected")
149
+ const n = P(e);
150
+ if (n !== "available" && n !== "selected")
139
151
  return;
140
- const c = v.has(e.id);
141
- U((m) => {
142
- const y = new Set(m);
143
- return c ? y.delete(e.id) : y.add(e.id), y;
144
- }), c ? g == null || g(e) : (d == null || d(e), d || console.log("Seat selected:", e));
145
- }, [F, v, d, g]), k = M(() => r ? r.seats.filter((e) => v.has(e.id)) : [], [r, v]);
152
+ const l = m.has(e.id);
153
+ H((h) => {
154
+ const p = new Set(h);
155
+ return l ? p.delete(e.id) : p.add(e.id), p;
156
+ }), l ? f?.(e) : (d?.(e), d || console.log("Seat selected:", e));
157
+ }, [P, m, d, f]), C = N(() => i ? M.filter((e) => m.has(e.id)) : [], [M, m]);
146
158
  $(() => {
147
- f == null || f(k);
148
- }, [k, f]);
149
- const _ = j((e) => {
150
- if (!a) return;
159
+ a?.(C);
160
+ }, [C, a]);
161
+ const I = j((e) => {
162
+ if (!T) return;
151
163
  e.evt.preventDefault();
152
- const s = P.current;
153
- if (!s) return;
154
- const c = R, m = s.getPointerPosition();
155
- if (!m) return;
156
- const y = {
157
- x: (m.x - x.x) / c,
158
- y: (m.y - x.y) / c
159
- }, H = e.evt.deltaY > 0 ? -1 : 1, V = 1.05;
160
- let C = H > 0 ? c * V : c / V;
161
- C = Math.max(0.5, Math.min(5, C)), X(C), q({
162
- x: m.x - y.x * C,
163
- y: m.y - y.y * C
164
+ const n = V.current;
165
+ if (!n) return;
166
+ const l = F, h = n.getPointerPosition();
167
+ if (!h) return;
168
+ const p = {
169
+ x: (h.x - w.x) / l,
170
+ y: (h.y - w.y) / l
171
+ }, J = e.evt.deltaY > 0 ? -1 : 1, L = 1.05;
172
+ let k = J > 0 ? l * L : l / L;
173
+ k = Math.max(0.5, Math.min(5, k)), U(k), X({
174
+ x: h.x - p.x * k,
175
+ y: h.y - p.y * k
164
176
  });
165
- }, [a, R, x]);
166
- return K ? /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${N}`, children: /* @__PURE__ */ i("p", { children: "Loading seat map..." }) }) : b ? /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${N}`, children: /* @__PURE__ */ p("p", { className: "text-red-500", children: [
177
+ }, [T, F, w]);
178
+ return G ? /* @__PURE__ */ s("div", { className: `flex items-center justify-center h-full ${R}`, children: /* @__PURE__ */ s("p", { children: "Loading seat map..." }) }) : x ? /* @__PURE__ */ s("div", { className: `flex items-center justify-center h-full ${R}`, children: /* @__PURE__ */ u("p", { className: "text-red-500", children: [
167
179
  "Error loading seat map: ",
168
- b.message
169
- ] }) }) : r ? /* @__PURE__ */ p("div", { className: `relative ${N}`, children: [
170
- /* @__PURE__ */ p(
171
- Q,
180
+ x.message
181
+ ] }) }) : i ? /* @__PURE__ */ u("div", { className: `relative ${R}`, children: [
182
+ /* @__PURE__ */ u(
183
+ Z,
172
184
  {
173
- ref: P,
174
- width: r.canvas.width,
175
- height: r.canvas.height,
176
- scaleX: R,
177
- scaleY: R,
178
- x: x.x,
179
- y: x.y,
180
- onWheel: _,
181
- style: { backgroundColor: r.canvas.backgroundColor },
185
+ ref: V,
186
+ width: i.canvas.width,
187
+ height: i.canvas.height,
188
+ scaleX: F,
189
+ scaleY: F,
190
+ x: w.x,
191
+ y: w.y,
192
+ onWheel: I,
193
+ style: { backgroundColor: i.canvas.backgroundColor },
182
194
  children: [
183
- /* @__PURE__ */ i(L, { listening: !1, children: (T = r.stages) == null ? void 0 : T.map((e) => /* @__PURE__ */ i(
195
+ /* @__PURE__ */ s(W, { listening: !1, children: K.map((e) => /* @__PURE__ */ s(
184
196
  B,
185
197
  {
186
198
  stage: e,
@@ -188,11 +200,11 @@ const ae = ({
188
200
  },
189
201
  e.id
190
202
  )) }),
191
- /* @__PURE__ */ i(L, { children: r.seats.map((e) => /* @__PURE__ */ i(
192
- Y,
203
+ /* @__PURE__ */ s(W, { children: M.map((e) => /* @__PURE__ */ s(
204
+ _,
193
205
  {
194
206
  seat: e,
195
- state: F(e),
207
+ state: P(e),
196
208
  colors: E,
197
209
  onClick: O
198
210
  },
@@ -201,21 +213,21 @@ const ae = ({
201
213
  ]
202
214
  }
203
215
  ),
204
- k.length > 0 && /* @__PURE__ */ p("div", { className: "absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg", children: [
205
- /* @__PURE__ */ p("h3", { className: "font-semibold mb-2", children: [
216
+ C.length > 0 && /* @__PURE__ */ u("div", { className: "absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg", children: [
217
+ /* @__PURE__ */ u("h3", { className: "font-semibold mb-2", children: [
206
218
  "Selected Seats (",
207
- k.length,
219
+ C.length,
208
220
  ")"
209
221
  ] }),
210
- /* @__PURE__ */ i("div", { className: "max-h-48 overflow-y-auto space-y-1", children: k.map((e) => /* @__PURE__ */ p("div", { className: "text-sm", children: [
222
+ /* @__PURE__ */ s("div", { className: "max-h-48 overflow-y-auto space-y-1", children: C.map((e) => /* @__PURE__ */ u("div", { className: "text-sm", children: [
211
223
  e.seatNumber,
212
224
  e.price && ` - ${E.currency} ${e.price.toFixed(2)}`
213
225
  ] }, e.id)) })
214
226
  ] })
215
- ] }) : /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${N}`, children: /* @__PURE__ */ i("p", { children: "No configuration provided" }) });
227
+ ] }) : /* @__PURE__ */ s("div", { className: `flex items-center justify-center h-full ${R}`, children: /* @__PURE__ */ s("p", { children: "No configuration provided" }) });
216
228
  };
217
229
  export {
218
- re as DEFAULT_COLORS,
219
- ae as SeatMapViewer,
220
- te as useConfigFetcher
230
+ ne as DEFAULT_COLORS,
231
+ ce as SeatMapViewer,
232
+ re as useConfigFetcher
221
233
  };
@@ -1,4 +1,4 @@
1
- export type SeatState = "available" | "reserved" | "selected" | "unavailable";
1
+ export type SeatState = "available" | "reserved" | "selected" | "unavailable" | "hidden";
2
2
  export type SeatShape = "circle" | "square" | "rounded-square";
3
3
  export interface SeatData {
4
4
  id: string;
@@ -23,6 +23,7 @@ export interface SerializedSeat {
23
23
  columnLabel?: string;
24
24
  seatNumber?: string;
25
25
  price?: number;
26
+ floorId?: string;
26
27
  }
27
28
  export interface SerializedSection {
28
29
  id: string;
@@ -41,6 +42,7 @@ export interface SerializedStage {
41
42
  y: number;
42
43
  };
43
44
  config: any;
45
+ floorId?: string;
44
46
  }
45
47
  export interface ColorSettings {
46
48
  canvasBackground: string;
@@ -49,6 +51,7 @@ export interface ColorSettings {
49
51
  seatReserved: string;
50
52
  seatSelected: string;
51
53
  seatUnavailable: string;
54
+ seatHidden: string;
52
55
  gridLines: string;
53
56
  currency: string;
54
57
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@zonetrix/viewer",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
+ "type": "module",
4
5
  "description": "Lightweight React component for rendering interactive seat maps",
5
6
  "main": "./dist/index.js",
6
7
  "module": "./dist/index.mjs",
@@ -34,19 +35,19 @@
34
35
  "author": "",
35
36
  "license": "MIT",
36
37
  "peerDependencies": {
38
+ "konva": "^9.0.0",
37
39
  "react": "^18.0.0 || ^19.0.0",
38
40
  "react-dom": "^18.0.0 || ^19.0.0",
39
- "konva": "^9.0.0",
40
41
  "react-konva": "^18.0.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@types/react": "^19.2.0",
44
45
  "@types/react-dom": "^19.2.0",
45
- "@vitejs/plugin-react-swc": "^4.0.0",
46
+ "@vitejs/plugin-react-swc": "^4.2.2",
46
47
  "konva": "^9.3.18",
47
48
  "react-konva": "^18.2.10",
48
49
  "typescript": "^5.8.3",
49
- "vite": "^5.4.19",
50
+ "vite": "^7.3.0",
50
51
  "vite-plugin-dts": "^3.7.0"
51
52
  }
52
53
  }