@zonetrix/viewer 2.3.0 → 2.4.1

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
@@ -113,7 +113,7 @@ function BookingApp() {
113
113
  | `onSelectionChange` | `(seats: SeatData[]) => void` | No | Callback when selection changes |
114
114
  | `colorOverrides` | `Partial<ColorSettings>` | No | Custom colors for seat states |
115
115
  | `showTooltip` | `boolean` | No | Show tooltips on hover (default: true) |
116
- | `zoomEnabled` | `boolean` | No | Enable/disable mouse wheel zoom (default: true) |
116
+ | `zoomEnabled` | `boolean` | No | Enable/disable zoom functionality (default: true) |
117
117
  | `className` | `string` | No | Custom CSS class for the container |
118
118
  | `onConfigLoad` | `(config: SeatMapConfig) => void` | No | Callback when config is loaded |
119
119
  | `onError` | `(error: Error) => void` | No | Callback when an error occurs |
@@ -122,6 +122,13 @@ function BookingApp() {
122
122
  | `floorSelectorClassName` | `string` | No | Custom CSS class for floor selector |
123
123
  | `showAllFloorsOption` | `boolean` | No | Show "All" button in floor selector (default: true) |
124
124
  | `allFloorsLabel` | `string` | No | Custom label for "All" button (default: 'All') |
125
+ | `fitToView` | `boolean` | No | Auto-fit content on load (default: true) |
126
+ | `fitPadding` | `number` | No | Padding around content when fitting (default: 40) |
127
+ | `showZoomControls` | `boolean` | No | Show zoom +/- buttons (default: true) |
128
+ | `zoomControlsPosition` | `string` | No | Position: 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' (default: 'bottom-right') |
129
+ | `zoomControlsClassName` | `string` | No | Custom CSS class for zoom controls |
130
+ | `maxZoom` | `number` | No | Maximum zoom level (default: 3) |
131
+ | `zoomStep` | `number` | No | Zoom increment per click (default: 0.25) |
125
132
 
126
133
  *Note: Either `config` or `configUrl` must be provided.
127
134
 
@@ -245,22 +252,33 @@ function SelectionTracker() {
245
252
  }
246
253
  ```
247
254
 
248
- ### 5. Disable Zoom for Mobile
255
+ ### 5. Customize Zoom Controls
249
256
 
250
257
  ```tsx
251
258
  import { SeatMapViewer } from '@zonetrix/viewer';
252
259
 
253
- function MobileOptimized() {
254
- const isMobile = window.innerWidth < 768;
255
-
260
+ function CustomZoom() {
256
261
  return (
257
262
  <SeatMapViewer
258
263
  config={venueConfig}
259
- zoomEnabled={!isMobile}
264
+ // Zoom controls appear in bottom-right by default
265
+ zoomControlsPosition="bottom-left"
266
+ maxZoom={5} // Allow up to 5x zoom
267
+ zoomStep={0.5} // Larger zoom increments
260
268
  onSeatSelect={(seat) => handleSelection(seat)}
261
269
  />
262
270
  );
263
271
  }
272
+
273
+ // Hide zoom controls entirely
274
+ function NoZoomControls() {
275
+ return (
276
+ <SeatMapViewer
277
+ config={venueConfig}
278
+ showZoomControls={false}
279
+ />
280
+ );
281
+ }
264
282
  ```
265
283
 
266
284
  ### 6. Multi-floor Venue (Built-in Floor Selector)
@@ -23,5 +23,10 @@ export interface SeatMapViewerProps {
23
23
  allFloorsLabel?: string;
24
24
  fitToView?: boolean;
25
25
  fitPadding?: number;
26
+ showZoomControls?: boolean;
27
+ zoomControlsPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
28
+ zoomControlsClassName?: string;
29
+ maxZoom?: number;
30
+ zoomStep?: number;
26
31
  }
27
32
  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 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;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),n=require("react"),E=require("react-konva");function J(o){const[p,h]=n.useState(null),[a,y]=n.useState(!1),[m,S]=n.useState(null),j=async()=>{if(o){y(!0),S(null);try{const x=await fetch(o);if(!x.ok)throw new Error(`Failed to fetch config: ${x.statusText}`);const r=await x.json();h(r)}catch(x){const r=x instanceof Error?x:new Error("Unknown error occurred");S(r),console.error("Failed to fetch seat map config:",r)}finally{y(!1)}}};return n.useEffect(()=>{j()},[o]),{config:p,loading:a,error:m,refetch:j}}const Q={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",seatHidden:"#4a4a4a",gridLines:"#404040",currency:"KD"},ee=n.memo(({seat:o,state:p,colors:h,onClick:a,onMouseEnter:y,onMouseLeave:m})=>{const x={available:h.seatAvailable,reserved:h.seatReserved,selected:h.seatSelected,unavailable:h.seatUnavailable,hidden:h.seatHidden}[p],r=p==="available"||p==="selected",g=n.useCallback(()=>{r&&a(o)},[o,a,r]),b=n.useCallback(k=>{y(o,k);const M=k.target.getStage();M&&r&&(M.container().style.cursor="pointer")},[o,y,r]),f=n.useCallback(k=>{m();const M=k.target.getStage();M&&(M.container().style.cursor="grab")},[m]),w={x:o.position.x,y:o.position.y,fill:x,stroke:"#ffffff",strokeWidth:1,onClick:g,onTap:g,onMouseEnter:b,onMouseLeave:f};return o.shape==="circle"?t.jsx(E.Circle,{...w,radius:12}):t.jsx(E.Rect,{...w,width:24,height:24,offsetX:12,offsetY:12,cornerRadius:o.shape==="square"?0:4})});ee.displayName="ViewerSeat";const te=n.memo(({stage:o,stageColor:p})=>t.jsxs(E.Group,{x:o.position.x,y:o.position.y,children:[t.jsx(E.Rect,{width:o.config.width,height:o.config.height,fill:p+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),t.jsx(E.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"})]}));te.displayName="ViewerStage";const ne=n.memo(({floors:o,currentFloorId:p,onFloorChange:h,showAllOption:a,allLabel:y,position:m,className:S})=>{const j=n.useMemo(()=>[...o].sort((f,w)=>f.order-w.order),[o]),r={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]},g={padding:"6px 14px",fontSize:"14px",fontWeight:500,border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",transition:"all 0.2s ease"},b={...g,backgroundColor:"#3A7DE5",borderColor:"#3A7DE5"};return t.jsxs("div",{className:S,style:r,children:[a&&t.jsx("button",{type:"button",onClick:()=>h(null),style:p===null?b:g,children:y}),j.map(f=>t.jsx("button",{type:"button",onClick:()=>h(f.id),style:p===f.id?b:g,children:f.name},f.id))]})});ne.displayName="FloorSelectorBar";const se=n.memo(({scale:o,minScale:p,maxScale:h,onZoomIn:a,onZoomOut:y,position:m,className:S})=>{const x={position:"absolute",display:"flex",flexDirection:"column",gap:"4px",padding:"8px",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]},r={width:"36px",height:"36px",fontSize:"20px",fontWeight:"bold",border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",transition:"all 0.2s ease"},g={...r,opacity:.4,cursor:"not-allowed"},b=o<h,f=o>p;return t.jsxs("div",{className:S,style:x,children:[t.jsx("button",{type:"button",onClick:a,disabled:!b,style:b?r:g,title:"Zoom In",children:"+"}),t.jsx("button",{type:"button",onClick:y,disabled:!f,style:f?r:g,title:"Zoom Out",children:"−"})]})});se.displayName="ZoomControls";const oe=n.memo(({visible:o,x:p,y:h,seat:a,currency:y,state:m})=>{if(!o||!a)return null;const S=a.seatNumber||(a.rowLabel&&a.columnLabel?`${a.rowLabel}-${a.columnLabel}`:"N/A"),j={position:"fixed",left:`${p+15}px`,top:`${h+15}px`,zIndex:1e3,pointerEvents:"none"},x={backgroundColor:"rgba(26, 26, 26, 0.95)",color:"#fff",border:"1px solid #444",borderRadius:"8px",padding:"8px 12px",fontSize:"13px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.3)",minWidth:"140px"},r={color:"#9ca3af",marginRight:"4px"},g={fontWeight:600},b={color:"#4ade80",fontWeight:600},f={fontSize:"11px",color:"#6b7280",textTransform:"capitalize",marginTop:"4px"};return t.jsx("div",{style:j,children:t.jsxs("div",{style:x,children:[a.sectionName&&t.jsxs("div",{style:{marginBottom:"4px"},children:[t.jsx("span",{style:r,children:"Section:"}),t.jsx("span",{style:{...g,color:"#3b82f6"},children:a.sectionName})]}),t.jsxs("div",{style:{marginBottom:"4px"},children:[t.jsx("span",{style:r,children:"Seat:"}),t.jsx("span",{style:g,children:S})]}),a.price!==void 0&&a.price>0&&t.jsxs("div",{style:{marginBottom:"4px"},children:[t.jsx("span",{style:r,children:"Price:"}),t.jsxs("span",{style:b,children:[y," ",a.price.toFixed(2)]})]}),t.jsxs("div",{style:f,children:["Status: ",m]})]})})});oe.displayName="SeatTooltip";const Be=({config:o,configUrl:p,floorId:h,onFloorChange:a,reservedSeats:y=[],unavailableSeats:m=[],onSeatSelect:S,onSeatDeselect:j,onSelectionChange:x,colorOverrides:r,showTooltip:g=!0,zoomEnabled:b=!0,className:f="",onConfigLoad:w,onError:k,showFloorSelector:M,floorSelectorPosition:ie="top-left",floorSelectorClassName:ae,showAllFloorsOption:re=!0,allFloorsLabel:le="All",fitToView:$=!0,fitPadding:P=40,showZoomControls:ce=!0,zoomControlsPosition:de="bottom-right",zoomControlsClassName:ue,maxZoom:L=3,zoomStep:D=.25})=>{const fe=n.useRef(null),[R,he]=n.useState(new Set),[v,Z]=n.useState(1),[I,T]=n.useState({x:0,y:0}),[pe,xe]=n.useState(null),[U,K]=n.useState(!1),[ge,be]=n.useState(1),[A,G]=n.useState({visible:!1,x:0,y:0,seat:null,state:"available"}),{config:ye,loading:me,error:B}=J(p),s=o||ye,V=h!==void 0,C=V?h||null:pe,Se=n.useCallback(e=>{V||xe(e),a?.(e)},[V,a]),q=s?.floors||[],ve=M!==void 0?M:q.length>1,z=n.useMemo(()=>s?{...s.colors,...r}:{...Q,...r},[s,r]),F=n.useMemo(()=>{if(!s)return[];let e=s.seats.filter(i=>i.state!=="hidden");return C&&(e=e.filter(i=>i.floorId===C||!i.floorId&&C==="floor_default")),e},[s,C]),O=n.useMemo(()=>s?.stages?C?s.stages.filter(e=>e.floorId===C||!e.floorId&&C==="floor_default"):s.stages:[],[s,C]),N=n.useMemo(()=>{if(!s||F.length===0&&O.length===0)return null;const e=12;let i=1/0,l=1/0,d=-1/0,c=-1/0;return F.forEach(u=>{i=Math.min(i,u.position.x-e),l=Math.min(l,u.position.y-e),d=Math.max(d,u.position.x+e),c=Math.max(c,u.position.y+e)}),O.forEach(u=>{i=Math.min(i,u.position.x),l=Math.min(l,u.position.y),d=Math.max(d,u.position.x+(u.config?.width||200)),c=Math.max(c,u.position.y+(u.config?.height||100))}),{minX:i,minY:l,maxX:d,maxY:c,width:d-i,height:c-l}},[s,F,O]);n.useEffect(()=>{if(!$||U||!s||!N)return;const e=s.canvas.width,i=s.canvas.height,l=e-P*2,d=i-P*2,c=l/N.width,u=d/N.height,H=Math.min(c,u,L),Ne=N.minX+N.width/2,Ee=N.minY+N.height/2,Re=e/2,Fe=i/2,Le=Re-Ne*H,Ae=Fe-Ee*H;Z(H),T({x:Le,y:Ae}),be(H),K(!0)},[$,U,s,N,P,L]),n.useEffect(()=>{$&&K(!1)},[C,$]);const W=n.useMemo(()=>{const e=new Set(y),i=new Set(m);return{reserved:e,unavailable:i}},[y,m]),X=n.useCallback(e=>{const i=e.id,l=e.seatNumber||"";return W.unavailable.has(i)||W.unavailable.has(l)?"unavailable":W.reserved.has(i)||W.reserved.has(l)?"reserved":R.has(i)?"selected":e.state},[W,R]);n.useEffect(()=>{s&&w&&w(s)},[s,w]),n.useEffect(()=>{B&&k&&k(B)},[B,k]);const je=n.useCallback(e=>{const i=X(e);if(i!=="available"&&i!=="selected")return;const l=R.has(e.id);he(d=>{const c=new Set(d);return l?c.delete(e.id):c.add(e.id),c}),l?j?.(e):(S?.(e),S||console.log("Seat selected:",e))},[X,R,S,j]),Y=n.useMemo(()=>s?F.filter(e=>R.has(e.id)):[],[F,R]);n.useEffect(()=>{x?.(Y)},[Y,x]);const _=ge,Ce=n.useCallback(()=>{if(!b)return;const e=Math.min(v+D,L);if(e!==v){const i=s?.canvas.width||800,l=s?.canvas.height||600,d=i/2,c=l/2,u={x:(d-I.x)/v,y:(c-I.y)/v};Z(e),T({x:d-u.x*e,y:c-u.y*e})}},[b,v,D,L,s,I]),we=n.useCallback(()=>{if(!b)return;const e=Math.max(v-D,_);if(e!==v){const i=s?.canvas.width||800,l=s?.canvas.height||600,d=i/2,c=l/2,u={x:(d-I.x)/v,y:(c-I.y)/v};Z(e),T({x:d-u.x*e,y:c-u.y*e})}},[b,v,D,_,s,I]),ke=n.useCallback(e=>{T({x:e.target.x(),y:e.target.y()})},[]),Me=n.useCallback((e,i)=>{if(!g)return;const l=i.target.getStage();if(!l)return;const d=l.getPointerPosition();if(!d)return;const c=l.container().getBoundingClientRect();G({visible:!0,x:c.left+d.x,y:c.top+d.y,seat:e,state:X(e)})},[g,X]),Ie=n.useCallback(()=>{G(e=>({...e,visible:!1}))},[]);return me?t.jsx("div",{className:`flex items-center justify-center h-full ${f}`,children:t.jsx("p",{children:"Loading seat map..."})}):B?t.jsx("div",{className:`flex items-center justify-center h-full ${f}`,children:t.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",B.message]})}):s?t.jsxs("div",{className:`relative ${f}`,children:[ve&&q.length>0&&t.jsx(ne,{floors:q,currentFloorId:C,onFloorChange:Se,showAllOption:re,allLabel:le,position:ie,className:ae}),t.jsxs(E.Stage,{ref:fe,width:s.canvas.width,height:s.canvas.height,scaleX:v,scaleY:v,x:I.x,y:I.y,draggable:!0,onDragEnd:ke,style:{backgroundColor:s.canvas.backgroundColor,cursor:"grab"},children:[t.jsx(E.Layer,{listening:!1,children:O.map(e=>t.jsx(te,{stage:e,stageColor:z.stageColor},e.id))}),t.jsx(E.Layer,{children:F.map(e=>t.jsx(ee,{seat:e,state:X(e),colors:z,onClick:je,onMouseEnter:Me,onMouseLeave:Ie},e.id))})]}),g&&t.jsx(oe,{visible:A.visible,x:A.x,y:A.y,seat:A.seat,currency:z.currency,state:A.state}),ce&&b&&t.jsx(se,{scale:v,minScale:_,maxScale:L,onZoomIn:Ce,onZoomOut:we,position:de,className:ue}),Y.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 (",Y.length,")"]}),t.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:Y.map(e=>t.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${z.currency} ${e.price.toFixed(2)}`]},e.id))})]})]}):t.jsx("div",{className:`flex items-center justify-center h-full ${f}`,children:t.jsx("p",{children:"No configuration provided"})})};exports.DEFAULT_COLORS=Q;exports.SeatMapViewer=Be;exports.useConfigFetcher=J;
