@zonetrix/viewer 2.2.0 → 2.3.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
@@ -49,7 +49,8 @@ import type {
49
49
  SeatShape,
50
50
  SeatData,
51
51
  SeatMapConfig,
52
- ColorSettings
52
+ ColorSettings,
53
+ FloorConfig
53
54
  } from '@zonetrix/viewer';
54
55
 
55
56
  // Hooks
@@ -103,7 +104,7 @@ function BookingApp() {
103
104
  |------|------|----------|-------------|
104
105
  | `config` | `SeatMapConfig` | No* | Seat map configuration object |
105
106
  | `configUrl` | `string` | No* | URL to fetch configuration from |
106
- | `floorId` | `string` | No | Filter seats/stages by floor ID |
107
+ | `floorId` | `string` | No | Filter seats/stages by floor ID (controlled mode) |
107
108
  | `onFloorChange` | `(floorId: string) => void` | No | Callback when floor changes |
108
109
  | `reservedSeats` | `string[]` | No | Array of seat IDs/numbers to mark as reserved |
109
110
  | `unavailableSeats` | `string[]` | No | Array of seat IDs/numbers to mark as unavailable |
@@ -116,6 +117,11 @@ function BookingApp() {
116
117
  | `className` | `string` | No | Custom CSS class for the container |
117
118
  | `onConfigLoad` | `(config: SeatMapConfig) => void` | No | Callback when config is loaded |
118
119
  | `onError` | `(error: Error) => void` | No | Callback when an error occurs |
120
+ | `showFloorSelector` | `boolean` | No | Show/hide built-in floor selector (default: true when floors > 1) |
121
+ | `floorSelectorPosition` | `string` | No | Position: 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' |
122
+ | `floorSelectorClassName` | `string` | No | Custom CSS class for floor selector |
123
+ | `showAllFloorsOption` | `boolean` | No | Show "All" button in floor selector (default: true) |
124
+ | `allFloorsLabel` | `string` | No | Custom label for "All" button (default: 'All') |
119
125
 
120
126
  *Note: Either `config` or `configUrl` must be provided.
121
127
 
@@ -257,19 +263,43 @@ function MobileOptimized() {
257
263
  }
258
264
  ```
259
265
 
260
- ### 6. Multi-floor Venue
266
+ ### 6. Multi-floor Venue (Built-in Floor Selector)
267
+
268
+ The viewer includes a built-in floor selector that automatically appears when your config has multiple floors.
261
269
 
262
270
  ```tsx
263
- import { useState } from 'react';
264
271
  import { SeatMapViewer } from '@zonetrix/viewer';
265
272
 
266
273
  function MultiFloorVenue() {
267
- const [currentFloor, setCurrentFloor] = useState('floor_1');
274
+ return (
275
+ <SeatMapViewer
276
+ config={venueConfig}
277
+ onSeatSelect={(seat) => handleSelection(seat)}
278
+ // Floor selector auto-shows when config.floors.length > 1
279
+ // Customize position and labels:
280
+ floorSelectorPosition="top-right"
281
+ allFloorsLabel="All Floors"
282
+ />
283
+ );
284
+ }
285
+ ```
286
+
287
+ #### Custom Floor Selector (Controlled Mode)
288
+
289
+ For full control over the floor selector UI, use controlled mode:
290
+
291
+ ```tsx
292
+ import { useState } from 'react';
293
+ import { SeatMapViewer } from '@zonetrix/viewer';
294
+
295
+ function CustomFloorSelector() {
296
+ const [currentFloor, setCurrentFloor] = useState<string | null>(null);
268
297
 
269
298
  return (
270
299
  <div>
271
- {/* Floor selector */}
300
+ {/* Your custom floor selector */}
272
301
  <div className="floor-tabs">
302
+ <button onClick={() => setCurrentFloor(null)}>All</button>
273
303
  {venueConfig.floors?.map((floor) => (
274
304
  <button
275
305
  key={floor.id}
@@ -283,7 +313,8 @@ function MultiFloorVenue() {
283
313
 
284
314
  <SeatMapViewer
285
315
  config={venueConfig}
286
- floorId={currentFloor}
316
+ showFloorSelector={false} // Hide built-in selector
317
+ floorId={currentFloor || undefined}
287
318
  onFloorChange={setCurrentFloor}
288
319
  onSeatSelect={(seat) => handleSelection(seat)}
289
320
  />
@@ -364,7 +395,11 @@ The viewer accepts a `SeatMapConfig` object. You can create these configurations
364
395
  }
365
396
  ],
366
397
  "sections": [],
367
- "stages": []
398
+ "stages": [],
399
+ "floors": [
400
+ { "id": "floor_1", "name": "Ground Floor", "order": 0 },
401
+ { "id": "floor_2", "name": "First Floor", "order": 1 }
402
+ ]
368
403
  }
369
404
  ```
370
405
 
@@ -416,6 +451,17 @@ interface ColorSettings {
416
451
  }
417
452
  ```
418
453
 
454
+ ### FloorConfig
455
+
456
+ ```typescript
457
+ interface FloorConfig {
458
+ id: string; // Unique identifier (e.g., "floor_1")
459
+ name: string; // Display name (e.g., "Ground Floor")
460
+ order: number; // Sort order (0 = first)
461
+ color?: string; // Optional floor color
462
+ }
463
+ ```
464
+
419
465
  ## Seat States Explained
420
466
 
421
467
  | State | Description | User Can Select? | Visual |
@@ -21,5 +21,7 @@ export interface SeatMapViewerProps {
21
21
  floorSelectorClassName?: string;
22
22
  showAllFloorsOption?: boolean;
23
23
  allFloorsLabel?: string;
24
+ fitToView?: boolean;
25
+ fitPadding?: number;
24
26
  }
25
27
  export declare const SeatMapViewer: React.FC<SeatMapViewerProps>;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),t=require("react"),S=require("react-konva");function B(o){const[c,i]=t.useState(null),[g,x]=t.useState(!1),[m,f]=t.useState(null),u=async()=>{if(o){x(!0),f(null);try{const a=await fetch(o);if(!a.ok)throw new Error(`Failed to fetch config: ${a.statusText}`);const l=await a.json();i(l)}catch(a){const l=a instanceof Error?a:new Error("Unknown error occurred");f(l),console.error("Failed to fetch seat map config:",l)}finally{x(!1)}}};return t.useEffect(()=>{u()},[o]),{config:c,loading:g,error:m,refetch:u}}const O={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",seatHidden:"#4a4a4a",gridLines:"#404040",currency:"KD"},W=t.memo(({seat:o,state:c,colors:i,onClick:g})=>{const f={available:i.seatAvailable,reserved:i.seatReserved,selected:i.seatSelected,unavailable:i.seatUnavailable,hidden:i.seatHidden}[c],u=c==="available"||c==="selected",a=t.useCallback(()=>{u&&g(o)},[o,g,u]),l={x:o.position.x,y:o.position.y,fill:f,stroke:"#ffffff",strokeWidth:1,onClick:a,onTap:a};return o.shape==="circle"?s.jsx(S.Circle,{...l,radius:12}):s.jsx(S.Rect,{...l,width:24,height:24,offsetX:12,offsetY:12,cornerRadius:o.shape==="square"?0:4})});W.displayName="ViewerSeat";const _=t.memo(({stage:o,stageColor:c})=>s.jsxs(S.Group,{x:o.position.x,y:o.position.y,children:[s.jsx(S.Rect,{width:o.config.width,height:o.config.height,fill:c+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),s.jsx(S.Text,{text:o.config.label,x:0,y:0,width:o.config.width,height:o.config.height,fontSize:24,fontStyle:"bold",fill:"#ffffff",align:"center",verticalAlign:"middle"})]}));_.displayName="ViewerStage";const q=t.memo(({floors:o,currentFloorId:c,onFloorChange:i,showAllOption:g,allLabel:x,position:m,className:f})=>{const u=t.useMemo(()=>[...o].sort((d,w)=>d.order-w.order),[o]),l={position:"absolute",display:"flex",alignItems:"center",gap:"8px",padding:"8px 12px",backgroundColor:"rgba(26, 26, 26, 0.95)",borderRadius:"8px",margin:"12px",zIndex:10,...{"top-left":{top:0,left:0},"top-right":{top:0,right:0},"bottom-left":{bottom:0,left:0},"bottom-right":{bottom:0,right:0}}[m]},E={padding:"6px 14px",fontSize:"14px",fontWeight:500,border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",transition:"all 0.2s ease"},j={...E,backgroundColor:"#3A7DE5",borderColor:"#3A7DE5"};return s.jsxs("div",{className:f,style:l,children:[g&&s.jsx("button",{type:"button",onClick:()=>i(null),style:c===null?j:E,children:x}),u.map(d=>s.jsx("button",{type:"button",onClick:()=>i(d.id),style:c===d.id?j:E,children:d.name},d.id))]})});q.displayName="FloorSelectorBar";const le=({config:o,configUrl:c,floorId:i,onFloorChange:g,reservedSeats:x=[],unavailableSeats:m=[],onSeatSelect:f,onSeatDeselect:u,onSelectionChange:a,colorOverrides:l,showTooltip:E=!0,zoomEnabled:j=!0,className:d="",onConfigLoad:w,onError:A,showFloorSelector:$,floorSelectorPosition:U="top-left",floorSelectorClassName:Y,showAllFloorsOption:H=!0,allFloorsLabel:K="All"})=>{const D=t.useRef(null),[y,X]=t.useState(new Set),[F,G]=t.useState(1),[C,J]=t.useState({x:0,y:0}),[Q,Z]=t.useState(null),{config:ee,loading:te,error:k}=B(c),r=o||ee,I=i!==void 0,p=I?i||null:Q,se=t.useCallback(e=>{I||Z(e),g?.(e)},[I,g]),T=r?.floors||[],oe=$!==void 0?$:T.length>1,z=t.useMemo(()=>r?{...r.colors,...l}:{...O,...l},[r,l]),L=t.useMemo(()=>{if(!r)return[];let e=r.seats.filter(n=>n.state!=="hidden");return p&&(e=e.filter(n=>n.floorId===p||!n.floorId&&p==="floor_default")),e},[r,p]),re=t.useMemo(()=>r?.stages?p?r.stages.filter(e=>e.floorId===p||!e.floorId&&p==="floor_default"):r.stages:[],[r,p]),N=t.useMemo(()=>{const e=new Set(x),n=new Set(m);return{reserved:e,unavailable:n}},[x,m]),P=t.useCallback(e=>{const n=e.id,h=e.seatNumber||"";return N.unavailable.has(n)||N.unavailable.has(h)?"unavailable":N.reserved.has(n)||N.reserved.has(h)?"reserved":y.has(n)?"selected":e.state},[N,y]);t.useEffect(()=>{r&&w&&w(r)},[r,w]),t.useEffect(()=>{k&&A&&A(k)},[k,A]);const ne=t.useCallback(e=>{const n=P(e);if(n!=="available"&&n!=="selected")return;const h=y.has(e.id);X(b=>{const v=new Set(b);return h?v.delete(e.id):v.add(e.id),v}),h?u?.(e):(f?.(e),f||console.log("Seat selected:",e))},[P,y,f,u]),M=t.useMemo(()=>r?L.filter(e=>y.has(e.id)):[],[L,y]);t.useEffect(()=>{a?.(M)},[M,a]);const ie=t.useCallback(e=>{if(!j)return;e.evt.preventDefault();const n=D.current;if(!n)return;const h=F,b=n.getPointerPosition();if(!b)return;const v={x:(b.x-C.x)/h,y:(b.y-C.y)/h},ae=e.evt.deltaY>0?-1:1,V=1.05;let R=ae>0?h*V:h/V;R=Math.max(.5,Math.min(5,R)),G(R),J({x:b.x-v.x*R,y:b.y-v.y*R})},[j,F,C]);return te?s.jsx("div",{className:`flex items-center justify-center h-full ${d}`,children:s.jsx("p",{children:"Loading seat map..."})}):k?s.jsx("div",{className:`flex items-center justify-center h-full ${d}`,children:s.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",k.message]})}):r?s.jsxs("div",{className:`relative ${d}`,children:[oe&&T.length>0&&s.jsx(q,{floors:T,currentFloorId:p,onFloorChange:se,showAllOption:H,allLabel:K,position:U,className:Y}),s.jsxs(S.Stage,{ref:D,width:r.canvas.width,height:r.canvas.height,scaleX:F,scaleY:F,x:C.x,y:C.y,onWheel:ie,style:{backgroundColor:r.canvas.backgroundColor},children:[s.jsx(S.Layer,{listening:!1,children:re.map(e=>s.jsx(_,{stage:e,stageColor:z.stageColor},e.id))}),s.jsx(S.Layer,{children:L.map(e=>s.jsx(W,{seat:e,state:P(e),colors:z,onClick:ne},e.id))})]}),M.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 (",M.length,")"]}),s.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:M.map(e=>s.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${z.currency} ${e.price.toFixed(2)}`]},e.id))})]})]}):s.jsx("div",{className:`flex items-center justify-center h-full ${d}`,children:s.jsx("p",{children:"No configuration provided"})})};exports.DEFAULT_COLORS=O;exports.SeatMapViewer=le;exports.useConfigFetcher=B;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),t=require("react"),v=require("react-konva");function K(i){const[h,d]=t.useState(null),[S,y]=t.useState(!1),[w,x]=t.useState(null),g=async()=>{if(i){y(!0),x(null);try{const f=await fetch(i);if(!f.ok)throw new Error(`Failed to fetch config: ${f.statusText}`);const u=await f.json();d(u)}catch(f){const u=f instanceof Error?f:new Error("Unknown error occurred");x(u),console.error("Failed to fetch seat map config:",u)}finally{y(!1)}}};return t.useEffect(()=>{g()},[i]),{config:h,loading:S,error:w,refetch:g}}const G={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",seatHidden:"#4a4a4a",gridLines:"#404040",currency:"KD"},J=t.memo(({seat:i,state:h,colors:d,onClick:S})=>{const x={available:d.seatAvailable,reserved:d.seatReserved,selected:d.seatSelected,unavailable:d.seatUnavailable,hidden:d.seatHidden}[h],g=h==="available"||h==="selected",f=t.useCallback(()=>{g&&S(i)},[i,S,g]),u={x:i.position.x,y:i.position.y,fill:x,stroke:"#ffffff",strokeWidth:1,onClick:f,onTap:f};return i.shape==="circle"?o.jsx(v.Circle,{...u,radius:12}):o.jsx(v.Rect,{...u,width:24,height:24,offsetX:12,offsetY:12,cornerRadius:i.shape==="square"?0:4})});J.displayName="ViewerSeat";const Q=t.memo(({stage:i,stageColor:h})=>o.jsxs(v.Group,{x:i.position.x,y:i.position.y,children:[o.jsx(v.Rect,{width:i.config.width,height:i.config.height,fill:h+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),o.jsx(v.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 Z=t.memo(({floors:i,currentFloorId:h,onFloorChange:d,showAllOption:S,allLabel:y,position:w,className:x})=>{const g=t.useMemo(()=>[...i].sort((p,I)=>p.order-I.order),[i]),u={position:"absolute",display:"flex",alignItems:"center",gap:"8px",padding:"8px 12px",backgroundColor:"rgba(26, 26, 26, 0.95)",borderRadius:"8px",margin:"12px",zIndex:10,...{"top-left":{top:0,left:0},"top-right":{top:0,right:0},"bottom-left":{bottom:0,left:0},"bottom-right":{bottom:0,right:0}}[w]},Y={padding:"6px 14px",fontSize:"14px",fontWeight:500,border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",transition:"all 0.2s ease"},E={...Y,backgroundColor:"#3A7DE5",borderColor:"#3A7DE5"};return o.jsxs("div",{className:x,style:u,children:[S&&o.jsx("button",{type:"button",onClick:()=>d(null),style:h===null?E:Y,children:y}),g.map(p=>o.jsx("button",{type:"button",onClick:()=>d(p.id),style:h===p.id?E:Y,children:p.name},p.id))]})});Z.displayName="FloorSelectorBar";const Se=({config:i,configUrl:h,floorId:d,onFloorChange:S,reservedSeats:y=[],unavailableSeats:w=[],onSeatSelect:x,onSeatDeselect:g,onSelectionChange:f,colorOverrides:u,showTooltip:Y=!0,zoomEnabled:E=!0,className:p="",onConfigLoad:I,onError:T,showFloorSelector:O,floorSelectorPosition:ee="top-left",floorSelectorClassName:te,showAllFloorsOption:ne=!0,allFloorsLabel:se="All",fitToView:z=!0,fitPadding:W=40})=>{const P=t.useRef(null),[j,oe]=t.useState(new Set),[X,V]=t.useState(1),[F,_]=t.useState({x:0,y:0}),[ie,re]=t.useState(null),[q,U]=t.useState(!1),{config:ae,loading:le,error:N}=K(h),s=i||ae,$=d!==void 0,m=$?d||null:ie,ce=t.useCallback(e=>{$||re(e),S?.(e)},[$,S]),B=s?.floors||[],de=O!==void 0?O:B.length>1,D=t.useMemo(()=>s?{...s.colors,...u}:{...G,...u},[s,u]),k=t.useMemo(()=>{if(!s)return[];let e=s.seats.filter(n=>n.state!=="hidden");return m&&(e=e.filter(n=>n.floorId===m||!n.floorId&&m==="floor_default")),e},[s,m]),L=t.useMemo(()=>s?.stages?m?s.stages.filter(e=>e.floorId===m||!e.floorId&&m==="floor_default"):s.stages:[],[s,m]),b=t.useMemo(()=>{if(!s||k.length===0&&L.length===0)return null;const e=12;let n=1/0,r=1/0,a=-1/0,l=-1/0;return k.forEach(c=>{n=Math.min(n,c.position.x-e),r=Math.min(r,c.position.y-e),a=Math.max(a,c.position.x+e),l=Math.max(l,c.position.y+e)}),L.forEach(c=>{n=Math.min(n,c.position.x),r=Math.min(r,c.position.y),a=Math.max(a,c.position.x+(c.config?.width||200)),l=Math.max(l,c.position.y+(c.config?.height||100))}),{minX:n,minY:r,maxX:a,maxY:l,width:a-n,height:l-r}},[s,k,L]);t.useEffect(()=>{if(!z||q||!s||!b)return;const e=s.canvas.width,n=s.canvas.height,r=e-W*2,a=n-W*2,l=r/b.width,c=a/b.height,M=Math.min(l,c,2),C=b.minX+b.width/2,he=b.minY+b.height/2,pe=e/2,xe=n/2,ge=pe-C*M,me=xe-he*M;V(M),_({x:ge,y:me}),U(!0)},[z,q,s,b,W]),t.useEffect(()=>{z&&U(!1)},[m,z]);const R=t.useMemo(()=>{const e=new Set(y),n=new Set(w);return{reserved:e,unavailable:n}},[y,w]),H=t.useCallback(e=>{const n=e.id,r=e.seatNumber||"";return R.unavailable.has(n)||R.unavailable.has(r)?"unavailable":R.reserved.has(n)||R.reserved.has(r)?"reserved":j.has(n)?"selected":e.state},[R,j]);t.useEffect(()=>{s&&I&&I(s)},[s,I]),t.useEffect(()=>{N&&T&&T(N)},[N,T]);const fe=t.useCallback(e=>{const n=H(e);if(n!=="available"&&n!=="selected")return;const r=j.has(e.id);oe(a=>{const l=new Set(a);return r?l.delete(e.id):l.add(e.id),l}),r?g?.(e):(x?.(e),x||console.log("Seat selected:",e))},[H,j,x,g]),A=t.useMemo(()=>s?k.filter(e=>j.has(e.id)):[],[k,j]);t.useEffect(()=>{f?.(A)},[A,f]);const ue=t.useCallback(e=>{if(!E)return;e.evt.preventDefault();const n=P.current;if(!n)return;const r=X,a=n.getPointerPosition();if(!a)return;const l={x:(a.x-F.x)/r,y:(a.y-F.y)/r},c=e.evt.deltaY>0?-1:1,M=1.05;let C=c>0?r*M:r/M;C=Math.max(.5,Math.min(5,C)),V(C),_({x:a.x-l.x*C,y:a.y-l.y*C})},[E,X,F]);return le?o.jsx("div",{className:`flex items-center justify-center h-full ${p}`,children:o.jsx("p",{children:"Loading seat map..."})}):N?o.jsx("div",{className:`flex items-center justify-center h-full ${p}`,children:o.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",N.message]})}):s?o.jsxs("div",{className:`relative ${p}`,children:[de&&B.length>0&&o.jsx(Z,{floors:B,currentFloorId:m,onFloorChange:ce,showAllOption:ne,allLabel:se,position:ee,className:te}),o.jsxs(v.Stage,{ref:P,width:s.canvas.width,height:s.canvas.height,scaleX:X,scaleY:X,x:F.x,y:F.y,onWheel:ue,style:{backgroundColor:s.canvas.backgroundColor},children:[o.jsx(v.Layer,{listening:!1,children:L.map(e=>o.jsx(Q,{stage:e,stageColor:D.stageColor},e.id))}),o.jsx(v.Layer,{children:k.map(e=>o.jsx(J,{seat:e,state:H(e),colors:D,onClick:fe},e.id))})]}),A.length>0&&o.jsxs("div",{className:"absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg",children:[o.jsxs("h3",{className:"font-semibold mb-2",children:["Selected Seats (",A.length,")"]}),o.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:A.map(e=>o.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${D.currency} ${e.price.toFixed(2)}`]},e.id))})]})]}):o.jsx("div",{className:`flex items-center justify-center h-full ${p}`,children:o.jsx("p",{children:"No configuration provided"})})};exports.DEFAULT_COLORS=G;exports.SeatMapViewer=Se;exports.useConfigFetcher=K;
package/dist/index.mjs CHANGED
@@ -1,34 +1,34 @@
1
- import { jsx as r, jsxs as m } from "react/jsx-runtime";
2
- import { useState as y, useEffect as $, useRef as ue, useCallback as z, useMemo as w, memo as V } from "react";
3
- import { Stage as he, Layer as H, Group as pe, Rect as O, Text as ge, Circle as me } from "react-konva";
4
- function be(t) {
5
- const [a, i] = y(null), [h, g] = y(!1), [b, d] = y(null), f = async () => {
6
- if (t) {
7
- g(!0), d(null);
1
+ import { jsx as r, jsxs as v } from "react/jsx-runtime";
2
+ import { useState as S, useEffect as F, useRef as we, useCallback as W, useMemo as k, memo as O } from "react";
3
+ import { Stage as Ce, Layer as Z, Group as ke, Rect as ee, Text as Ie, Circle as Me } from "react-konva";
4
+ function Ne(i) {
5
+ const [h, c] = S(null), [y, x] = S(!1), [w, p] = S(null), g = async () => {
6
+ if (i) {
7
+ x(!0), p(null);
8
8
  try {
9
- const s = await fetch(t);
10
- if (!s.ok)
11
- throw new Error(`Failed to fetch config: ${s.statusText}`);
12
- const l = await s.json();
13
- i(l);
14
- } catch (s) {
15
- const l = s instanceof Error ? s : new Error("Unknown error occurred");
16
- d(l), console.error("Failed to fetch seat map config:", l);
9
+ const d = await fetch(i);
10
+ if (!d.ok)
11
+ throw new Error(`Failed to fetch config: ${d.statusText}`);
12
+ const f = await d.json();
13
+ c(f);
14
+ } catch (d) {
15
+ const f = d instanceof Error ? d : new Error("Unknown error occurred");
16
+ p(f), console.error("Failed to fetch seat map config:", f);
17
17
  } finally {
18
- g(!1);
18
+ x(!1);
19
19
  }
20
20
  }
21
21
  };
22
- return $(() => {
23
- f();
24
- }, [t]), {
25
- config: a,
26
- loading: h,
27
- error: b,
28
- refetch: f
22
+ return F(() => {
23
+ g();
24
+ }, [i]), {
25
+ config: h,
26
+ loading: y,
27
+ error: w,
28
+ refetch: g
29
29
  };
30
30
  }
31
- const Se = {
31
+ const Fe = {
32
32
  canvasBackground: "#1a1a1a",
33
33
  stageColor: "#808080",
34
34
  seatAvailable: "#2C2B30",
@@ -38,64 +38,64 @@ const Se = {
38
38
  seatHidden: "#4a4a4a",
39
39
  gridLines: "#404040",
40
40
  currency: "KD"
41
- }, U = V(({ seat: t, state: a, colors: i, onClick: h }) => {
42
- const d = {
43
- available: i.seatAvailable,
44
- reserved: i.seatReserved,
45
- selected: i.seatSelected,
46
- unavailable: i.seatUnavailable,
47
- hidden: i.seatHidden
41
+ }, te = O(({ seat: i, state: h, colors: c, onClick: y }) => {
42
+ const p = {
43
+ available: c.seatAvailable,
44
+ reserved: c.seatReserved,
45
+ selected: c.seatSelected,
46
+ unavailable: c.seatUnavailable,
47
+ hidden: c.seatHidden
48
48
  // Hidden seats are filtered out, but included for type safety
49
- }[a], f = a === "available" || a === "selected", s = z(() => {
50
- f && h(t);
51
- }, [t, h, f]), l = {
52
- x: t.position.x,
53
- y: t.position.y,
54
- fill: d,
49
+ }[h], g = h === "available" || h === "selected", d = W(() => {
50
+ g && y(i);
51
+ }, [i, y, g]), f = {
52
+ x: i.position.x,
53
+ y: i.position.y,
54
+ fill: p,
55
55
  stroke: "#ffffff",
56
56
  strokeWidth: 1,
57
- onClick: s,
58
- onTap: s
57
+ onClick: d,
58
+ onTap: d
59
59
  };
60
- return t.shape === "circle" ? /* @__PURE__ */ r(
61
- me,
60
+ return i.shape === "circle" ? /* @__PURE__ */ r(
61
+ Me,
62
62
  {
63
- ...l,
63
+ ...f,
64
64
  radius: 12
65
65
  }
66
66
  ) : /* @__PURE__ */ r(
67
- O,
67
+ ee,
68
68
  {
69
- ...l,
69
+ ...f,
70
70
  width: 24,
71
71
  height: 24,
72
72
  offsetX: 12,
73
73
  offsetY: 12,
74
- cornerRadius: t.shape === "square" ? 0 : 4
74
+ cornerRadius: i.shape === "square" ? 0 : 4
75
75
  }
76
76
  );
77
77
  });
78
- U.displayName = "ViewerSeat";
79
- const X = V(({ stage: t, stageColor: a }) => /* @__PURE__ */ m(pe, { x: t.position.x, y: t.position.y, children: [
78
+ te.displayName = "ViewerSeat";
79
+ const ne = O(({ stage: i, stageColor: h }) => /* @__PURE__ */ v(ke, { x: i.position.x, y: i.position.y, children: [
80
80
  /* @__PURE__ */ r(
81
- O,
81
+ ee,
82
82
  {
83
- width: t.config.width,
84
- height: t.config.height,
85
- fill: a + "80",
83
+ width: i.config.width,
84
+ height: i.config.height,
85
+ fill: h + "80",
86
86
  stroke: "#ffffff",
87
87
  strokeWidth: 2,
88
88
  cornerRadius: 10
89
89
  }
90
90
  ),
91
91
  /* @__PURE__ */ r(
92
- ge,
92
+ Ie,
93
93
  {
94
- text: t.config.label,
94
+ text: i.config.label,
95
95
  x: 0,
96
96
  y: 0,
97
- width: t.config.width,
98
- height: t.config.height,
97
+ width: i.config.width,
98
+ height: i.config.height,
99
99
  fontSize: 24,
100
100
  fontStyle: "bold",
101
101
  fill: "#ffffff",
@@ -104,20 +104,20 @@ const X = V(({ stage: t, stageColor: a }) => /* @__PURE__ */ m(pe, { x: t.positi
104
104
  }
105
105
  )
106
106
  ] }));
107
- X.displayName = "ViewerStage";
108
- const q = V(({
109
- floors: t,
110
- currentFloorId: a,
111
- onFloorChange: i,
112
- showAllOption: h,
113
- allLabel: g,
114
- position: b,
115
- className: d
107
+ ne.displayName = "ViewerStage";
108
+ const ie = O(({
109
+ floors: i,
110
+ currentFloorId: h,
111
+ onFloorChange: c,
112
+ showAllOption: y,
113
+ allLabel: x,
114
+ position: w,
115
+ className: p
116
116
  }) => {
117
- const f = w(
118
- () => [...t].sort((c, k) => c.order - k.order),
119
- [t]
120
- ), l = {
117
+ const g = k(
118
+ () => [...i].sort((u, E) => u.order - E.order),
119
+ [i]
120
+ ), f = {
121
121
  position: "absolute",
122
122
  display: "flex",
123
123
  alignItems: "center",
@@ -132,8 +132,8 @@ const q = V(({
132
132
  "top-right": { top: 0, right: 0 },
133
133
  "bottom-left": { bottom: 0, left: 0 },
134
134
  "bottom-right": { bottom: 0, right: 0 }
135
- }[b]
136
- }, E = {
135
+ }[w]
136
+ }, $ = {
137
137
  padding: "6px 14px",
138
138
  fontSize: "14px",
139
139
  fontWeight: 500,
@@ -143,177 +143,197 @@ const q = V(({
143
143
  color: "#fff",
144
144
  cursor: "pointer",
145
145
  transition: "all 0.2s ease"
146
- }, C = {
147
- ...E,
146
+ }, A = {
147
+ ...$,
148
148
  backgroundColor: "#3A7DE5",
149
149
  borderColor: "#3A7DE5"
150
150
  };
151
- return /* @__PURE__ */ m("div", { className: d, style: l, children: [
152
- h && /* @__PURE__ */ r(
151
+ return /* @__PURE__ */ v("div", { className: p, style: f, children: [
152
+ y && /* @__PURE__ */ r(
153
153
  "button",
154
154
  {
155
155
  type: "button",
156
- onClick: () => i(null),
157
- style: a === null ? C : E,
158
- children: g
156
+ onClick: () => c(null),
157
+ style: h === null ? A : $,
158
+ children: x
159
159
  }
160
160
  ),
161
- f.map((c) => /* @__PURE__ */ r(
161
+ g.map((u) => /* @__PURE__ */ r(
162
162
  "button",
163
163
  {
164
164
  type: "button",
165
- onClick: () => i(c.id),
166
- style: a === c.id ? C : E,
167
- children: c.name
165
+ onClick: () => c(u.id),
166
+ style: h === u.id ? A : $,
167
+ children: u.name
168
168
  },
169
- c.id
169
+ u.id
170
170
  ))
171
171
  ] });
172
172
  });
173
- q.displayName = "FloorSelectorBar";
174
- const we = ({
175
- config: t,
176
- configUrl: a,
177
- floorId: i,
178
- onFloorChange: h,
179
- reservedSeats: g = [],
180
- unavailableSeats: b = [],
181
- onSeatSelect: d,
182
- onSeatDeselect: f,
183
- onSelectionChange: s,
184
- colorOverrides: l,
185
- showTooltip: E = !0,
186
- zoomEnabled: C = !0,
187
- className: c = "",
188
- onConfigLoad: k,
189
- onError: j,
173
+ ie.displayName = "FloorSelectorBar";
174
+ const Ye = ({
175
+ config: i,
176
+ configUrl: h,
177
+ floorId: c,
178
+ onFloorChange: y,
179
+ reservedSeats: x = [],
180
+ unavailableSeats: w = [],
181
+ onSeatSelect: p,
182
+ onSeatDeselect: g,
183
+ onSelectionChange: d,
184
+ colorOverrides: f,
185
+ showTooltip: $ = !0,
186
+ zoomEnabled: A = !0,
187
+ className: u = "",
188
+ onConfigLoad: E,
189
+ onError: D,
190
190
  // Floor selector props
191
- showFloorSelector: W,
192
- floorSelectorPosition: G = "top-left",
193
- floorSelectorClassName: K,
194
- showAllFloorsOption: J = !0,
195
- allFloorsLabel: Q = "All"
191
+ showFloorSelector: U,
192
+ floorSelectorPosition: oe = "top-left",
193
+ floorSelectorClassName: re,
194
+ showAllFloorsOption: se = !0,
195
+ allFloorsLabel: ae = "All",
196
+ fitToView: j = !0,
197
+ fitPadding: H = 40
196
198
  }) => {
197
- const _ = ue(null), [v, Z] = y(/* @__PURE__ */ new Set()), [T, ee] = y(1), [N, te] = y({ x: 0, y: 0 }), [oe, re] = y(null), { config: ne, loading: ie, error: I } = be(a), o = t || ne, M = i !== void 0, p = M ? i || null : oe, se = z((e) => {
198
- M || re(e), h?.(e);
199
- }, [M, h]), P = o?.floors || [], le = W !== void 0 ? W : P.length > 1, B = w(
200
- () => o ? { ...o.colors, ...l } : { ...Se, ...l },
201
- [o, l]
202
- ), D = w(() => {
203
- if (!o) return [];
204
- let e = o.seats.filter((n) => n.state !== "hidden");
205
- return p && (e = e.filter(
206
- (n) => n.floorId === p || !n.floorId && p === "floor_default"
199
+ const q = we(null), [I, le] = S(/* @__PURE__ */ new Set()), [B, G] = S(1), [R, K] = S({ x: 0, y: 0 }), [ce, de] = S(null), [J, Q] = S(!1), { config: fe, loading: he, error: Y } = Ne(h), n = i || fe, L = c !== void 0, m = L ? c || null : ce, ue = W((e) => {
200
+ L || de(e), y?.(e);
201
+ }, [L, y]), P = n?.floors || [], pe = U !== void 0 ? U : P.length > 1, V = k(
202
+ () => n ? { ...n.colors, ...f } : { ...Fe, ...f },
203
+ [n, f]
204
+ ), M = k(() => {
205
+ if (!n) return [];
206
+ let e = n.seats.filter((t) => t.state !== "hidden");
207
+ return m && (e = e.filter(
208
+ (t) => t.floorId === m || !t.floorId && m === "floor_default"
207
209
  )), e;
208
- }, [o, p]), ae = w(() => o?.stages ? p ? o.stages.filter(
209
- (e) => e.floorId === p || !e.floorId && p === "floor_default"
210
- ) : o.stages : [], [o, p]), A = w(() => {
211
- const e = new Set(g), n = new Set(b);
212
- return { reserved: e, unavailable: n };
213
- }, [g, b]), L = z((e) => {
214
- const n = e.id, u = e.seatNumber || "";
215
- return A.unavailable.has(n) || A.unavailable.has(u) ? "unavailable" : A.reserved.has(n) || A.reserved.has(u) ? "reserved" : v.has(n) ? "selected" : e.state;
216
- }, [A, v]);
217
- $(() => {
218
- o && k && k(o);
219
- }, [o, k]), $(() => {
220
- I && j && j(I);
221
- }, [I, j]);
222
- const ce = z((e) => {
223
- const n = L(e);
224
- if (n !== "available" && n !== "selected")
210
+ }, [n, m]), T = k(() => n?.stages ? m ? n.stages.filter(
211
+ (e) => e.floorId === m || !e.floorId && m === "floor_default"
212
+ ) : n.stages : [], [n, m]), b = k(() => {
213
+ if (!n || M.length === 0 && T.length === 0)
214
+ return null;
215
+ const e = 12;
216
+ let t = 1 / 0, o = 1 / 0, s = -1 / 0, a = -1 / 0;
217
+ return M.forEach((l) => {
218
+ t = Math.min(t, l.position.x - e), o = Math.min(o, l.position.y - e), s = Math.max(s, l.position.x + e), a = Math.max(a, l.position.y + e);
219
+ }), T.forEach((l) => {
220
+ t = Math.min(t, l.position.x), o = Math.min(o, l.position.y), s = Math.max(s, l.position.x + (l.config?.width || 200)), a = Math.max(a, l.position.y + (l.config?.height || 100));
221
+ }), { minX: t, minY: o, maxX: s, maxY: a, width: s - t, height: a - o };
222
+ }, [n, M, T]);
223
+ F(() => {
224
+ if (!j || J || !n || !b) return;
225
+ const e = n.canvas.width, t = n.canvas.height, o = e - H * 2, s = t - H * 2, a = o / b.width, l = s / b.height, N = Math.min(a, l, 2), C = b.minX + b.width / 2, ye = b.minY + b.height / 2, xe = e / 2, be = t / 2, ve = xe - C * N, Se = be - ye * N;
226
+ G(N), K({ x: ve, y: Se }), Q(!0);
227
+ }, [j, J, n, b, H]), F(() => {
228
+ j && Q(!1);
229
+ }, [m, j]);
230
+ const z = k(() => {
231
+ const e = new Set(x), t = new Set(w);
232
+ return { reserved: e, unavailable: t };
233
+ }, [x, w]), _ = W((e) => {
234
+ const t = e.id, o = e.seatNumber || "";
235
+ return z.unavailable.has(t) || z.unavailable.has(o) ? "unavailable" : z.reserved.has(t) || z.reserved.has(o) ? "reserved" : I.has(t) ? "selected" : e.state;
236
+ }, [z, I]);
237
+ F(() => {
238
+ n && E && E(n);
239
+ }, [n, E]), F(() => {
240
+ Y && D && D(Y);
241
+ }, [Y, D]);
242
+ const ge = W((e) => {
243
+ const t = _(e);
244
+ if (t !== "available" && t !== "selected")
225
245
  return;
226
- const u = v.has(e.id);
227
- Z((S) => {
228
- const x = new Set(S);
229
- return u ? x.delete(e.id) : x.add(e.id), x;
230
- }), u ? f?.(e) : (d?.(e), d || console.log("Seat selected:", e));
231
- }, [L, v, d, f]), F = w(() => o ? D.filter((e) => v.has(e.id)) : [], [D, v]);
232
- $(() => {
233
- s?.(F);
234
- }, [F, s]);
235
- const de = z((e) => {
236
- if (!C) return;
246
+ const o = I.has(e.id);
247
+ le((s) => {
248
+ const a = new Set(s);
249
+ return o ? a.delete(e.id) : a.add(e.id), a;
250
+ }), o ? g?.(e) : (p?.(e), p || console.log("Seat selected:", e));
251
+ }, [_, I, p, g]), X = k(() => n ? M.filter((e) => I.has(e.id)) : [], [M, I]);
252
+ F(() => {
253
+ d?.(X);
254
+ }, [X, d]);
255
+ const me = W((e) => {
256
+ if (!A) return;
237
257
  e.evt.preventDefault();
238
- const n = _.current;
239
- if (!n) return;
240
- const u = T, S = n.getPointerPosition();
241
- if (!S) return;
242
- const x = {
243
- x: (S.x - N.x) / u,
244
- y: (S.y - N.y) / u
245
- }, fe = e.evt.deltaY > 0 ? -1 : 1, Y = 1.05;
246
- let R = fe > 0 ? u * Y : u / Y;
247
- R = Math.max(0.5, Math.min(5, R)), ee(R), te({
248
- x: S.x - x.x * R,
249
- y: S.y - x.y * R
258
+ const t = q.current;
259
+ if (!t) return;
260
+ const o = B, s = t.getPointerPosition();
261
+ if (!s) return;
262
+ const a = {
263
+ x: (s.x - R.x) / o,
264
+ y: (s.y - R.y) / o
265
+ }, l = e.evt.deltaY > 0 ? -1 : 1, N = 1.05;
266
+ let C = l > 0 ? o * N : o / N;
267
+ C = Math.max(0.5, Math.min(5, C)), G(C), K({
268
+ x: s.x - a.x * C,
269
+ y: s.y - a.y * C
250
270
  });
251
- }, [C, T, N]);
252
- return ie ? /* @__PURE__ */ r("div", { className: `flex items-center justify-center h-full ${c}`, children: /* @__PURE__ */ r("p", { children: "Loading seat map..." }) }) : I ? /* @__PURE__ */ r("div", { className: `flex items-center justify-center h-full ${c}`, children: /* @__PURE__ */ m("p", { className: "text-red-500", children: [
271
+ }, [A, B, R]);
272
+ return he ? /* @__PURE__ */ r("div", { className: `flex items-center justify-center h-full ${u}`, children: /* @__PURE__ */ r("p", { children: "Loading seat map..." }) }) : Y ? /* @__PURE__ */ r("div", { className: `flex items-center justify-center h-full ${u}`, children: /* @__PURE__ */ v("p", { className: "text-red-500", children: [
253
273
  "Error loading seat map: ",
254
- I.message
255
- ] }) }) : o ? /* @__PURE__ */ m("div", { className: `relative ${c}`, children: [
256
- le && P.length > 0 && /* @__PURE__ */ r(
257
- q,
274
+ Y.message
275
+ ] }) }) : n ? /* @__PURE__ */ v("div", { className: `relative ${u}`, children: [
276
+ pe && P.length > 0 && /* @__PURE__ */ r(
277
+ ie,
258
278
  {
259
279
  floors: P,
260
- currentFloorId: p,
261
- onFloorChange: se,
262
- showAllOption: J,
263
- allLabel: Q,
264
- position: G,
265
- className: K
280
+ currentFloorId: m,
281
+ onFloorChange: ue,
282
+ showAllOption: se,
283
+ allLabel: ae,
284
+ position: oe,
285
+ className: re
266
286
  }
267
287
  ),
268
- /* @__PURE__ */ m(
269
- he,
288
+ /* @__PURE__ */ v(
289
+ Ce,
270
290
  {
271
- ref: _,
272
- width: o.canvas.width,
273
- height: o.canvas.height,
274
- scaleX: T,
275
- scaleY: T,
276
- x: N.x,
277
- y: N.y,
278
- onWheel: de,
279
- style: { backgroundColor: o.canvas.backgroundColor },
291
+ ref: q,
292
+ width: n.canvas.width,
293
+ height: n.canvas.height,
294
+ scaleX: B,
295
+ scaleY: B,
296
+ x: R.x,
297
+ y: R.y,
298
+ onWheel: me,
299
+ style: { backgroundColor: n.canvas.backgroundColor },
280
300
  children: [
281
- /* @__PURE__ */ r(H, { listening: !1, children: ae.map((e) => /* @__PURE__ */ r(
282
- X,
301
+ /* @__PURE__ */ r(Z, { listening: !1, children: T.map((e) => /* @__PURE__ */ r(
302
+ ne,
283
303
  {
284
304
  stage: e,
285
- stageColor: B.stageColor
305
+ stageColor: V.stageColor
286
306
  },
287
307
  e.id
288
308
  )) }),
289
- /* @__PURE__ */ r(H, { children: D.map((e) => /* @__PURE__ */ r(
290
- U,
309
+ /* @__PURE__ */ r(Z, { children: M.map((e) => /* @__PURE__ */ r(
310
+ te,
291
311
  {
292
312
  seat: e,
293
- state: L(e),
294
- colors: B,
295
- onClick: ce
313
+ state: _(e),
314
+ colors: V,
315
+ onClick: ge
296
316
  },
297
317
  e.id
298
318
  )) })
299
319
  ]
300
320
  }
301
321
  ),
302
- F.length > 0 && /* @__PURE__ */ m("div", { className: "absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg", children: [
303
- /* @__PURE__ */ m("h3", { className: "font-semibold mb-2", children: [
322
+ X.length > 0 && /* @__PURE__ */ v("div", { className: "absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg", children: [
323
+ /* @__PURE__ */ v("h3", { className: "font-semibold mb-2", children: [
304
324
  "Selected Seats (",
305
- F.length,
325
+ X.length,
306
326
  ")"
307
327
  ] }),
308
- /* @__PURE__ */ r("div", { className: "max-h-48 overflow-y-auto space-y-1", children: F.map((e) => /* @__PURE__ */ m("div", { className: "text-sm", children: [
328
+ /* @__PURE__ */ r("div", { className: "max-h-48 overflow-y-auto space-y-1", children: X.map((e) => /* @__PURE__ */ v("div", { className: "text-sm", children: [
309
329
  e.seatNumber,
310
- e.price && ` - ${B.currency} ${e.price.toFixed(2)}`
330
+ e.price && ` - ${V.currency} ${e.price.toFixed(2)}`
311
331
  ] }, e.id)) })
312
332
  ] })
313
- ] }) : /* @__PURE__ */ r("div", { className: `flex items-center justify-center h-full ${c}`, children: /* @__PURE__ */ r("p", { children: "No configuration provided" }) });
333
+ ] }) : /* @__PURE__ */ r("div", { className: `flex items-center justify-center h-full ${u}`, children: /* @__PURE__ */ r("p", { children: "No configuration provided" }) });
314
334
  };
315
335
  export {
316
- Se as DEFAULT_COLORS,
317
- we as SeatMapViewer,
318
- be as useConfigFetcher
336
+ Fe as DEFAULT_COLORS,
337
+ Ye as SeatMapViewer,
338
+ Ne as useConfigFetcher
319
339
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonetrix/viewer",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "description": "Lightweight React component for rendering interactive seat maps",
6
6
  "main": "./dist/index.js",