@nwebui/react-niagara-px
Version:
React Niagara Px View
266 lines • 8.69 kB
JavaScript
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