package/dist/index.mjs CHANGED
@@ -1,34 +1,34 @@
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);
1
+ import { jsx as i, jsxs as x } from "react/jsx-runtime";
2
+ import { useState as I, useEffect as W, useRef as zt, useCallback as C, useMemo as L, memo as j } from "react";
3
+ import { Stage as Dt, Layer as nt, Group as Tt, Rect as ot, Text as jt, Circle as Ht } from "react-konva";
4
+ function Ot(n) {
5
+ const [u, h] = I(null), [r, b] = I(!1), [m, v] = I(null), w = async () => {
6
+ if (n) {
7
+ b(!0), v(null);
8
8
  try {
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);
9
+ const p = await fetch(n);
10
+ if (!p.ok)
11
+ throw new Error(`Failed to fetch config: ${p.statusText}`);
12
+ const s = await p.json();
13
+ h(s);
14
+ } catch (p) {
15
+ const s = p instanceof Error ? p : new Error("Unknown error occurred");
16
+ v(s), console.error("Failed to fetch seat map config:", s);
17
17
  } finally {
18
- x(!1);
18
+ b(!1);
19
19
  }
20
20
  }
21
21
  };
22
- return F(() => {
23
- g();
24
- }, [i]), {
25
- config: h,
26
- loading: y,
27
- error: w,
28
- refetch: g
22
+ return W(() => {
23
+ w();
24
+ }, [n]), {
25
+ config: u,
26
+ loading: r,
27
+ error: m,
28
+ refetch: w
29
29
  };
30
30
  }
