@zonetrix/viewer 2.4.0 → 2.5.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
@@ -112,7 +112,7 @@ function BookingApp() {
112
112
  | `onSeatDeselect` | `(seat: SeatData) => void` | No | Callback when a seat is deselected |
113
113
  | `onSelectionChange` | `(seats: SeatData[]) => void` | No | Callback when selection changes |
114
114
  | `colorOverrides` | `Partial<ColorSettings>` | No | Custom colors for seat states |
115
- | `showTooltip` | `boolean` | No | Show tooltips on hover (default: true) |
115
+ | `showTooltip` | `boolean` | No | Show seat tooltips on hover with seat info, price, status (default: true) |
116
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 |
@@ -28,5 +28,6 @@ export interface SeatMapViewerProps {
28
28
  zoomControlsClassName?: string;
29
29
  maxZoom?: number;
30
30
  zoomStep?: number;
31
+ touchEnabled?: boolean;
31
32
  }
32
33
  export declare const SeatMapViewer: React.FC<SeatMapViewerProps>;
@@ -0,0 +1,11 @@
1
+ import { RefObject } from 'react';
2
+ interface ContainerSize {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ /**
7
+ * Hook to track the size of a container element using ResizeObserver.
8
+ * Returns { width: 0, height: 0 } until the container is mounted and measured.
9
+ */
10
+ export declare function useContainerSize(containerRef: RefObject<HTMLDivElement | null>): ContainerSize;
11
+ export {};
@@ -0,0 +1,25 @@
1
+ import { RefObject } from 'react';
2
+ import { default as Konva } from 'konva';
3
+ interface TouchGestureConfig {
4
+ enabled: boolean;
5
+ minScale: number;
6
+ maxScale: number;
7
+ currentScale: number;
8
+ currentPosition: {
9
+ x: number;
10
+ y: number;
11
+ };
12
+ onScaleChange: (scale: number, position: {
13
+ x: number;
14
+ y: number;
15
+ }) => void;
16
+ onPositionChange: (position: {
17
+ x: number;
18
+ y: number;
19
+ }) => void;
20
+ }
21
+ /**
22
+ * Hook to handle touch gestures (pinch-to-zoom and two-finger pan) on a Konva Stage.
23
+ */
24
+ export declare function useTouchGestures(stageRef: RefObject<Konva.Stage | null>, config: TouchGestureConfig): void;
25
+ export {};
package/dist/index.d.ts CHANGED
@@ -3,3 +3,5 @@ export type { SeatMapViewerProps } from './components/SeatMapViewer';
3
3
  export type { SeatState, SeatShape, SeatData, SerializedSeat, SerializedSection, SerializedStage, ColorSettings, SeatMapConfig, FloorConfig, } from './types';
4
4
  export { DEFAULT_COLORS } from './types';
5
5
  export { useConfigFetcher } from './hooks/useConfigFetcher';
6
+ export { useContainerSize } from './hooks/useContainerSize';
7
+ export { useTouchGestures } from './hooks/useTouchGestures';
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"),k=require("react-konva");function P(i){const[h,u]=t.useState(null),[m,S]=t.useState(!1),[C,x]=t.useState(null),b=async()=>{if(i){S(!0),x(null);try{const f=await fetch(i);if(!f.ok)throw new Error(`Failed to fetch config: ${f.statusText}`);const c=await f.json();u(c)}catch(f){const c=f instanceof Error?f:new Error("Unknown error occurred");x(c),console.error("Failed to fetch seat map config:",c)}finally{S(!1)}}};return t.useEffect(()=>{b()},[i]),{config:h,loading:m,error:C,refetch:b}}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:u,onClick:m})=>{const x={available:u.seatAvailable,reserved:u.seatReserved,selected:u.seatSelected,unavailable:u.seatUnavailable,hidden:u.seatHidden}[h],b=h==="available"||h==="selected",f=t.useCallback(()=>{b&&m(i)},[i,m,b]),c={x:i.position.x,y:i.position.y,fill:x,stroke:"#ffffff",strokeWidth:1,onClick:f,onTap:f};return i.shape==="circle"?o.jsx(k.Circle,{...c,radius:12}):o.jsx(k.Rect,{...c,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(k.Group,{x:i.position.x,y:i.position.y,children:[o.jsx(k.Rect,{width:i.config.width,height:i.config.height,fill:h+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),o.jsx(k.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 ee=t.memo(({floors:i,currentFloorId:h,onFloorChange:u,showAllOption:m,allLabel:S,position:C,className:x})=>{const b=t.useMemo(()=>[...i].sort((p,E)=>p.order-E.order),[i]),c={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}}[C]},M={padding:"6px 14px",fontSize:"14px",fontWeight:500,border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",transition:"all 0.2s ease"},y={...M,backgroundColor:"#3A7DE5",borderColor:"#3A7DE5"};return o.jsxs("div",{className:x,style:c,children:[m&&o.jsx("button",{type:"button",onClick:()=>u(null),style:h===null?y:M,children:S}),b.map(p=>o.jsx("button",{type:"button",onClick:()=>u(p.id),style:h===p.id?y:M,children:p.name},p.id))]})});ee.displayName="FloorSelectorBar";const te=t.memo(({scale:i,minScale:h,maxScale:u,onZoomIn:m,onZoomOut:S,position:C,className:x})=>{const f={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}}[C]},c={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"},M={...c,opacity:.4,cursor:"not-allowed"},y=i<u,p=i>h;return o.jsxs("div",{className:x,style:f,children:[o.jsx("button",{type:"button",onClick:m,disabled:!y,style:y?c:M,title:"Zoom In",children:"+"}),o.jsx("button",{type:"button",onClick:S,disabled:!p,style:p?c:M,title:"Zoom Out",children:"−"})]})});te.displayName="ZoomControls";const Ee=({config:i,configUrl:h,floorId:u,onFloorChange:m,reservedSeats:S=[],unavailableSeats:C=[],onSeatSelect:x,onSeatDeselect:b,onSelectionChange:f,colorOverrides:c,showTooltip:M=!0,zoomEnabled:y=!0,className:p="",onConfigLoad:E,onError:W,showFloorSelector:q,floorSelectorPosition:ne="top-left",floorSelectorClassName:oe,showAllFloorsOption:se=!0,allFloorsLabel:ie="All",fitToView:Y=!0,fitPadding:B=40,showZoomControls:ae=!0,zoomControlsPosition:re="bottom-right",zoomControlsClassName:le,maxZoom:F=3,zoomStep:O=.25})=>{const ce=t.useRef(null),[I,de]=t.useState(new Set),[g,H]=t.useState(1),[w,T]=t.useState({x:0,y:0}),[ue,fe]=t.useState(null),[U,K]=t.useState(!1),[he,pe]=t.useState(1),{config:ge,loading:xe,error:R}=P(h),n=i||ge,$=u!==void 0,v=$?u||null:ue,me=t.useCallback(e=>{$||fe(e),m?.(e)},[$,m]),Z=n?.floors||[],be=q!==void 0?q:Z.length>1,z=t.useMemo(()=>n?{...n.colors,...c}:{...G,...c},[n,c]),N=t.useMemo(()=>{if(!n)return[];let e=n.seats.filter(s=>s.state!=="hidden");return v&&(e=e.filter(s=>s.floorId===v||!s.floorId&&v==="floor_default")),e},[n,v]),D=t.useMemo(()=>n?.stages?v?n.stages.filter(e=>e.floorId===v||!e.floorId&&v==="floor_default"):n.stages:[],[n,v]),j=t.useMemo(()=>{if(!n||N.length===0&&D.length===0)return null;const e=12;let s=1/0,r=1/0,d=-1/0,l=-1/0;return N.forEach(a=>{s=Math.min(s,a.position.x-e),r=Math.min(r,a.position.y-e),d=Math.max(d,a.position.x+e),l=Math.max(l,a.position.y+e)}),D.forEach(a=>{s=Math.min(s,a.position.x),r=Math.min(r,a.position.y),d=Math.max(d,a.position.x+(a.config?.width||200)),l=Math.max(l,a.position.y+(a.config?.height||100))}),{minX:s,minY:r,maxX:d,maxY:l,width:d-s,height:l-r}},[n,N,D]);t.useEffect(()=>{if(!Y||U||!n||!j)return;const e=n.canvas.width,s=n.canvas.height,r=e-B*2,d=s-B*2,l=r/j.width,a=d/j.height,L=Math.min(l,a,F),we=j.minX+j.width/2,je=j.minY+j.height/2,ke=e/2,Me=s/2,Ie=ke-we*L,Ne=Me-je*L;H(L),T({x:Ie,y:Ne}),pe(L),K(!0)},[Y,U,n,j,B,F]),t.useEffect(()=>{Y&&K(!1)},[v,Y]);const A=t.useMemo(()=>{const e=new Set(S),s=new Set(C);return{reserved:e,unavailable:s}},[S,C]),V=t.useCallback(e=>{const s=e.id,r=e.seatNumber||"";return A.unavailable.has(s)||A.unavailable.has(r)?"unavailable":A.reserved.has(s)||A.reserved.has(r)?"reserved":I.has(s)?"selected":e.state},[A,I]);t.useEffect(()=>{n&&E&&E(n)},[n,E]),t.useEffect(()=>{R&&W&&W(R)},[R,W]);const ye=t.useCallback(e=>{const s=V(e);if(s!=="available"&&s!=="selected")return;const r=I.has(e.id);de(d=>{const l=new Set(d);return r?l.delete(e.id):l.add(e.id),l}),r?b?.(e):(x?.(e),x||console.log("Seat selected:",e))},[V,I,x,b]),X=t.useMemo(()=>n?N.filter(e=>I.has(e.id)):[],[N,I]);t.useEffect(()=>{f?.(X)},[X,f]);const _=he,Se=t.useCallback(()=>{if(!y)return;const e=Math.min(g+O,F);if(e!==g){const s=n?.canvas.width||800,r=n?.canvas.height||600,d=s/2,l=r/2,a={x:(d-w.x)/g,y:(l-w.y)/g};H(e),T({x:d-a.x*e,y:l-a.y*e})}},[y,g,O,F,n,w]),ve=t.useCallback(()=>{if(!y)return;const e=Math.max(g-O,_);if(e!==g){const s=n?.canvas.width||800,r=n?.canvas.height||600,d=s/2,l=r/2,a={x:(d-w.x)/g,y:(l-w.y)/g};H(e),T({x:d-a.x*e,y:l-a.y*e})}},[y,g,O,_,n,w]),Ce=t.useCallback(e=>{T({x:e.target.x(),y:e.target.y()})},[]);return xe?o.jsx("div",{className:`flex items-center justify-center h-full ${p}`,children:o.jsx("p",{children:"Loading seat map..."})}):R?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: ",R.message]})}):n?o.jsxs("div",{className:`relative ${p}`,children:[be&&Z.length>0&&o.jsx(ee,{floors:Z,currentFloorId:v,onFloorChange:me,showAllOption:se,allLabel:ie,position:ne,className:oe}),o.jsxs(k.Stage,{ref:ce,width:n.canvas.width,height:n.canvas.height,scaleX:g,scaleY:g,x:w.x,y:w.y,draggable:!0,onDragEnd:Ce,style:{backgroundColor:n.canvas.backgroundColor,cursor:"grab"},children:[o.jsx(k.Layer,{listening:!1,children:D.map(e=>o.jsx(Q,{stage:e,stageColor:z.stageColor},e.id))}),o.jsx(k.Layer,{children:N.map(e=>o.jsx(J,{seat:e,state:V(e),colors:z,onClick:ye},e.id))})]}),ae&&y&&o.jsx(te,{scale:g,minScale:_,maxScale:F,onZoomIn:Se,onZoomOut:ve,position:re,className:le}),X.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 (",X.length,")"]}),o.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:X.map(e=>o.jsxs("div",{className:"text-sm",children:[e.seatNumber,e.price&&` - ${z.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=Ee;exports.useConfigFetcher=P;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),e=require("react"),L=require("react-konva");function dt(n){const[i,d]=e.useState(null),[r,b]=e.useState(!1),[S,p]=e.useState(null),v=async()=>{if(n){b(!0),p(null);try{const y=await fetch(n);if(!y.ok)throw new Error(`Failed to fetch config: ${y.statusText}`);const a=await y.json();d(a)}catch(y){const a=y instanceof Error?y:new Error("Unknown error occurred");p(a),console.error("Failed to fetch seat map config:",a)}finally{b(!1)}}};return e.useEffect(()=>{v()},[n]),{config:i,loading:r,error:S,refetch:v}}function ft(n){const[i,d]=e.useState({width:0,height:0});return e.useEffect(()=>{const r=n.current;if(!r)return;const{width:b,height:S}=r.getBoundingClientRect();b>0&&S>0&&d({width:b,height:S});const p=new ResizeObserver(v=>{const y=v[0];if(!y)return;const{width:a,height:s}=y.contentRect;a>0&&s>0&&d(u=>u.width===a&&u.height===s?u:{width:a,height:s})});return p.observe(r),()=>{p.disconnect()}},[n]),i}function ut(n,i){return Math.sqrt(Math.pow(i.x-n.x,2)+Math.pow(i.y-n.y,2))}function ht(n,i){return{x:(n.x+i.x)/2,y:(n.y+i.y)/2}}function xt(n,i){const d=e.useRef(null),r=e.useRef(null),b=e.useRef(1);e.useEffect(()=>{const S=n.current;if(!S||!i.enabled)return;const p=S.container(),v=s=>{if(s.touches.length===2){s.preventDefault();const u={x:s.touches[0].clientX,y:s.touches[0].clientY},g={x:s.touches[1].clientX,y:s.touches[1].clientY};d.current=ut(u,g),r.current=ht(u,g),b.current=i.currentScale}},y=s=>{if(s.touches.length!==2)return;s.preventDefault();const u={x:s.touches[0].clientX,y:s.touches[0].clientY},g={x:s.touches[1].clientX,y:s.touches[1].clientY},k=ut(u,g),C=ht(u,g);if(d.current!==null&&r.current!==null){const R=k/d.current,P=Math.min(Math.max(i.currentScale*R,i.minScale),i.maxScale),V=p.getBoundingClientRect(),_=C.x-V.left,G=C.y-V.top,W=i.currentScale,T={x:(_-i.currentPosition.x)/W,y:(G-i.currentPosition.y)/W},et=C.x-r.current.x,nt=C.y-r.current.y,ot={x:_-T.x*P+et,y:G-T.y*P+nt};i.onScaleChange(P,ot),d.current=k,r.current=C}},a=s=>{s.touches.length<2&&(d.current=null,r.current=null)};return p.addEventListener("touchstart",v,{passive:!1}),p.addEventListener("touchmove",y,{passive:!1}),p.addEventListener("touchend",a),()=>{p.removeEventListener("touchstart",v),p.removeEventListener("touchmove",y),p.removeEventListener("touchend",a)}},[n,i])}const pt={canvasBackground:"#1a1a1a",stageColor:"#808080",seatAvailable:"#2C2B30",seatReserved:"#FCEA00",seatSelected:"#3A7DE5",seatUnavailable:"#6b7280",seatHidden:"#4a4a4a",gridLines:"#404040",currency:"KD"},gt=e.memo(({seat:n,state:i,colors:d,onClick:r,onMouseEnter:b,onMouseLeave:S})=>{const y={available:d.seatAvailable,reserved:d.seatReserved,selected:d.seatSelected,unavailable:d.seatUnavailable,hidden:d.seatHidden}[i],a=i==="available"||i==="selected",s=e.useCallback(()=>{a&&r(n)},[n,r,a]),u=e.useCallback(C=>{b(n,C);const R=C.target.getStage();R&&a&&(R.container().style.cursor="pointer")},[n,b,a]),g=e.useCallback(C=>{S();const R=C.target.getStage();R&&(R.container().style.cursor="grab")},[S]),k={x:n.position.x,y:n.position.y,fill:y,stroke:"#ffffff",strokeWidth:1,onClick:s,onTap:s,onMouseEnter:u,onMouseLeave:g};return n.shape==="circle"?o.jsx(L.Circle,{...k,radius:12}):o.jsx(L.Rect,{...k,width:24,height:24,offsetX:12,offsetY:12,cornerRadius:n.shape==="square"?0:4})});gt.displayName="ViewerSeat";const yt=e.memo(({stage:n,stageColor:i})=>o.jsxs(L.Group,{x:n.position.x,y:n.position.y,children:[o.jsx(L.Rect,{width:n.config.width,height:n.config.height,fill:i+"80",stroke:"#ffffff",strokeWidth:2,cornerRadius:10}),o.jsx(L.Text,{text:n.config.label,x:0,y:0,width:n.config.width,height:n.config.height,fontSize:24,fontStyle:"bold",fill:"#ffffff",align:"center",verticalAlign:"middle"})]}));yt.displayName="ViewerStage";const mt=e.memo(({floors:n,currentFloorId:i,onFloorChange:d,showAllOption:r,allLabel:b,position:S,className:p})=>{const v=e.useMemo(()=>[...n].sort((g,k)=>g.order-k.order),[n]),a={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}}[S]},s={padding:"10px 16px",fontSize:"14px",fontWeight:500,border:"1px solid #444",borderRadius:"6px",backgroundColor:"transparent",color:"#fff",cursor:"pointer",transition:"all 0.2s ease",minHeight:"44px",touchAction:"manipulation"},u={...s,backgroundColor:"#3A7DE5",borderColor:"#3A7DE5"};return o.jsxs("div",{className:p,style:a,children:[r&&o.jsx("button",{type:"button",onClick:()=>d(null),style:i===null?u:s,children:b}),v.map(g=>o.jsx("button",{type:"button",onClick:()=>d(g.id),style:i===g.id?u:s,children:g.name},g.id))]})});mt.displayName="FloorSelectorBar";const bt=e.memo(({scale:n,minScale:i,maxScale:d,onZoomIn:r,onZoomOut:b,position:S,className:p})=>{const y={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}}[S]},a={width:"44px",height:"44px",minWidth:"44px",minHeight:"44px",fontSize:"22px",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",touchAction:"manipulation"},s={...a,opacity:.4,cursor:"not-allowed"},u=n<d,g=n>i;return o.jsxs("div",{className:p,style:y,children:[o.jsx("button",{type:"button",onClick:r,disabled:!u,style:u?a:s,title:"Zoom In",children:"+"}),o.jsx("button",{type:"button",onClick:b,disabled:!g,style:g?a:s,title:"Zoom Out",children:"−"})]})});bt.displayName="ZoomControls";const St=e.memo(({visible:n,x:i,y:d,seat:r,currency:b,state:S})=>{if(!n||!r)return null;const p=r.seatNumber||(r.rowLabel&&r.columnLabel?`${r.rowLabel}-${r.columnLabel}`:"N/A"),v={position:"fixed",left:`${i+15}px`,top:`${d+15}px`,zIndex:1e3,pointerEvents:"none"},y={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"},a={color:"#9ca3af",marginRight:"4px"},s={fontWeight:600},u={color:"#4ade80",fontWeight:600},g={fontSize:"11px",color:"#6b7280",textTransform:"capitalize",marginTop:"4px"};return o.jsx("div",{style:v,children:o.jsxs("div",{style:y,children:[r.sectionName&&o.jsxs("div",{style:{marginBottom:"4px"},children:[o.jsx("span",{style:a,children:"Section:"}),o.jsx("span",{style:{...s,color:"#3b82f6"},children:r.sectionName})]}),o.jsxs("div",{style:{marginBottom:"4px"},children:[o.jsx("span",{style:a,children:"Seat:"}),o.jsx("span",{style:s,children:p})]}),r.price!==void 0&&r.price>0&&o.jsxs("div",{style:{marginBottom:"4px"},children:[o.jsx("span",{style:a,children:"Price:"}),o.jsxs("span",{style:u,children:[b," ",r.price.toFixed(2)]})]}),o.jsxs("div",{style:g,children:["Status: ",S]})]})})});St.displayName="SeatTooltip";const Vt=({config:n,configUrl:i,floorId:d,onFloorChange:r,reservedSeats:b=[],unavailableSeats:S=[],onSeatSelect:p,onSeatDeselect:v,onSelectionChange:y,colorOverrides:a,showTooltip:s=!0,zoomEnabled:u=!0,className:g="",onConfigLoad:k,onError:C,showFloorSelector:R,floorSelectorPosition:P="top-left",floorSelectorClassName:V,showAllFloorsOption:_=!0,allFloorsLabel:G="All",fitToView:W=!0,fitPadding:T=40,showZoomControls:et=!0,zoomControlsPosition:nt="bottom-right",zoomControlsClassName:ot,maxZoom:I=3,zoomStep:U=.25,touchEnabled:vt=!0})=>{const st=e.useRef(null),at=e.useRef(null),w=ft(at),[X,wt]=e.useState(new Set),[j,A]=e.useState(1),[M,D]=e.useState({x:0,y:0}),[Ct,jt]=e.useState(null),[Mt,kt]=e.useState(1),Z=e.useRef({width:0,height:0}),[B,lt]=e.useState({visible:!1,x:0,y:0,seat:null,state:"available"}),{config:Rt,loading:Et,error:z}=dt(i),l=n||Rt,it=d!==void 0,E=it?d||null:Ct,It=e.useCallback(t=>{it||jt(t),r?.(t)},[it,r]),rt=l?.floors||[],Nt=R!==void 0?R:rt.length>1,K=e.useMemo(()=>l?{...l.colors,...a}:{...pt,...a},[l,a]),Y=e.useMemo(()=>{if(!l)return[];let t=l.seats.filter(c=>c.state!=="hidden");return E&&(t=t.filter(c=>c.floorId===E||!c.floorId&&E==="floor_default")),t},[l,E]),J=e.useMemo(()=>l?.stages?E?l.stages.filter(t=>t.floorId===E||!t.floorId&&E==="floor_default"):l.stages:[],[l,E]),N=e.useMemo(()=>{if(!l||Y.length===0&&J.length===0)return null;const t=12;let c=1/0,f=1/0,h=-1/0,x=-1/0;return Y.forEach(m=>{c=Math.min(c,m.position.x-t),f=Math.min(f,m.position.y-t),h=Math.max(h,m.position.x+t),x=Math.max(x,m.position.y+t)}),J.forEach(m=>{c=Math.min(c,m.position.x),f=Math.min(f,m.position.y),h=Math.max(h,m.position.x+(m.config?.width||200)),x=Math.max(x,m.position.y+(m.config?.height||100))}),{minX:c,minY:f,maxX:h,maxY:x,width:h-c,height:x-f}},[l,Y,J]);e.useEffect(()=>{if(!W||!l||!N||w.width===0||w.height===0)return;const t=Math.abs(w.width-Z.current.width),c=Math.abs(w.height-Z.current.height);if(!(Z.current.width===0)&&t<10&&c<10)return;Z.current=w;const h=w.width,x=w.height,m=h-T*2,q=x-T*2,Q=m/N.width,ct=q/N.height,tt=Math.min(Q,ct,I),Bt=N.minX+N.width/2,zt=N.minY+N.height/2,$t=h/2,Ht=x/2,Ot=$t-Bt*tt,qt=Ht-zt*tt;A(tt),D({x:Ot,y:qt}),kt(tt)},[W,l,N,T,I,w,E]);const $=e.useMemo(()=>{const t=new Set(b),c=new Set(S);return{reserved:t,unavailable:c}},[b,S]),H=e.useCallback(t=>{const c=t.id,f=t.seatNumber||"";return $.unavailable.has(c)||$.unavailable.has(f)?"unavailable":$.reserved.has(c)||$.reserved.has(f)?"reserved":X.has(c)?"selected":t.state},[$,X]);e.useEffect(()=>{l&&k&&k(l)},[l,k]),e.useEffect(()=>{z&&C&&C(z)},[z,C]);const Lt=e.useCallback(t=>{const c=H(t);if(c!=="available"&&c!=="selected")return;const f=X.has(t.id);wt(h=>{const x=new Set(h);return f?x.delete(t.id):x.add(t.id),x}),f?v?.(t):(p?.(t),p||console.log("Seat selected:",t))},[H,X,p,v]),O=e.useMemo(()=>l?Y.filter(t=>X.has(t.id)):[],[Y,X]);e.useEffect(()=>{y?.(O)},[O,y]);const F=Mt,Dt=e.useCallback(()=>{if(!u)return;const t=Math.min(j+U,I);if(t!==j){const c=w.width||l?.canvas.width||800,f=w.height||l?.canvas.height||600,h=c/2,x=f/2,m={x:(h-M.x)/j,y:(x-M.y)/j};A(t),D({x:h-m.x*t,y:x-m.y*t})}},[u,j,U,I,w,l,M]),Tt=e.useCallback(()=>{if(!u)return;const t=Math.max(j-U,F);if(t!==j){const c=w.width||l?.canvas.width||800,f=w.height||l?.canvas.height||600,h=c/2,x=f/2,m={x:(h-M.x)/j,y:(x-M.y)/j};A(t),D({x:h-m.x*t,y:x-m.y*t})}},[u,j,U,F,w,l,M]),Xt=e.useCallback(t=>{D({x:t.target.x(),y:t.target.y()})},[]),Yt=e.useCallback(t=>{if(!u)return;t.evt.preventDefault();const c=st.current;if(!c)return;const f=c.scaleX(),h=c.getPointerPosition();if(!h)return;const x=1.1,m=t.evt.deltaY>0?f/x:f*x,q=Math.min(Math.max(m,F),I),Q={x:(h.x-M.x)/f,y:(h.y-M.y)/f},ct={x:h.x-Q.x*q,y:h.y-Q.y*q};A(q),D(ct)},[u,M,F,I]);xt(st,{enabled:vt&&u,minScale:F,maxScale:I,currentScale:j,currentPosition:M,onScaleChange:(t,c)=>{A(t),D(c)},onPositionChange:t=>{D(t)}});const Ft=e.useCallback((t,c)=>{if(!s)return;const f=c.target.getStage();if(!f)return;const h=f.getPointerPosition();if(!h)return;const x=f.container().getBoundingClientRect();lt({visible:!0,x:x.left+h.x,y:x.top+h.y,seat:t,state:H(t)})},[s,H]),Pt=e.useCallback(()=>{lt(t=>({...t,visible:!1}))},[]);if(Et)return o.jsx("div",{className:`flex items-center justify-center h-full ${g}`,children:o.jsx("p",{children:"Loading seat map..."})});if(z)return o.jsx("div",{className:`flex items-center justify-center h-full ${g}`,children:o.jsxs("p",{className:"text-red-500",children:["Error loading seat map: ",z.message]})});if(!l)return o.jsx("div",{className:`flex items-center justify-center h-full ${g}`,children:o.jsx("p",{children:"No configuration provided"})});const Wt=w.width||l.canvas.width,At=w.height||l.canvas.height;return o.jsxs("div",{ref:at,className:`relative ${g}`,style:{width:"100%",height:"100%"},children:[Nt&&rt.length>0&&o.jsx(mt,{floors:rt,currentFloorId:E,onFloorChange:It,showAllOption:_,allLabel:G,position:P,className:V}),o.jsxs(L.Stage,{ref:st,width:Wt,height:At,scaleX:j,scaleY:j,x:M.x,y:M.y,draggable:!0,onDragEnd:Xt,onWheel:Yt,style:{backgroundColor:l.canvas.backgroundColor,cursor:"grab"},children:[o.jsx(L.Layer,{listening:!1,children:J.map(t=>o.jsx(yt,{stage:t,stageColor:K.stageColor},t.id))}),o.jsx(L.Layer,{children:Y.map(t=>o.jsx(gt,{seat:t,state:H(t),colors:K,onClick:Lt,onMouseEnter:Ft,onMouseLeave:Pt},t.id))})]}),s&&o.jsx(St,{visible:B.visible,x:B.x,y:B.y,seat:B.seat,currency:K.currency,state:B.state}),et&&u&&o.jsx(bt,{scale:j,minScale:F,maxScale:I,onZoomIn:Dt,onZoomOut:Tt,position:nt,className:ot}),O.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 (",O.length,")"]}),o.jsx("div",{className:"max-h-48 overflow-y-auto space-y-1",children:O.map(t=>o.jsxs("div",{className:"text-sm",children:[t.seatNumber,t.price&&` - ${K.currency} ${t.price.toFixed(2)}`]},t.id))})]})]})};exports.DEFAULT_COLORS=pt;exports.SeatMapViewer=Vt;exports.useConfigFetcher=dt;exports.useContainerSize=ft;exports.useTouchGestures=xt;
package/dist/index.mjs CHANGED
@@ -1,34 +1,97 @@
1
- import { jsx as i, jsxs as w } from "react/jsx-runtime";
2
- import { useState as C, useEffect as Y, useRef as Et, useCallback as M, useMemo as F, memo as Z } from "react";
3
- import { Stage as Wt, Layer as tt, Group as jt, Rect as et, Text as Bt, Circle as Dt } from "react-konva";
4
- function Ht(o) {
5
- const [f, d] = C(null), [m, x] = C(!1), [S, g] = C(null), y = async () => {
6
- if (o) {
7
- x(!0), g(null);
1
+ import { jsx as h, jsxs as w } from "react/jsx-runtime";
2
+ import { useState as L, useEffect as W, useRef as z, useCallback as I, useMemo as T, memo as U } from "react";
3
+ import { Stage as Gt, Layer as gt, Group as Ut, Rect as mt, Text as Kt, Circle as Jt } from "react-konva";
4
+ function Qt(e) {
5
+ const [o, d] = L(null), [i, m] = L(!1), [b, p] = L(null), S = async () => {
6
+ if (e) {
7
+ m(!0), p(null);
8
8
  try {
9
- const h = await fetch(o);
10
- if (!h.ok)
11
- throw new Error(`Failed to fetch config: ${h.statusText}`);
12
- const l = await h.json();
13
- d(l);
14
- } catch (h) {
15
- const l = h instanceof Error ? h : new Error("Unknown error occurred");
16
- g(l), console.error("Failed to fetch seat map config:", l);
9
+ const y = await fetch(e);
10
+ if (!y.ok)
11
+ throw new Error(`Failed to fetch config: ${y.statusText}`);
12
+ const s = await y.json();
13
+ d(s);
14
+ } catch (y) {
15
+ const s = y instanceof Error ? y : new Error("Unknown error occurred");
16
+ p(s), console.error("Failed to fetch seat map config:", s);
17
17
  } finally {
18
- x(!1);
18
+ m(!1);
19
19
  }
20
20
  }
21
21
  };
22
- return Y(() => {
23
- y();
24
- }, [o]), {
25
- config: f,
26
- loading: m,
27
- error: S,
28
- refetch: y
22
+ return W(() => {
23
+ S();
24
+ }, [e]), {
25
+ config: o,
26
+ loading: i,
27
+ error: b,
28
+ refetch: S
29
29
  };
30
30
  }
31
- const Tt = {
31
+ function te(e) {
32
+ const [o, d] = L({ width: 0, height: 0 });
33
+ return W(() => {
34
+ const i = e.current;
35
+ if (!i) return;
36
+ const { width: m, height: b } = i.getBoundingClientRect();
37
+ m > 0 && b > 0 && d({ width: m, height: b });
38
+ const p = new ResizeObserver((S) => {
39
+ const y = S[0];
40
+ if (!y) return;
41
+ const { width: s, height: n } = y.contentRect;
42
+ s > 0 && n > 0 && d((a) => a.width === s && a.height === n ? a : { width: s, height: n });
43
+ });
44
+ return p.observe(i), () => {
45
+ p.disconnect();
46
+ };
47
+ }, [e]), o;
48
+ }
49
+ function yt(e, o) {
50
+ return Math.sqrt(Math.pow(o.x - e.x, 2) + Math.pow(o.y - e.y, 2));
51
+ }
52
+ function xt(e, o) {
53
+ return {
54
+ x: (e.x + o.x) / 2,
55
+ y: (e.y + o.y) / 2
56
+ };
57
+ }
58
+ function ee(e, o) {
59
+ const d = z(null), i = z(null), m = z(1);
60
+ W(() => {
61
+ const b = e.current;
62
+ if (!b || !o.enabled) return;
63
+ const p = b.container(), S = (n) => {
64
+ if (n.touches.length === 2) {
65
+ n.preventDefault();
66
+ const a = { x: n.touches[0].clientX, y: n.touches[0].clientY }, g = { x: n.touches[1].clientX, y: n.touches[1].clientY };
67
+ d.current = yt(a, g), i.current = xt(a, g), m.current = o.currentScale;
68
+ }
69
+ }, y = (n) => {
70
+ if (n.touches.length !== 2) return;
71
+ n.preventDefault();
72
+ const a = { x: n.touches[0].clientX, y: n.touches[0].clientY }, g = { x: n.touches[1].clientX, y: n.touches[1].clientY }, N = yt(a, g), C = xt(a, g);
73
+ if (d.current !== null && i.current !== null) {
74
+ const D = N / d.current, $ = Math.min(
75
+ Math.max(o.currentScale * D, o.minScale),
76
+ o.maxScale
77
+ ), K = p.getBoundingClientRect(), J = C.x - K.left, Q = C.y - K.top, H = o.currentScale, P = {
78
+ x: (J - o.currentPosition.x) / H,
79
+ y: (Q - o.currentPosition.y) / H
80
+ }, st = C.x - i.current.x, ct = C.y - i.current.y, at = {
81
+ x: J - P.x * $ + st,
82
+ y: Q - P.y * $ + ct
83
+ };
84
+ o.onScaleChange($, at), d.current = N, i.current = C;
85
+ }
86
+ }, s = (n) => {
87
+ n.touches.length < 2 && (d.current = null, i.current = null);
88
+ };
89
+ return p.addEventListener("touchstart", S, { passive: !1 }), p.addEventListener("touchmove", y, { passive: !1 }), p.addEventListener("touchend", s), () => {
90
+ p.removeEventListener("touchstart", S), p.removeEventListener("touchmove", y), p.removeEventListener("touchend", s);
91
+ };
92
+ }, [e, o]);
93
+ }
94
+ const ne = {
32
95
  canvasBackground: "#1a1a1a",
33
96
  stageColor: "#808080",
34
97
  seatAvailable: "#2C2B30",
@@ -38,64 +101,74 @@ const Tt = {
38
101
  seatHidden: "#4a4a4a",
39
102
  gridLines: "#404040",
40
103
  currency: "KD"
41
- }, nt = Z(({ seat: o, state: f, colors: d, onClick: m }) => {
42
- const g = {
104
+ }, bt = U(({ seat: e, state: o, colors: d, onClick: i, onMouseEnter: m, onMouseLeave: b }) => {
105
+ const y = {
43
106
  available: d.seatAvailable,
44
107
  reserved: d.seatReserved,
45
108
  selected: d.seatSelected,
46
109
  unavailable: d.seatUnavailable,
47
110
  hidden: d.seatHidden
48
111
  // Hidden seats are filtered out, but included for type safety
49
- }[f], y = f === "available" || f === "selected", h = M(() => {
50
- y && m(o);
51
- }, [o, m, y]), l = {
52
- x: o.position.x,
53
- y: o.position.y,
54
- fill: g,
112
+ }[o], s = o === "available" || o === "selected", n = I(() => {
113
+ s && i(e);
114
+ }, [e, i, s]), a = I((C) => {
115
+ m(e, C);
116
+ const D = C.target.getStage();
117
+ D && s && (D.container().style.cursor = "pointer");
118
+ }, [e, m, s]), g = I((C) => {
119
+ b();
120
+ const D = C.target.getStage();
121
+ D && (D.container().style.cursor = "grab");
122
+ }, [b]), N = {
123
+ x: e.position.x,
124
+ y: e.position.y,
125
+ fill: y,
55
126
  stroke: "#ffffff",
56
127
  strokeWidth: 1,
57
- onClick: h,
58
- onTap: h
128
+ onClick: n,
129
+ onTap: n,
130
+ onMouseEnter: a,
131
+ onMouseLeave: g
59
132
  };
60
- return o.shape === "circle" ? /* @__PURE__ */ i(
61
- Dt,
133
+ return e.shape === "circle" ? /* @__PURE__ */ h(
134
+ Jt,
62
135
  {
63
- ...l,
136
+ ...N,
64
137
  radius: 12
65
138
  }
66
- ) : /* @__PURE__ */ i(
67
- et,
139
+ ) : /* @__PURE__ */ h(
140
+ mt,
68
141
  {
69
- ...l,
142
+ ...N,
70
143
  width: 24,
71
144
  height: 24,
72
145
  offsetX: 12,
73
146
  offsetY: 12,
74
- cornerRadius: o.shape === "square" ? 0 : 4
147
+ cornerRadius: e.shape === "square" ? 0 : 4
75
148
  }
76
149
  );
77
150
  });
78
- nt.displayName = "ViewerSeat";
79
- const ot = Z(({ stage: o, stageColor: f }) => /* @__PURE__ */ w(jt, { x: o.position.x, y: o.position.y, children: [
80
- /* @__PURE__ */ i(
81
- et,
151
+ bt.displayName = "ViewerSeat";
152
+ const St = U(({ stage: e, stageColor: o }) => /* @__PURE__ */ w(Ut, { x: e.position.x, y: e.position.y, children: [
153
+ /* @__PURE__ */ h(
154
+ mt,
82
155
  {
83
- width: o.config.width,
84
- height: o.config.height,
85
- fill: f + "80",
156
+ width: e.config.width,
157
+ height: e.config.height,
158
+ fill: o + "80",
86
159
  stroke: "#ffffff",
87
160
  strokeWidth: 2,
88
161
  cornerRadius: 10
89
162
  }
90
163
  ),
91
- /* @__PURE__ */ i(
92
- Bt,
164
+ /* @__PURE__ */ h(
165
+ Kt,
93
166
  {
94
- text: o.config.label,
167
+ text: e.config.label,
95
168
  x: 0,
96
169
  y: 0,
97
- width: o.config.width,
98
- height: o.config.height,
170
+ width: e.config.width,
171
+ height: e.config.height,
99
172
  fontSize: 24,
100
173
  fontStyle: "bold",
101
174
  fill: "#ffffff",
@@ -104,20 +177,20 @@ const ot = Z(({ stage: o, stageColor: f }) => /* @__PURE__ */ w(jt, { x: o.posit
104
177
  }
105
178
  )
106
179
  ] }));
107
- ot.displayName = "ViewerStage";
108
- const it = Z(({
109
- floors: o,
110
- currentFloorId: f,
180
+ St.displayName = "ViewerStage";
181
+ const vt = U(({
182
+ floors: e,
183
+ currentFloorId: o,
111
184
  onFloorChange: d,
112
- showAllOption: m,
113
- allLabel: x,
114
- position: S,
115
- className: g
185
+ showAllOption: i,
186
+ allLabel: m,
187
+ position: b,
188
+ className: p
116
189
  }) => {
117
- const y = F(
118
- () => [...o].sort((u, A) => u.order - A.order),
119
- [o]
120
- ), l = {
190
+ const S = T(
191
+ () => [...e].sort((g, N) => g.order - N.order),
192
+ [e]
193
+ ), s = {
121
194
  position: "absolute",
122
195
  display: "flex",
123
196
  alignItems: "center",
@@ -132,9 +205,9 @@ const it = Z(({
132
205
  "top-right": { top: 0, right: 0 },
133
206
  "bottom-left": { bottom: 0, left: 0 },
134
207
  "bottom-right": { bottom: 0, right: 0 }
135
- }[S]
136
- }, N = {
137
- padding: "6px 14px",
208
+ }[b]
209
+ }, n = {
210
+ padding: "10px 16px",
138
211
  fontSize: "14px",
139
212
  fontWeight: 500,
140
213
  border: "1px solid #444",
@@ -142,45 +215,47 @@ const it = Z(({
142
215
  backgroundColor: "transparent",
143
216
  color: "#fff",
144
217
  cursor: "pointer",
145
- transition: "all 0.2s ease"
146
- }, b = {
147
- ...N,
218
+ transition: "all 0.2s ease",
219
+ minHeight: "44px",
220
+ touchAction: "manipulation"
221
+ }, a = {
222
+ ...n,
148
223
  backgroundColor: "#3A7DE5",
149
224
  borderColor: "#3A7DE5"
150
225
  };
151
- return /* @__PURE__ */ w("div", { className: g, style: l, children: [
152
- m && /* @__PURE__ */ i(
226
+ return /* @__PURE__ */ w("div", { className: p, style: s, children: [
227
+ i && /* @__PURE__ */ h(
153
228
  "button",
154
229
  {
155
230
  type: "button",
156
231
  onClick: () => d(null),
157
- style: f === null ? b : N,
158
- children: x
232
+ style: o === null ? a : n,
233
+ children: m
159
234
  }
160
235
  ),
161
- y.map((u) => /* @__PURE__ */ i(
236
+ S.map((g) => /* @__PURE__ */ h(
162
237
  "button",
163
238
  {
164
239
  type: "button",
165
- onClick: () => d(u.id),
166
- style: f === u.id ? b : N,
167
- children: u.name
240
+ onClick: () => d(g.id),
241
+ style: o === g.id ? a : n,
242
+ children: g.name
168
243
  },
169
- u.id
244
+ g.id
170
245
  ))
171
246
  ] });
172
247
  });
173
- it.displayName = "FloorSelectorBar";
174
- const st = Z(({
175
- scale: o,
176
- minScale: f,
248
+ vt.displayName = "FloorSelectorBar";
249
+ const wt = U(({
250
+ scale: e,
251
+ minScale: o,
177
252
  maxScale: d,
178
- onZoomIn: m,
179
- onZoomOut: x,
180
- position: S,
181
- className: g
253
+ onZoomIn: i,
254
+ onZoomOut: m,
255
+ position: b,
256
+ className: p
182
257
  }) => {
183
- const h = {
258
+ const y = {
184
259
  position: "absolute",
185
260
  display: "flex",
186
261
  flexDirection: "column",
@@ -195,11 +270,13 @@ const st = Z(({
195
270
  "top-right": { top: 0, right: 0 },
196
271
  "bottom-left": { bottom: 0, left: 0 },
197
272
  "bottom-right": { bottom: 0, right: 0 }
198
- }[S]
199
- }, l = {
200
- width: "36px",
201
- height: "36px",
202
- fontSize: "20px",
273
+ }[b]
274
+ }, s = {
275
+ width: "44px",
276
+ height: "44px",
277
+ minWidth: "44px",
278
+ minHeight: "44px",
279
+ fontSize: "22px",
203
280
  fontWeight: "bold",
204
281
  border: "1px solid #444",
205
282
  borderRadius: "6px",
@@ -209,235 +286,377 @@ const st = Z(({
209
286
  display: "flex",
210
287
  alignItems: "center",
211
288
  justifyContent: "center",
212
- transition: "all 0.2s ease"
213
- }, N = {
214
- ...l,
289
+ transition: "all 0.2s ease",
290
+ touchAction: "manipulation"
291
+ }, n = {
292
+ ...s,
215
293
  opacity: 0.4,
216
294
  cursor: "not-allowed"
217
- }, b = o < d, u = o > f;
218
- return /* @__PURE__ */ w("div", { className: g, style: h, children: [
219
- /* @__PURE__ */ i(
295
+ }, a = e < d, g = e > o;
296
+ return /* @__PURE__ */ w("div", { className: p, style: y, children: [
297
+ /* @__PURE__ */ h(
220
298
  "button",
221
299
  {
222
300
  type: "button",
223
- onClick: m,
224
- disabled: !b,
225
- style: b ? l : N,
301
+ onClick: i,
302
+ disabled: !a,
303
+ style: a ? s : n,
226
304
  title: "Zoom In",
227
305
  children: "+"
228
306
  }
229
307
  ),
230
- /* @__PURE__ */ i(
308
+ /* @__PURE__ */ h(
231
309
  "button",
232
310
  {
233
311
  type: "button",
234
- onClick: x,
235
- disabled: !u,
236
- style: u ? l : N,
312
+ onClick: m,
313
+ disabled: !g,
314
+ style: g ? s : n,
237
315
  title: "Zoom Out",
238
316
  children: "−"
239
317
  }
240
318
  )
241
319
  ] });
242
320
  });
243
- st.displayName = "ZoomControls";
244
- const zt = ({
245
- config: o,
246
- configUrl: f,
321
+ wt.displayName = "ZoomControls";
322
+ const Ct = U(({
323
+ visible: e,
324
+ x: o,
325
+ y: d,
326
+ seat: i,
327
+ currency: m,
328
+ state: b
329
+ }) => {
330
+ if (!e || !i) return null;
331
+ const p = i.seatNumber || (i.rowLabel && i.columnLabel ? `${i.rowLabel}-${i.columnLabel}` : "N/A"), S = {
332
+ position: "fixed",
333
+ left: `${o + 15}px`,
334
+ top: `${d + 15}px`,
335
+ zIndex: 1e3,
336
+ pointerEvents: "none"
337
+ }, y = {
338
+ backgroundColor: "rgba(26, 26, 26, 0.95)",
339
+ color: "#fff",
340
+ border: "1px solid #444",
341
+ borderRadius: "8px",
342
+ padding: "8px 12px",
343
+ fontSize: "13px",
344
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)",
345
+ minWidth: "140px"
346
+ }, s = {
347
+ color: "#9ca3af",
348
+ marginRight: "4px"
349
+ }, n = {
350
+ fontWeight: 600
351
+ }, a = {
352
+ color: "#4ade80",
353
+ fontWeight: 600
354
+ }, g = {
355
+ fontSize: "11px",
356
+ color: "#6b7280",
357
+ textTransform: "capitalize",
358
+ marginTop: "4px"
359
+ };
360
+ return /* @__PURE__ */ h("div", { style: S, children: /* @__PURE__ */ w("div", { style: y, children: [
361
+ i.sectionName && /* @__PURE__ */ w("div", { style: { marginBottom: "4px" }, children: [
362
+ /* @__PURE__ */ h("span", { style: s, children: "Section:" }),
363
+ /* @__PURE__ */ h("span", { style: { ...n, color: "#3b82f6" }, children: i.sectionName })
364
+ ] }),
365
+ /* @__PURE__ */ w("div", { style: { marginBottom: "4px" }, children: [
366
+ /* @__PURE__ */ h("span", { style: s, children: "Seat:" }),
367
+ /* @__PURE__ */ h("span", { style: n, children: p })
368
+ ] }),
369
+ i.price !== void 0 && i.price > 0 && /* @__PURE__ */ w("div", { style: { marginBottom: "4px" }, children: [
370
+ /* @__PURE__ */ h("span", { style: s, children: "Price:" }),
371
+ /* @__PURE__ */ w("span", { style: a, children: [
372
+ m,
373
+ " ",
374
+ i.price.toFixed(2)
375
+ ] })
376
+ ] }),
377
+ /* @__PURE__ */ w("div", { style: g, children: [
378
+ "Status: ",
379
+ b
380
+ ] })
381
+ ] }) });
382
+ });
383
+ Ct.displayName = "SeatTooltip";
384
+ const se = ({
385
+ config: e,
386
+ configUrl: o,
247
387
  floorId: d,
248
- onFloorChange: m,
249
- reservedSeats: x = [],
250
- unavailableSeats: S = [],
251
- onSeatSelect: g,
252
- onSeatDeselect: y,
253
- onSelectionChange: h,
254
- colorOverrides: l,
255
- showTooltip: N = !0,
256
- zoomEnabled: b = !0,
257
- className: u = "",
258
- onConfigLoad: A,
259
- onError: z,
388
+ onFloorChange: i,
389
+ reservedSeats: m = [],
390
+ unavailableSeats: b = [],
391
+ onSeatSelect: p,
392
+ onSeatDeselect: S,
393
+ onSelectionChange: y,
394
+ colorOverrides: s,
395
+ showTooltip: n = !0,
396
+ zoomEnabled: a = !0,
397
+ className: g = "",
398
+ onConfigLoad: N,
399
+ onError: C,
260
400
  // Floor selector props
261
- showFloorSelector: P,
262
- floorSelectorPosition: rt = "top-left",
263
- floorSelectorClassName: at,
264
- showAllFloorsOption: lt = !0,
265
- allFloorsLabel: ct = "All",
266
- fitToView: D = !0,
267
- fitPadding: L = 40,
401
+ showFloorSelector: D,
402
+ floorSelectorPosition: $ = "top-left",
403
+ floorSelectorClassName: K,
404
+ showAllFloorsOption: J = !0,
405
+ allFloorsLabel: Q = "All",
406
+ fitToView: H = !0,
407
+ fitPadding: P = 40,
268
408
  // Zoom controls
269
- showZoomControls: dt = !0,
270
- zoomControlsPosition: ht = "bottom-right",
271
- zoomControlsClassName: ft,
272
- maxZoom: E = 3,
273
- zoomStep: H = 0.25
409
+ showZoomControls: st = !0,
410
+ zoomControlsPosition: ct = "bottom-right",
411
+ zoomControlsClassName: at,
412
+ maxZoom: X = 3,
413
+ zoomStep: tt = 0.25,
414
+ // Touch gestures
415
+ touchEnabled: Mt = !0
274
416
  }) => {
275
- const ut = Et(null), [R, pt] = C(/* @__PURE__ */ new Set()), [p, V] = C(1), [k, T] = C({ x: 0, y: 0 }), [gt, mt] = C(null), [J, Q] = C(!1), [yt, bt] = C(1), { config: xt, loading: vt, error: W } = Ht(f), e = o || xt, _ = d !== void 0, v = _ ? d || null : gt, St = M((t) => {
276
- _ || mt(t), m?.(t);
277
- }, [_, m]), U = e?.floors || [], wt = P !== void 0 ? P : U.length > 1, q = F(
278
- () => e ? { ...e.colors, ...l } : { ...Tt, ...l },
279
- [e, l]
280
- ), X = F(() => {
281
- if (!e) return [];
282
- let t = e.seats.filter((n) => n.state !== "hidden");
283
- return v && (t = t.filter(
284
- (n) => n.floorId === v || !n.floorId && v === "floor_default"
417
+ const lt = z(null), ft = z(null), v = te(ft), [B, kt] = L(/* @__PURE__ */ new Set()), [M, j] = L(1), [k, E] = L({ x: 0, y: 0 }), [It, Nt] = L(null), [Dt, Rt] = L(1), et = z({ width: 0, height: 0 }), [O, pt] = L({ visible: !1, x: 0, y: 0, seat: null, state: "available" }), { config: Lt, loading: Xt, error: V } = Qt(o), c = e || Lt, ht = d !== void 0, R = ht ? d || null : It, Yt = I((t) => {
418
+ ht || Nt(t), i?.(t);
419
+ }, [ht, i]), dt = c?.floors || [], Et = D !== void 0 ? D : dt.length > 1, nt = T(
420
+ () => c ? { ...c.colors, ...s } : { ...ne, ...s },
421
+ [c, s]
422
+ ), F = T(() => {
423
+ if (!c) return [];
424
+ let t = c.seats.filter((r) => r.state !== "hidden");
425
+ return R && (t = t.filter(
426
+ (r) => r.floorId === R || !r.floorId && R === "floor_default"
285
427
  )), t;
286
- }, [e, v]), $ = F(() => e?.stages ? v ? e.stages.filter(
287
- (t) => t.floorId === v || !t.floorId && v === "floor_default"
288
- ) : e.stages : [], [e, v]), I = F(() => {
289
- if (!e || X.length === 0 && $.length === 0)
428
+ }, [c, R]), ot = T(() => c?.stages ? R ? c.stages.filter(
429
+ (t) => t.floorId === R || !t.floorId && R === "floor_default"
430
+ ) : c.stages : [], [c, R]), Y = T(() => {
431
+ if (!c || F.length === 0 && ot.length === 0)
290
432
  return null;
291
433
  const t = 12;
292
- let n = 1 / 0, r = 1 / 0, c = -1 / 0, a = -1 / 0;
293
- return X.forEach((s) => {
294
- n = Math.min(n, s.position.x - t), r = Math.min(r, s.position.y - t), c = Math.max(c, s.position.x + t), a = Math.max(a, s.position.y + t);
295
- }), $.forEach((s) => {
296
- n = Math.min(n, s.position.x), r = Math.min(r, s.position.y), c = Math.max(c, s.position.x + (s.config?.width || 200)), a = Math.max(a, s.position.y + (s.config?.height || 100));
297
- }), { minX: n, minY: r, maxX: c, maxY: a, width: c - n, height: a - r };
298
- }, [e, X, $]);
299
- Y(() => {
300
- if (!D || J || !e || !I) return;
301
- const t = e.canvas.width, n = e.canvas.height, r = t - L * 2, c = n - L * 2, a = r / I.width, s = c / I.height, O = Math.min(a, s, E), Mt = I.minX + I.width / 2, Ft = I.minY + I.height / 2, Rt = t / 2, Xt = n / 2, Yt = Rt - Mt * O, At = Xt - Ft * O;
302
- V(O), T({ x: Yt, y: At }), bt(O), Q(!0);
303
- }, [D, J, e, I, L, E]), Y(() => {
304
- D && Q(!1);
305
- }, [v, D]);
306
- const j = F(() => {
307
- const t = new Set(x), n = new Set(S);
308
- return { reserved: t, unavailable: n };
309
- }, [x, S]), G = M((t) => {
310
- const n = t.id, r = t.seatNumber || "";
311
- return j.unavailable.has(n) || j.unavailable.has(r) ? "unavailable" : j.reserved.has(n) || j.reserved.has(r) ? "reserved" : R.has(n) ? "selected" : t.state;
312
- }, [j, R]);
313
- Y(() => {
314
- e && A && A(e);
315
- }, [e, A]), Y(() => {
316
- W && z && z(W);
317
- }, [W, z]);
318
- const Ct = M((t) => {
319
- const n = G(t);
320
- if (n !== "available" && n !== "selected")
434
+ let r = 1 / 0, u = 1 / 0, l = -1 / 0, f = -1 / 0;
435
+ return F.forEach((x) => {
436
+ r = Math.min(r, x.position.x - t), u = Math.min(u, x.position.y - t), l = Math.max(l, x.position.x + t), f = Math.max(f, x.position.y + t);
437
+ }), ot.forEach((x) => {
438
+ r = Math.min(r, x.position.x), u = Math.min(u, x.position.y), l = Math.max(l, x.position.x + (x.config?.width || 200)), f = Math.max(f, x.position.y + (x.config?.height || 100));
439
+ }), { minX: r, minY: u, maxX: l, maxY: f, width: l - r, height: f - u };
440
+ }, [c, F, ot]);
441
+ W(() => {
442
+ if (!H || !c || !Y || v.width === 0 || v.height === 0) return;
443
+ const t = Math.abs(v.width - et.current.width), r = Math.abs(v.height - et.current.height);
444
+ if (!(et.current.width === 0) && t < 10 && r < 10) return;
445
+ et.current = v;
446
+ const l = v.width, f = v.height, x = l - P * 2, G = f - P * 2, it = x / Y.width, ut = G / Y.height, rt = Math.min(it, ut, X), jt = Y.minX + Y.width / 2, Ot = Y.minY + Y.height / 2, Vt = l / 2, Zt = f / 2, _t = Vt - jt * rt, qt = Zt - Ot * rt;
447
+ j(rt), E({ x: _t, y: qt }), Rt(rt);
448
+ }, [H, c, Y, P, X, v, R]);
449
+ const Z = T(() => {
450
+ const t = new Set(m), r = new Set(b);
451
+ return { reserved: t, unavailable: r };
452
+ }, [m, b]), _ = I((t) => {
453
+ const r = t.id, u = t.seatNumber || "";
454
+ return Z.unavailable.has(r) || Z.unavailable.has(u) ? "unavailable" : Z.reserved.has(r) || Z.reserved.has(u) ? "reserved" : B.has(r) ? "selected" : t.state;
455
+ }, [Z, B]);
456
+ W(() => {
457
+ c && N && N(c);
458
+ }, [c, N]), W(() => {
459
+ V && C && C(V);
460
+ }, [V, C]);
461
+ const Tt = I((t) => {
462
+ const r = _(t);
463
+ if (r !== "available" && r !== "selected")
321
464
  return;
322
- const r = R.has(t.id);
323
- pt((c) => {
324
- const a = new Set(c);
325
- return r ? a.delete(t.id) : a.add(t.id), a;
326
- }), r ? y?.(t) : (g?.(t), g || console.log("Seat selected:", t));
327
- }, [G, R, g, y]), B = F(() => e ? X.filter((t) => R.has(t.id)) : [], [X, R]);
328
- Y(() => {
329
- h?.(B);
330
- }, [B, h]);
331
- const K = yt, kt = M(() => {
332
- if (!b) return;
333
- const t = Math.min(p + H, E);
334
- if (t !== p) {
335
- const n = e?.canvas.width || 800, r = e?.canvas.height || 600, c = n / 2, a = r / 2, s = {
336
- x: (c - k.x) / p,
337
- y: (a - k.y) / p
465
+ const u = B.has(t.id);
466
+ kt((l) => {
467
+ const f = new Set(l);
468
+ return u ? f.delete(t.id) : f.add(t.id), f;
469
+ }), u ? S?.(t) : (p?.(t), p || console.log("Seat selected:", t));
470
+ }, [_, B, p, S]), q = T(() => c ? F.filter((t) => B.has(t.id)) : [], [F, B]);
471
+ W(() => {
472
+ y?.(q);
473
+ }, [q, y]);
474
+ const A = Dt, Wt = I(() => {
475
+ if (!a) return;
476
+ const t = Math.min(M + tt, X);
477
+ if (t !== M) {
478
+ const r = v.width || c?.canvas.width || 800, u = v.height || c?.canvas.height || 600, l = r / 2, f = u / 2, x = {
479
+ x: (l - k.x) / M,
480
+ y: (f - k.y) / M
338
481
  };
339
- V(t), T({
340
- x: c - s.x * t,
341
- y: a - s.y * t
482
+ j(t), E({
483
+ x: l - x.x * t,
484
+ y: f - x.y * t
342
485
  });
343
486
  }
344
- }, [b, p, H, E, e, k]), It = M(() => {
345
- if (!b) return;
346
- const t = Math.max(p - H, K);
347
- if (t !== p) {
348
- const n = e?.canvas.width || 800, r = e?.canvas.height || 600, c = n / 2, a = r / 2, s = {
349
- x: (c - k.x) / p,
350
- y: (a - k.y) / p
487
+ }, [a, M, tt, X, v, c, k]), Pt = I(() => {
488
+ if (!a) return;
489
+ const t = Math.max(M - tt, A);
490
+ if (t !== M) {
491
+ const r = v.width || c?.canvas.width || 800, u = v.height || c?.canvas.height || 600, l = r / 2, f = u / 2, x = {
492
+ x: (l - k.x) / M,
493
+ y: (f - k.y) / M
351
494
  };
352
- V(t), T({
353
- x: c - s.x * t,
354
- y: a - s.y * t
495
+ j(t), E({
496
+ x: l - x.x * t,
497
+ y: f - x.y * t
355
498
  });
356
499
  }
357
- }, [b, p, H, K, e, k]), Nt = M((t) => {
358
- T({
500
+ }, [a, M, tt, A, v, c, k]), Bt = I((t) => {
501
+ E({
359
502
  x: t.target.x(),
360
503
  y: t.target.y()
361
504
  });
505
+ }, []), Ft = I((t) => {
506
+ if (!a) return;
507
+ t.evt.preventDefault();
508
+ const r = lt.current;
509
+ if (!r) return;
510
+ const u = r.scaleX(), l = r.getPointerPosition();
511
+ if (!l) return;
512
+ const f = 1.1, x = t.evt.deltaY > 0 ? u / f : u * f, G = Math.min(Math.max(x, A), X), it = {
513
+ x: (l.x - k.x) / u,
514
+ y: (l.y - k.y) / u
515
+ }, ut = {
516
+ x: l.x - it.x * G,
517
+ y: l.y - it.y * G
518
+ };
519
+ j(G), E(ut);
520
+ }, [a, k, A, X]);
521
+ ee(lt, {
522
+ enabled: Mt && a,
523
+ minScale: A,
524
+ maxScale: X,
525
+ currentScale: M,
526
+ currentPosition: k,
527
+ onScaleChange: (t, r) => {
528
+ j(t), E(r);
529
+ },
530
+ onPositionChange: (t) => {
531
+ E(t);
532
+ }
533
+ });
534
+ const At = I((t, r) => {
535
+ if (!n) return;
536
+ const u = r.target.getStage();
537
+ if (!u) return;
538
+ const l = u.getPointerPosition();
539
+ if (!l) return;
540
+ const f = u.container().getBoundingClientRect();
541
+ pt({
542
+ visible: !0,
543
+ x: f.left + l.x,
544
+ y: f.top + l.y,
545
+ seat: t,
546
+ state: _(t)
547
+ });
548
+ }, [n, _]), zt = I(() => {
549
+ pt((t) => ({ ...t, visible: !1 }));
362
550
  }, []);
363
- return vt ? /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${u}`, children: /* @__PURE__ */ i("p", { children: "Loading seat map..." }) }) : W ? /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${u}`, children: /* @__PURE__ */ w("p", { className: "text-red-500", children: [
364
- "Error loading seat map: ",
365
- W.message
366
- ] }) }) : e ? /* @__PURE__ */ w("div", { className: `relative ${u}`, children: [
367
- wt && U.length > 0 && /* @__PURE__ */ i(
368
- it,
369
- {
370
- floors: U,
371
- currentFloorId: v,
372
- onFloorChange: St,
373
- showAllOption: lt,
374
- allLabel: ct,
375
- position: rt,
376
- className: at
377
- }
378
- ),
379
- /* @__PURE__ */ w(
380
- Wt,
381
- {
382
- ref: ut,
383
- width: e.canvas.width,
384
- height: e.canvas.height,
385
- scaleX: p,
386
- scaleY: p,
387
- x: k.x,
388
- y: k.y,
389
- draggable: !0,
390
- onDragEnd: Nt,
391
- style: { backgroundColor: e.canvas.backgroundColor, cursor: "grab" },
392
- children: [
393
- /* @__PURE__ */ i(tt, { listening: !1, children: $.map((t) => /* @__PURE__ */ i(
394
- ot,
395
- {
396
- stage: t,
397
- stageColor: q.stageColor
398
- },
399
- t.id
400
- )) }),
401
- /* @__PURE__ */ i(tt, { children: X.map((t) => /* @__PURE__ */ i(
402
- nt,
403
- {
404
- seat: t,
405
- state: G(t),
406
- colors: q,
407
- onClick: Ct
408
- },
409
- t.id
410
- )) })
411
- ]
412
- }
413
- ),
414
- dt && b && /* @__PURE__ */ i(
415
- st,
416
- {
417
- scale: p,
418
- minScale: K,
419
- maxScale: E,
420
- onZoomIn: kt,
421
- onZoomOut: It,
422
- position: ht,
423
- className: ft
424
- }
425
- ),
426
- B.length > 0 && /* @__PURE__ */ w("div", { className: "absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg", children: [
427
- /* @__PURE__ */ w("h3", { className: "font-semibold mb-2", children: [
428
- "Selected Seats (",
429
- B.length,
430
- ")"
431
- ] }),
432
- /* @__PURE__ */ i("div", { className: "max-h-48 overflow-y-auto space-y-1", children: B.map((t) => /* @__PURE__ */ w("div", { className: "text-sm", children: [
433
- t.seatNumber,
434
- t.price && ` - ${q.currency} ${t.price.toFixed(2)}`
435
- ] }, t.id)) })
436
- ] })
437
- ] }) : /* @__PURE__ */ i("div", { className: `flex items-center justify-center h-full ${u}`, children: /* @__PURE__ */ i("p", { children: "No configuration provided" }) });
551
+ if (Xt)
552
+ return /* @__PURE__ */ h("div", { className: `flex items-center justify-center h-full ${g}`, children: /* @__PURE__ */ h("p", { children: "Loading seat map..." }) });
553
+ if (V)
554
+ return /* @__PURE__ */ h("div", { className: `flex items-center justify-center h-full ${g}`, children: /* @__PURE__ */ w("p", { className: "text-red-500", children: [
555
+ "Error loading seat map: ",
556
+ V.message
557
+ ] }) });
558
+ if (!c)
559
+ return /* @__PURE__ */ h("div", { className: `flex items-center justify-center h-full ${g}`, children: /* @__PURE__ */ h("p", { children: "No configuration provided" }) });
560
+ const $t = v.width || c.canvas.width, Ht = v.height || c.canvas.height;
561
+ return /* @__PURE__ */ w(
562
+ "div",
563
+ {
564
+ ref: ft,
565
+ className: `relative ${g}`,
566
+ style: { width: "100%", height: "100%" },
567
+ children: [
568
+ Et && dt.length > 0 && /* @__PURE__ */ h(
569
+ vt,
570
+ {
571
+ floors: dt,
572
+ currentFloorId: R,
573
+ onFloorChange: Yt,
574
+ showAllOption: J,
575
+ allLabel: Q,
576
+ position: $,
577
+ className: K
578
+ }
579
+ ),
580
+ /* @__PURE__ */ w(
581
+ Gt,
582
+ {
583
+ ref: lt,
584
+ width: $t,
585
+ height: Ht,
586
+ scaleX: M,
587
+ scaleY: M,
588
+ x: k.x,
589
+ y: k.y,
590
+ draggable: !0,
591
+ onDragEnd: Bt,
592
+ onWheel: Ft,
593
+ style: { backgroundColor: c.canvas.backgroundColor, cursor: "grab" },
594
+ children: [
595
+ /* @__PURE__ */ h(gt, { listening: !1, children: ot.map((t) => /* @__PURE__ */ h(
596
+ St,
597
+ {
598
+ stage: t,
599
+ stageColor: nt.stageColor
600
+ },
601
+ t.id
602
+ )) }),
603
+ /* @__PURE__ */ h(gt, { children: F.map((t) => /* @__PURE__ */ h(
604
+ bt,
605
+ {
606
+ seat: t,
607
+ state: _(t),
608
+ colors: nt,
609
+ onClick: Tt,
610
+ onMouseEnter: At,
611
+ onMouseLeave: zt
612
+ },
613
+ t.id
614
+ )) })
615
+ ]
616
+ }
617
+ ),
618
+ n && /* @__PURE__ */ h(
619
+ Ct,
620
+ {
621
+ visible: O.visible,
622
+ x: O.x,
623
+ y: O.y,
624
+ seat: O.seat,
625
+ currency: nt.currency,
626
+ state: O.state
627
+ }
628
+ ),
629
+ st && a && /* @__PURE__ */ h(
630
+ wt,
631
+ {
632
+ scale: M,
633
+ minScale: A,
634
+ maxScale: X,
635
+ onZoomIn: Wt,
636
+ onZoomOut: Pt,
637
+ position: ct,
638
+ className: at
639
+ }
640
+ ),
641
+ q.length > 0 && /* @__PURE__ */ w("div", { className: "absolute top-4 right-4 bg-white dark:bg-gray-800 p-4 rounded shadow-lg", children: [
642
+ /* @__PURE__ */ w("h3", { className: "font-semibold mb-2", children: [
643
+ "Selected Seats (",
644
+ q.length,
645
+ ")"
646
+ ] }),
647
+ /* @__PURE__ */ h("div", { className: "max-h-48 overflow-y-auto space-y-1", children: q.map((t) => /* @__PURE__ */ w("div", { className: "text-sm", children: [
648
+ t.seatNumber,
649
+ t.price && ` - ${nt.currency} ${t.price.toFixed(2)}`
650
+ ] }, t.id)) })
651
+ ] })
652
+ ]
653
+ }
654
+ );
438
655
  };
439
656
  export {
440
- Tt as DEFAULT_COLORS,
441
- zt as SeatMapViewer,
442
- Ht as useConfigFetcher
657
+ ne as DEFAULT_COLORS,
658
+ se as SeatMapViewer,
659
+ Qt as useConfigFetcher,
660
+ te as useContainerSize,
661
+ ee as useTouchGestures
443
662
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonetrix/viewer",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "type": "module",
5
5
  "description": "Lightweight React component for rendering interactive seat maps",
6
6
  "main": "./dist/index.js",