UNPKG

@nwebui/react-niagara-px

Version:
266 lines 8.69 kB
import Hammer from "hammerjs"; import React, { useEffect, useRef } from "react"; import { Widget } from "./Widget"; const ScrollPane = function ({ nativeWidget, children }) { const divRef = useRef(null); const pannerRef = useRef(); function childElement() { return divRef.current?.firstElementChild; } function applyTransform(animate) { const child = childElement(); if (!child) { return; } const x = pannerRef.current.transX; const y = pannerRef.current.transY; const s = pannerRef.current.scale; child.style.transform = `translate(${x}px, ${y}px) scale(${s})`; child.style.transformOrigin = "0 0"; child.style.transition = animate ? "transform 500ms" : "none"; } function limitTranslate() { const el = divRef.current; const child = childElement(); if (!child) { return; } const w = el.offsetWidth; const h = el.offsetHeight; const w2 = child.offsetWidth; const h2 = child.offsetHeight; const scale = pannerRef.current.scale; let minX = 0; let maxX = 0; let minY = 0; let maxY = 0; if (w >= w2 * scale[0]) { minX = maxX = (w - w2 * scale[0]) / 2; } else { minX = w - w2 * scale[0]; maxX = 0; } if (h >= h2 * scale[1]) { minY = maxY = (h - h2 * scale[1]) / 2; } else { minY = h - h2 * scale[1]; maxY = 0; } let transX = pannerRef.current.transX; let transY = pannerRef.current.transY; if (transX < minX) { transX = minX; } else if (transX > maxX) { transX = maxX; } if (transY < minY) { transY = minY; } else if (transY > maxY) { transY = maxY; } pannerRef.current.panTo(transX, transY); } function fit(animate) { const el = divRef.current; const child = childElement(); if (!child) { return; } if (child.offsetHeight / el.offsetHeight > child.offsetWidth / el.offsetWidth) { fitHeight(animate); } else { fitWidth(animate); } } function fitWidth(animate) { const el = divRef.current; const child = childElement(); if (!child) { return; } const scale = [ el.offsetWidth / child.offsetWidth, el.offsetHeight / child.offsetHeight, ]; pannerRef.current.zoom(scale); pannerRef.current.panTo(0, (el.offsetHeight - child.offsetHeight * scale[1]) / 2); applyTransform(animate); } function fitHeight(animate) { const el = divRef.current; const child = childElement(); if (!child) { return; } const scale = [ el.offsetWidth / child.offsetWidth, el.offsetHeight / child.offsetHeight, ]; pannerRef.current.zoom(scale); pannerRef.current.panTo((el.offsetWidth - child.offsetWidth * scale[0]) / 2, 0); applyTransform(animate); } function onWheel(e) { // @ts-ignore if (e.target.nodeName == "CANVAS") { return; } const el = divRef.current; const bounding = el.getBoundingClientRect(); const mouseX = e.pageX - bounding.left; const mouseY = e.pageY - bounding.top; let scale = pannerRef.current.scale; if (e.deltaY < 0) { scale = pannerRef.current.scale.map((x) => x * Math.sqrt(2)); } else if (e.deltaY > 0) { scale = pannerRef.current.scale.map((x) => x / Math.sqrt(2)); } pannerRef.current.zoom(scale, { x: mouseX, y: mouseY }); applyTransform(true); } useEffect(() => { const el = divRef.current; const ro = new ResizeObserver(() => { pannerRef.current = new CenteredPanZoom(el.offsetWidth, el.offsetHeight); fit(true); }); ro.observe(el); const hammer = new Hammer(el, { enable: true, // recognizers: [ // [Hammer.Pan, { direction: Hammer.DIRECTION_ALL }], // [Hammer.Pinch], // ], }); let startX, startY, startScale; let prevented = false; hammer.on("panstart", (e) => { if (e.target.nodeName == "INPUT" || e.target.nodeName == "CANVAS" || e.target.className.includes("MuiSlider") || e.target.className.includes("PrivateValueLabel") || e.target.className.includes("NoPan")) { prevented = true; } else { prevented = false; } startX = e.center.x; startY = e.center.y; }); hammer.on("panmove", (e) => { if (prevented) { return; } pannerRef.current.pan(e.center.x - startX, e.center.y - startY); startX = e.center.x; startY = e.center.y; applyTransform(false); }); hammer.on("panend pancancel", () => { if (prevented) { return; } limitTranslate(); applyTransform(true); }); hammer.on("pinchstart", () => { startScale = pannerRef.current.scale; }); hammer.on("pinchmove", (e) => { const bounding = el.getBoundingClientRect(); const centerX = e.center.x - bounding.left; const centerY = e.center.y - bounding.top; pannerRef.current.zoom([startScale[0] * e.scale[0], startScale[1] * e.scale[1]], { x: centerX, y: centerY }); applyTransform(false); }); hammer.on("pinchend pinchcancel", (e) => { limitTranslate(); applyTransform(true); }); hammer.on("doubletap", (e) => { fit(true); }); return () => { ro.unobserve(el); hammer.destroy(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const style = { position: "absolute", width: "100%", height: "100%", overflow: "hidden", }; return (React.createElement(Widget, { nativeWidget: nativeWidget, ref: divRef, style: style, onWheel: onWheel }, children)); }; export default ScrollPane; class ViewPort { width; height; x; y; constructor(width, height, x = 0, y = 0) { this.width = width; this.height = height; this.x = x; this.y = y; } convert(point, to) { const scaleWidth = this.width / to.width; const scaleHeight = this.height / to.height; return { x: point.x * scaleWidth - to.x * scaleWidth, y: point.y * scaleHeight - to.y * scaleHeight, }; } } class CenteredPanZoom { screen; viewport; _scale = [1.0, 1.0]; constructor(screenWidth, screenHeight) { this.screen = new ViewPort(screenWidth, screenHeight); this.viewport = new ViewPort(screenWidth, screenHeight); } get transX() { return this.viewport.x; } get transY() { return this.viewport.y; } get scale() { return this._scale; } panTo(x, y) { this.viewport.x = x; this.viewport.y = y; } pan(deltaX, deltaY) { this.viewport.x += deltaX; this.viewport.y += deltaY; } zoom(scale, screenCenter) { screenCenter = screenCenter || { x: 0, y: 0 }; const v1 = this.screen.convert(screenCenter, this.viewport); this.viewport.x *= scale[0] / this._scale[0]; this.viewport.y *= scale[1] / this._scale[1]; this.viewport.width = this.screen.width * scale[0]; this.viewport.height = this.screen.height * scale[1]; this._scale = scale; const v2 = this.screen.convert(screenCenter, this.viewport); const deltaX = v2.x - v1.x; const deltaY = v2.y - v1.y; this.viewport.x += deltaX * scale[0]; this.viewport.y += deltaY * scale[1]; } } //# sourceMappingURL=ScrollPane.js.map