31
- const Fe = {
31
+ const Zt = {
32
32
  canvasBackground: "#1a1a1a",
33
33
  stageColor: "#808080",
34
34
  seatAvailable: "#2C2B30",
@@ -38,64 +38,74 @@ const Fe = {
38
38
  seatHidden: "#4a4a4a",
39
39
  gridLines: "#404040",
40
40
  currency: "KD"
41
- }, te = O(({ seat: i, state: h, colors: c, onClick: y }) => {
41
+ }, it = j(({ seat: n, state: u, colors: h, onClick: r, onMouseEnter: b, onMouseLeave: m }) => {
42
42
  const p = {
43
- available: c.seatAvailable,
44
- reserved: c.seatReserved,
45
- selected: c.seatSelected,
46
- unavailable: c.seatUnavailable,
47
- hidden: c.seatHidden
43
+ available: h.seatAvailable,
44
+ reserved: h.seatReserved,
45
+ selected: h.seatSelected,
46
+ unavailable: h.seatUnavailable,
47
+ hidden: h.seatHidden
48
48
  // Hidden seats are filtered out, but included for type safety
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,
49
+ }[u], s = u === "available" || u === "selected", g = C(() => {
50
+ s && r(n);
51
+ }, [n, r, s]), y = C((M) => {
52
+ b(n, M);
53
+ const R = M.target.getStage();
54
+ R && s && (R.container().style.cursor = "pointer");
55
+ }, [n, b, s]), f = C((M) => {
56
+ m();
57
+ const R = M.target.getStage();
58
+ R && (R.container().style.cursor = "grab");
59
+ }, [m]), N = {
60
+ x: n.position.x,
61
+ y: n.position.y,
54
62
  fill: p,
55
63
  stroke: "#ffffff",
56
64
  strokeWidth: 1,
57
- onClick: d,
58
- onTap: d
65
+ onClick: g,
66
+ onTap: g,
67
+ onMouseEnter: y,
68
+ onMouseLeave: f
59
69
  };
60
- return i.shape === "circle" ? /* @__PURE__ */ r(
61
- Me,
70
+ return n.shape === "circle" ? /* @__PURE__ */ i(
71
+ Ht,
62
72
  {
63
- ...f,
73
+ ...N,
64
74
  radius: 12
65
75
  }
66
- ) : /* @__PURE__ */ r(
67
- ee,
76
+ ) : /* @__PURE__ */ i(
77
+ ot,
68
78
  {
69
- ...f,
79
+ ...N,
70
80
  width: 24,
71
81
  height: 24,
72
82
  offsetX: 12,
73
83
  offsetY: 12,
74
- cornerRadius: i.shape === "square" ? 0 : 4
84
+ cornerRadius: n.shape === "square" ? 0 : 4
75
85
  }
76
86
  );
77
87
  });
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
- /* @__PURE__ */ r(
81
- ee,
88
+ it.displayName = "ViewerSeat";
89
+ const rt = j(({ stage: n, stageColor: u }) => /* @__PURE__ */ x(Tt, { x: n.position.x, y: n.position.y, children: [
90
+ /* @__PURE__ */ i(
91
+ ot,
82
92
  {
83
- width: i.config.width,
84
- height: i.config.height,
85
- fill: h + "80",
93
+ width: n.config.width,
94
+ height: n.config.height,
95
+ fill: u + "80",
86
96
  stroke: "#ffffff",
87
97
  strokeWidth: 2,
88
98
  cornerRadius: 10
89
99
  }
90
100
  ),
91
- /* @__PURE__ */ r(
92
- Ie,
101
+ /* @__PURE__ */ i(
102
+ jt,
93
103
  {
94
- text: i.config.label,
104
+ text: n.config.label,
95
105
  x: 0,
96
106
  y: 0,
97
- width: i.config.width,
98
- height: i.config.height,
107
+ width: n.config.width,
108
+ height: n.config.height,
99
109
  fontSize: 24,
100
110
  fontStyle: "bold",
101
111
  fill: "#ffffff",
@@ -104,20 +114,20 @@ const ne = O(({ stage: i, stageColor: h }) => /* @__PURE__ */ v(ke, { x: i.posit
104
114
  }
105
115
  )
106
116
  ] }));
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
117
+ rt.displayName = "ViewerStage";
118
+ const st = j(({
119
+ floors: n,
120
+ currentFloorId: u,
121
+ onFloorChange: h,
122
+ showAllOption: r,
123
+ allLabel: b,
124
+ position: m,
125
+ className: v
116
126
  }) => {
117
- const g = k(
118
- () => [...i].sort((u, E) => u.order - E.order),
119
- [i]
120
- ), f = {
127
+ const w = L(
128
+ () => [...n].sort((f, N) => f.order - N.order),
129
+ [n]
130
+ ), s = {
121
131
  position: "absolute",
122
132
  display: "flex",
123
133
  alignItems: "center",
@@ -132,8 +142,8 @@ const ie = O(({
132
142
  "top-right": { top: 0, right: 0 },
133
143
  "bottom-left": { bottom: 0, left: 0 },
134
144
  "bottom-right": { bottom: 0, right: 0 }
135
- }[w]
136
- }, $ = {
145
+ }[m]
146
+ }, g = {
137
147
  padding: "6px 14px",
138
148
  fontSize: "14px",
139
149
  fontWeight: 500,
@@ -143,197 +153,392 @@ const ie = O(({
143
153
  color: "#fff",
144
154
  cursor: "pointer",
145
155
  transition: "all 0.2s ease"
146
- }, A = {
147
- ...$,
156
+ }, y = {
157
+ ...g,
148
158
  backgroundColor: "#3A7DE5",
149
159
  borderColor: "#3A7DE5"
150
160
  };
151
- return /* @__PURE__ */ v("div", { className: p, style: f, children: [
152
- y && /* @__PURE__ */ r(
161
+ return /* @__PURE__ */ x("div", { className: v, style: s, children: [
162
+ r && /* @__PURE__ */ i(
153
163
  "button",
154
164
  {
155
165
  type: "button",
156
- onClick: () => c(null),
157
- style: h === null ? A : $,
158
- children: x
166
+ onClick: () => h(null),
167
+ style: u === null ? y : g,
168
+ children: b
159
169
  }
160
170
  ),
161
- g.map((u) => /* @__PURE__ */ r(
171
+ w.map((f) => /* @__PURE__ */ i(
162
172
  "button",
163
173
  {
164
174
  type: "button",
165
- onClick: () => c(u.id),
166
- style: h === u.id ? A : $,
167
- children: u.name
175
+ onClick: () => h(f.id),
176
+ style: u === f.id ? y : g,
177
+ children: f.name
168
178
  },
169
- u.id
179
+ f.id
170
180
  ))
171
181
  ] });
172
182
  });
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,
183
+ st.displayName = "FloorSelectorBar";
184
+ const at = j(({
185
+ scale: n,
186
+ minScale: u,
187
+ maxScale: h,
188
+ onZoomIn: r,
189
+ onZoomOut: b,
190
+ position: m,
191
+ className: v
192
+ }) => {
193
+ const p = {
194
+ position: "absolute",
195
+ display: "flex",
196
+ flexDirection: "column",
197
+ gap: "4px",
198
+ padding: "8px",
199
+ backgroundColor: "rgba(26, 26, 26, 0.95)",
200
+ borderRadius: "8px",
201
+ margin: "12px",
202
+ zIndex: 10,
203
+ ...{
204
+ "top-left": { top: 0, left: 0 },
205
+ "top-right": { top: 0, right: 0 },
206
+ "bottom-left": { bottom: 0, left: 0 },
207
+ "bottom-right": { bottom: 0, right: 0 }
208
+ }[m]
209
+ }, s = {
210
+ width: "36px",
211
+ height: "36px",
212
+ fontSize: "20px",
213
+ fontWeight: "bold",
214
+ border: "1px solid #444",
215
+ borderRadius: "6px",
216
+ backgroundColor: "transparent",
217
+ color: "#fff",
218
+ cursor: "pointer",
219
+ display: "flex",
220
+ alignItems: "center",
221
+ justifyContent: "center",
222
+ transition: "all 0.2s ease"
223
+ }, g = {
224
+ ...s,
225
+ opacity: 0.4,
226
+ cursor: "not-allowed"
227
+ }, y = n < h, f = n > u;
228
+ return /* @__PURE__ */ x("div", { className: v, style: p, children: [
229
+ /* @__PURE__ */ i(
230
+ "button",
231
+ {
232
+ type: "button",
233
+ onClick: r,
234
+ disabled: !y,
235
+ style: y ? s : g,
236
+ title: "Zoom In",
237
+ children: "+"
238
+ }
239
+ ),
240
+ /* @__PURE__ */ i(
241
+ "button",
242
+ {
243
+ type: "button",
244
+ onClick: b,
245
+ disabled: !f,
246
+ style: f ? s : g,
247
+ title: "Zoom Out",
248
+ children: "−"
249
+ }
250
+ )
251
+ ] });
252
+ });
253
+ at.displayName = "ZoomControls";
254
+ const lt = j(({
255
+ visible: n,
256
+ x: u,
257
+ y: h,
258
+ seat: r,
259
+ currency: b,
260
+ state: m
261
+ }) => {
262
+ if (!n || !r) return null;
263
+ const v = r.seatNumber || (r.rowLabel && r.columnLabel ? `${r.rowLabel}-${r.columnLabel}` : "N/A"), w = {
264
+ position: "fixed",
265
+ left: `${u + 15}px`,
266
+ top: `${h + 15}px`,
267
+ zIndex: 1e3,
268
+ pointerEvents: "none"
269
+ }, p = {
270
+ backgroundColor: "rgba(26, 26, 26, 0.95)",
271
+ color: "#fff",
272
+ border: "1px solid #444",
273
+ borderRadius: "8px",
274
+ padding: "8px 12px",
275
+ fontSize: "13px",
276
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)",
277
+ minWidth: "140px"
278
+ }, s = {
279
+ color: "#9ca3af",
280
+ marginRight: "4px"
281
+ }, g = {
282
+ fontWeight: 600
283
+ }, y = {
284
+ color: "#4ade80",
285
+ fontWeight: 600
286
+ }, f = {
287
+ fontSize: "11px",
288
+ color: "#6b7280",
289
+ textTransform: "capitalize",
290
+ marginTop: "4px"
291
+ };
292
+ return /* @__PURE__ */ i("div", { style: w, children: /* @__PURE__ */ x("div", { style: p, children: [
293
+ r.sectionName && /* @__PURE__ */ x("div", { style: { marginBottom: "4px" }, children: [
294
+ /* @__PURE__ */ i("span", { style: s, children: "Section:" }),
295
+ /* @__PURE__ */ i("span", { style: { ...g, color: "#3b82f6" }, children: r.sectionName })
296
+ ] }),
297
+ /* @__PURE__ */ x("div", { style: { marginBottom: "4px" }, children: [
298
+ /* @__PURE__ */ i("span", { style: s, children: "Seat:" }),
299
+ /* @__PURE__ */ i("span", { style: g, children: v })
300
+ ] }),
301
+ r.price !== void 0 && r.price > 0 && /* @__PURE__ */ x("div", { style: { marginBottom: "4px" }, children: [
302
+ /* @__PURE__ */ i("span", { style: s, children: "Price:" }),
303
+ /* @__PURE__ */ x("span", { style: y, children: [
304
+ b,
305
+ " ",
306
+ r.price.toFixed(2)
307
+ ] })
308
+ ] }),
309
+ /* @__PURE__ */ x("div", { style: f, children: [
310
+ "Status: ",
311
+ m
312
+ ] })
313
+ ] }) });
314
+ });
315
+ lt.displayName = "SeatTooltip";
316
+ const Ut = ({
317
+ config: n,
318
+ configUrl: u,
319
+ floorId: h,
320
+ onFloorChange: r,
321
+ reservedSeats: b = [],
322
+ unavailableSeats: m = [],
323
+ onSeatSelect: v,
324
+ onSeatDeselect: w,
325
+ onSelectionChange: p,
326
+ colorOverrides: s,
327
+ showTooltip: g = !0,
328
+ zoomEnabled: y = !0,
329
+ className: f = "",
330
+ onConfigLoad: N,
331
+ onError: M,
190
332
  // Floor selector props
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
333
+ showFloorSelector: R,
334
+ floorSelectorPosition: ct = "top-left",
335
+ floorSelectorClassName: dt,
336
+ showAllFloorsOption: ft = !0,
337
+ allFloorsLabel: ht = "All",
338
+ fitToView: H = !0,
339
+ fitPadding: U = 40,
340
+ // Zoom controls
341
+ showZoomControls: ut = !0,
342
+ zoomControlsPosition: pt = "bottom-right",
343
+ zoomControlsClassName: gt,
344
+ maxZoom: X = 3,
345
+ zoomStep: O = 0.25
198
346
  }) => {
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"
209
- )), e;
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)
347
+ const yt = zt(null), [A, xt] = I(/* @__PURE__ */ new Set()), [S, q] = I(1), [F, Z] = I({ x: 0, y: 0 }), [bt, mt] = I(null), [Q, tt] = I(!1), [vt, St] = I(1), [Y, et] = I({ visible: !1, x: 0, y: 0, seat: null, state: "available" }), { config: wt, loading: Ct, error: $ } = Ot(u), e = n || wt, G = h !== void 0, k = G ? h || null : bt, kt = C((t) => {
348
+ G || mt(t), r?.(t);
349
+ }, [G, r]), K = e?.floors || [], It = R !== void 0 ? R : K.length > 1, P = L(
350
+ () => e ? { ...e.colors, ...s } : { ...Zt, ...s },
351
+ [e, s]
352
+ ), B = L(() => {
353
+ if (!e) return [];
354
+ let t = e.seats.filter((o) => o.state !== "hidden");
355
+ return k && (t = t.filter(
356
+ (o) => o.floorId === k || !o.floorId && k === "floor_default"
357
+ )), t;
358
+ }, [e, k]), V = L(() => e?.stages ? k ? e.stages.filter(
359
+ (t) => t.floorId === k || !t.floorId && k === "floor_default"
360
+ ) : e.stages : [], [e, k]), E = L(() => {
361
+ if (!e || B.length === 0 && V.length === 0)
214
362
  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")
363
+ const t = 12;
364
+ let o = 1 / 0, a = 1 / 0, c = -1 / 0, l = -1 / 0;
365
+ return B.forEach((d) => {
366
+ o = Math.min(o, d.position.x - t), a = Math.min(a, d.position.y - t), c = Math.max(c, d.position.x + t), l = Math.max(l, d.position.y + t);
367
+ }), V.forEach((d) => {
368
+ o = Math.min(o, d.position.x), a = Math.min(a, d.position.y), c = Math.max(c, d.position.x + (d.config?.width || 200)), l = Math.max(l, d.position.y + (d.config?.height || 100));
369
+ }), { minX: o, minY: a, maxX: c, maxY: l, width: c - o, height: l - a };
370
+ }, [e, B, V]);
371
+ W(() => {
372
+ if (!H || Q || !e || !E) return;
373
+ const t = e.canvas.width, o = e.canvas.height, a = t - U * 2, c = o - U * 2, l = a / E.width, d = c / E.height, _ = Math.min(l, d, X), At = E.minX + E.width / 2, Bt = E.minY + E.height / 2, Wt = t / 2, Xt = o / 2, Yt = Wt - At * _, $t = Xt - Bt * _;
374
+ q(_), Z({ x: Yt, y: $t }), St(_), tt(!0);
375
+ }, [H, Q, e, E, U, X]), W(() => {
376
+ H && tt(!1);
377
+ }, [k, H]);
378
+ const z = L(() => {
379
+ const t = new Set(b), o = new Set(m);
380
+ return { reserved: t, unavailable: o };
381
+ }, [b, m]), D = C((t) => {
382
+ const o = t.id, a = t.seatNumber || "";
383
+ return z.unavailable.has(o) || z.unavailable.has(a) ? "unavailable" : z.reserved.has(o) || z.reserved.has(a) ? "reserved" : A.has(o) ? "selected" : t.state;
384
+ }, [z, A]);
385
+ W(() => {
386
+ e && N && N(e);
387
+ }, [e, N]), W(() => {
388
+ $ && M && M($);
389
+ }, [$, M]);
390
+ const Nt = C((t) => {
391
+ const o = D(t);
392
+ if (o !== "available" && o !== "selected")
245
393
  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;
257
- e.evt.preventDefault();
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
394
+ const a = A.has(t.id);
395
+ xt((c) => {
396
+ const l = new Set(c);
397
+ return a ? l.delete(t.id) : l.add(t.id), l;
398
+ }), a ? w?.(t) : (v?.(t), v || console.log("Seat selected:", t));
399
+ }, [D, A, v, w]), T = L(() => e ? B.filter((t) => A.has(t.id)) : [], [B, A]);
400
+ W(() => {
401
+ p?.(T);
402
+ }, [T, p]);
403
+ const J = vt, Mt = C(() => {
404
+ if (!y) return;
405
+ const t = Math.min(S + O, X);
406
+ if (t !== S) {
407
+ const o = e?.canvas.width || 800, a = e?.canvas.height || 600, c = o / 2, l = a / 2, d = {
408
+ x: (c - F.x) / S,
409
+ y: (l - F.y) / S
410
+ };
411
+ q(t), Z({
412
+ x: c - d.x * t,
413
+ y: l - d.y * t
414
+ });
415
+ }
416
+ }, [y, S, O, X, e, F]), Rt = C(() => {
417
+ if (!y) return;
418
+ const t = Math.max(S - O, J);
419
+ if (t !== S) {
420
+ const o = e?.canvas.width || 800, a = e?.canvas.height || 600, c = o / 2, l = a / 2, d = {
421
+ x: (c - F.x) / S,
422
+ y: (l - F.y) / S
423
+ };
424
+ q(t), Z({
425
+ x: c - d.x * t,
426
+ y: l - d.y * t
427
+ });
428
+ }
429
+ }, [y, S, O, J, e, F]), Ft = C((t) => {
430
+ Z({
431
+ x: t.target.x(),
432
+ y: t.target.y()
433
+ });
434
+ }, []), Et = C((t, o) => {
435
+ if (!g) return;
436
+ const a = o.target.getStage();
437
+ if (!a) return;
438
+ const c = a.getPointerPosition();
439
+ if (!c) return;
440
+ const l = a.container().getBoundingClientRect();
441
+ et({
442
+ visible: !0,
443
+ x: l.left + c.x,
444
+ y: l.top + c.y,
445
+ seat: t,
446
+ state: D(t)
270
447
  });
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: [
448
+ }, [g, D]), Lt = C(() => {
449
+ et((t) => ({ ...t, visible: !1 }));
450
+ }, []);
451
+ return Ct ? /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${f}`, children: /* @__PURE__ */ i("p", { children: "Loading seat map..." }) }) : $ ? /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${f}`, children: /* @__PURE__ */ x("p", { className: "text-red-500", children: [
273
452
  "Error loading seat map: ",
274
- Y.message
275
- ] }) }) : n ? /* @__PURE__ */ v("div", { className: `relative ${u}`, children: [
276
- pe && P.length > 0 && /* @__PURE__ */ r(
277
- ie,
453
+ $.message
454
+ ] }) }) : e ? /* @__PURE__ */ x("div", { className: `relative ${f}`, children: [
455
+ It && K.length > 0 && /* @__PURE__ */ i(
456
+ st,
278
457
  {
279
- floors: P,
280
- currentFloorId: m,
281
- onFloorChange: ue,
282
- showAllOption: se,
283
- allLabel: ae,
284
- position: oe,
285
- className: re
458
+ floors: K,
459
+ currentFloorId: k,
460
+ onFloorChange: kt,
461
+ showAllOption: ft,
462
+ allLabel: ht,
463
+ position: ct,
464
+ className: dt
286
465
  }
287
466
  ),
288
- /* @__PURE__ */ v(
289
- Ce,
467
+ /* @__PURE__ */ x(
468
+ Dt,
290
469
  {
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 },
470
+ ref: yt,
471
+ width: e.canvas.width,
472
+ height: e.canvas.height,
473
+ scaleX: S,
474
+ scaleY: S,
475
+ x: F.x,
476
+ y: F.y,
477
+ draggable: !0,
478
+ onDragEnd: Ft,
479
+ style: { backgroundColor: e.canvas.backgroundColor, cursor: "grab" },
300
480
  children: [
301
- /* @__PURE__ */ r(Z, { listening: !1, children: T.map((e) => /* @__PURE__ */ r(
302
- ne,
481
+ /* @__PURE__ */ i(nt, { listening: !1, children: V.map((t) => /* @__PURE__ */ i(
482
+ rt,
303
483
  {
304
- stage: e,
305
- stageColor: V.stageColor
484
+ stage: t,
485
+ stageColor: P.stageColor
306
486
  },
307
- e.id
487
+ t.id
308
488
  )) }),
309
- /* @__PURE__ */ r(Z, { children: M.map((e) => /* @__PURE__ */ r(
310
- te,
489
+ /* @__PURE__ */ i(nt, { children: B.map((t) => /* @__PURE__ */ i(
490
+ it,
311
491
  {
312
- seat: e,
313
- state: _(e),
314
- colors: V,
315
- onClick: ge
492
+ seat: t,
493
+ state: D(t),
494
+ colors: P,
495
+ onClick: Nt,
496
+ onMouseEnter: Et,
497
+ onMouseLeave: Lt
316
498
  },
317
- e.id
499
+ t.id
318
500
  )) })
319
501
  ]
320
502
  }
321
503
  ),
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: [
504
+ g && /* @__PURE__ */ i(
505
+ lt,
506
+ {
507
+ visible: Y.visible,
508
+ x: Y.x,
509
+ y: Y.y,
510
+ seat: Y.seat,
511
+ currency: P.currency,
512
+ state: Y.state
513
+ }
514
+ ),
515
+ ut && y && /* @__PURE__ */ i(
516
+ at,
517
+ {
518
+ scale: S,
519
+ minScale: J,
520
+ maxScale: X,
521
+ onZoomIn: Mt,
522
+ onZoomOut: Rt,
523
+ position: pt,
524
+ className: gt
525
+ }
526
+ ),
527
+ T.length > 0 && /* @__PURE__ */ x("div", { className: "absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg", children: [
528
+ /* @__PURE__ */ x("h3", { className: "font-semibold mb-2", children: [
324
529
  "Selected Seats (",
325
- X.length,
530
+ T.length,
326
531
  ")"
327
532
  ] }),
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: [
329
- e.seatNumber,
330
- e.price && ` - ${V.currency} ${e.price.toFixed(2)}`
331
- ] }, e.id)) })
533
+ /* @__PURE__ */ i("div", { className: "max-h-48 overflow-y-auto space-y-1", children: T.map((t) => /* @__PURE__ */ x("div", { className: "text-sm", children: [
534
+ t.seatNumber,
535
+ t.price && ` - ${P.currency} ${t.price.toFixed(2)}`
536
+ ] }, t.id)) })
332
537
  ] })
333
- ] }) : /* @__PURE__ */ r("div", { className: `flex items-center justify-center h-full ${u}`, children: /* @__PURE__ */ r("p", { children: "No configuration provided" }) });
538
+ ] }) : /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${f}`, children: /* @__PURE__ */ i("p", { children: "No configuration provided" }) });
334
539
  };
335
540
  export {
336
- Fe as DEFAULT_COLORS,
337
- Ye as SeatMapViewer,
338
- Ne as useConfigFetcher
541
+ Zt as DEFAULT_COLORS,
542
+ Ut as SeatMapViewer,
543
+ Ot as useConfigFetcher
339
544
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonetrix/viewer",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "type": "module",
5
5
  "description": "Lightweight React component for rendering interactive seat maps",
6
6
  "main": "./dist/index.js",