From edeaa09ae8e4698a163ee8281be363f6a68c1d56 Mon Sep 17 00:00:00 2001 From: SrujanaSaka20 Date: Fri, 10 Jan 2025 14:55:25 +0530 Subject: [PATCH] Ntrfcs 42 : Zoom, Panning and Mobile click issue (#299) * restrict infinity zooming and panning, click issue in mobile view * changeset changes * fixed lint issues and testcase issues --- .changeset/three-ladybugs-tie.md | 5 ++ .../seatmaps-client/src/TicketMap/index.tsx | 29 ++++++++- .../src/__tests__/utils/zoom.test.ts | 28 ++++----- packages/seatmaps-client/src/utils/zoom.ts | 63 ++++++++++++++++--- 4 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 .changeset/three-ladybugs-tie.md diff --git a/.changeset/three-ladybugs-tie.md b/.changeset/three-ladybugs-tie.md new file mode 100644 index 0000000..e7c5906 --- /dev/null +++ b/.changeset/three-ladybugs-tie.md @@ -0,0 +1,5 @@ +--- +"@ticketevolution/seatmaps-client": patch +--- + +Fix mobile click issues and restrict infinite zooming and panning behavior. Limit zoom in to 10% of the original size and prevent zooming out beyond the original size. diff --git a/packages/seatmaps-client/src/TicketMap/index.tsx b/packages/seatmaps-client/src/TicketMap/index.tsx index 11f97d8..f43d84e 100644 --- a/packages/seatmaps-client/src/TicketMap/index.tsx +++ b/packages/seatmaps-client/src/TicketMap/index.tsx @@ -461,11 +461,19 @@ export class TicketMap extends Component { * Interaction Callbacks */ - onMouseOver = ({ clientX, clientY, target }: React.MouseEvent) => + onMouseOver = ({ + clientX, + clientY, + target, + }: React.MouseEvent) => { + if (this.state.isTouchDevice) return; this.doHover(target as HTMLElement, clientX, clientY); + }; - onMouseOut = ({ relatedTarget }: React.MouseEvent) => + onMouseOut = ({ relatedTarget }: React.MouseEvent) => { + if (this.state.isTouchDevice) return; this.doHoverCleanup(relatedTarget as HTMLElement); + }; onMouseMove = ({ nativeEvent }: React.MouseEvent) => this.setState({ @@ -475,15 +483,29 @@ export class TicketMap extends Component { onClick = () => this.doSelect(); + onTouchStart = (e: React.TouchEvent) => { + const section = this.getSectionFromTarget(e.target as HTMLElement); + if (section) { + this.setState({ + currentHoveredSection: section, + }); + this.highlightSection(section); + } + }; + onTouchMove = () => { this.setState({ dragging: true }); }; onTouchEnd = (e: React.TouchEvent) => { + const section = this.state.currentHoveredSection; + if (section) { + this.doSelect(section); + this.setState({ currentHoveredSection: undefined }); + } if (this.state.dragging) { e.preventDefault(); } - this.setState({ dragging: false }); }; @@ -602,6 +624,7 @@ export class TicketMap extends Component { onMouseOut={this.onMouseOut} onMouseMove={this.onMouseMove} onClick={this.onClick} + onTouchStart={this.onTouchStart} onTouchEnd={this.onTouchEnd} onTouchMove={this.onTouchMove} style={{ diff --git a/packages/seatmaps-client/src/__tests__/utils/zoom.test.ts b/packages/seatmaps-client/src/__tests__/utils/zoom.test.ts index e4f131c..cd8713f 100644 --- a/packages/seatmaps-client/src/__tests__/utils/zoom.test.ts +++ b/packages/seatmaps-client/src/__tests__/utils/zoom.test.ts @@ -181,18 +181,18 @@ describe("zoom", () => { it("zomms out", () => { zoomApi.zoomOut(0.1); - expect(mockPoint.height).toBe(11); - expect(mockPoint.width).toBe(11); - expect(mockPoint.x).toBe(9.5); - expect(mockPoint.y).toBe(9.5); + expect(mockPoint.height).toBe(10); + expect(mockPoint.width).toBe(10); + expect(mockPoint.x).toBe(10); + expect(mockPoint.y).toBe(10); }); it("resets to original position after zoom", () => { zoomApi.zoomOut(0.1); - expect(mockPoint.height).toBe(11); - expect(mockPoint.width).toBe(11); - expect(mockPoint.x).toBe(9.5); - expect(mockPoint.y).toBe(9.5); + expect(mockPoint.height).toBe(10); + expect(mockPoint.width).toBe(10); + expect(mockPoint.x).toBe(10); + expect(mockPoint.y).toBe(10); zoomApi.reset(); expect(mockPoint.height).toBe(10); @@ -246,8 +246,8 @@ describe("zoom", () => { expect(mockPoint.height).toBe(10); expect(mockPoint.width).toBe(10); - expect(mockPoint.x).toBe(12); - expect(mockPoint.y).toBe(12); + expect(mockPoint.x).toBe(10); + expect(mockPoint.y).toBe(10); }); it("handleWheel when ctrl key is not pressed and delta mode is DOM_DELTA_LINE", () => { @@ -275,10 +275,10 @@ describe("zoom", () => { } as unknown as Event; triggerEvent("wheel", e); - expect(mockPoint.height).toBe(10.651041666666668); - expect(mockPoint.width).toBe(10.48828125); - expect(mockPoint.x).toBe(9.755859375); - expect(mockPoint.y).toBe(9.674479166666666); + expect(mockPoint.height).toBe(10); + expect(mockPoint.width).toBe(10); + expect(mockPoint.x).toBe(10); + expect(mockPoint.y).toBe(10); }); it("handleWheel when ctrl key is pressed and delta mode is DOM_DELTA_LINE", () => { diff --git a/packages/seatmaps-client/src/utils/zoom.ts b/packages/seatmaps-client/src/utils/zoom.ts index e8d0766..bf5ece0 100644 --- a/packages/seatmaps-client/src/utils/zoom.ts +++ b/packages/seatmaps-client/src/utils/zoom.ts @@ -101,22 +101,53 @@ export function initializeZoom(svg: SVGSVGElement) { const originalViewboxWidth = viewbox.width; const originalViewboxHeight = viewbox.height; + function constrainPan() { + const minX = originalViewboxX; + const maxX = originalViewboxX + originalViewboxWidth - viewbox.width; + + const minY = originalViewboxY; + const maxY = originalViewboxY + originalViewboxHeight - viewbox.height; + + viewbox.x = Math.max(minX, Math.min(maxX, viewbox.x)); + viewbox.y = Math.max(minY, Math.min(maxY, viewbox.y)); + } + function translate(x: number, y: number) { viewbox.x += x; viewbox.y += y; + constrainPan(); } - function scale(scale: number) { + const MIN_ZOOM = 0.1; // Minimum zoom scale (10% of the original size) + const MAX_ZOOM = 1.0; // Maximum zoom scale (100% of the original size) + + function scale(scaleFactor: number) { + // Calculate the new dimensions of the viewbox + const newHeight = viewbox.height * scaleFactor; + const newWidth = viewbox.width * scaleFactor; + + // Ensure the new dimensions are within the min and max zoom bounds + const minViewboxHeight = originalViewboxHeight * MIN_ZOOM; + const maxViewboxHeight = originalViewboxHeight * MAX_ZOOM; + + if (newHeight < minViewboxHeight || newHeight > maxViewboxHeight) { + return; // Prevent scaling if the zoom level is outside the allowed bounds + } + + // Apply the scaling const initialViewboxHeight = viewbox.height; const initialViewboxWidth = viewbox.width; - viewbox.height *= scale; - viewbox.width *= scale; + viewbox.height = newHeight; + viewbox.width = newWidth; + // Adjust the viewbox to keep the zoom centered translate( 0 - (viewbox.width - initialViewboxWidth) / 2, 0 - (viewbox.height - initialViewboxHeight) / 2, ); + + constrainPan(); // Ensure panning stays within bounds after scaling } // decreases the size of the map, relative to the viewport size @@ -281,15 +312,33 @@ export function initializeZoom(svg: SVGSVGElement) { // Handle controlled scrolls as zoom inputs. const delta = deltaY; - viewbox.height = - viewbox.height * (1 + (delta / window.innerHeight) * ZOOM_COEFFICIENT); - viewbox.width = - viewbox.width * (1 + (delta / window.innerWidth) * ZOOM_COEFFICIENT); + // Calculate the scaling factor + const scaleFactor = 1 + (delta / window.innerHeight) * ZOOM_COEFFICIENT; + + // Determine the new dimensions + const newHeight = viewbox.height * scaleFactor; + const newWidth = viewbox.width * scaleFactor; + + // Enforce zoom restrictions + const minViewboxHeight = originalViewboxHeight * MIN_ZOOM; + const maxViewboxHeight = originalViewboxHeight * MAX_ZOOM; + // Check and enforce the minimum and maximum zoom + if (newHeight < minViewboxHeight || newHeight > maxViewboxHeight) { + return; + } + + // Apply the scaling + viewbox.height = newHeight; + viewbox.width = newWidth; + + // Adjust the viewbox to keep the zoom centered translate( 0 - (viewbox.width - initialViewboxWidth) / 2, 0 - (viewbox.height - initialViewboxHeight) / 2, ); + + constrainPan(); // Ensure panning stays within bounds after scaling } else { // Handle non-controlled scrolls as pan inputs. translate(