@daniel-wrz/react-components-scrollbar
Version:
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
153 lines (150 loc) • 5.74 kB
JavaScript
// src/components/scrollbar/scrollbar.tsx
import React, { useEffect, useRef, useReducer, useCallback } from "react";
// src/components/scrollbar/scrollbar-logic.ts
var InitScrollbarAction = class {
content;
container;
constructor(content, container) {
this.container = container;
this.content = content;
}
};
var SetThumbDraggingAction = class {
dragging;
thumbPosition;
constructor(dragging, thumbPosition) {
this.dragging = dragging;
this.thumbPosition = thumbPosition;
}
};
var ScrollbarReducer = (state, action) => {
switch (action.constructor) {
case InitScrollbarAction: {
const data = action;
return {
...state,
thumbHeight: ScrollbarServices.setThumbHeight(data.content, data.container, 20),
scrollingFactor: ScrollbarServices.calculateScrollingFactor(data.content, data.container)
};
}
case SetThumbDraggingAction: {
const data = action;
return {
...state,
thumbIsDraging: data.dragging,
thumbPosition: data.thumbPosition
};
}
default:
return state;
}
};
var ScrollbarServices = {
calculateScrollingFactor: (content, container) => {
if (content && container) {
const { scrollHeight: contentTotalHeight } = content;
const { clientHeight: containerHeight } = container;
return contentTotalHeight / containerHeight;
}
return 0;
},
setThumbHeight: (content, container, minThum) => {
if (content && container) {
const { scrollHeight: contentTotalHeight } = content;
const { clientHeight: containerHeight } = container;
const value = containerHeight / contentTotalHeight * containerHeight;
if (value === containerHeight) return 0;
return Math.max(value, minThum);
}
return 0;
}
};
// src/components/scrollbar/scrollbar.tsx
var Scrollbar = ({ children }) => {
const containerRef = useRef(null);
const contentRef = useRef(null);
const thumbRef = useRef(null);
const trackRef = useRef(null);
const observer = useRef(null);
const [state, dispatch] = useReducer(ScrollbarReducer, { thumbHeight: 0, thumbIsDraging: false, scrollingFactor: 0 });
const calculateThumb = () => dispatch(new InitScrollbarAction(contentRef.current, containerRef.current));
const handleThumbMousedown = (e) => {
if (!state.thumbIsDraging && thumbRef.current) {
e.preventDefault();
e.stopPropagation();
const top = parseInt(thumbRef.current.style.top || "0", 10);
dispatch(new SetThumbDraggingAction(true, e.clientY - top));
}
};
const handleScrollingContent = useCallback(() => {
const container = containerRef.current;
const content = contentRef.current;
const thumb = thumbRef.current;
if (content && thumb && container) {
const { clientHeight: containerHeight } = container;
const { clientHeight: thumbHeight } = thumb;
const position = content.scrollTop / state.scrollingFactor;
const v = Math.min(position, containerHeight - thumbHeight);
thumb.style.top = `${v}px`;
}
}, [state.scrollingFactor]);
const stopScrolling = useCallback((e) => {
dispatch(new SetThumbDraggingAction(false, 0));
}, []);
const startDragingThumb = useCallback((e) => {
if (state.thumbIsDraging) {
e.preventDefault();
e.stopPropagation();
const content = contentRef.current;
const container = containerRef.current;
const thumb = thumbRef.current;
if (state.thumbIsDraging && thumb && content && container) {
const { clientHeight: containerHeight } = container;
const { clientHeight: thumbHeight } = thumb;
const delta = e.clientY - state.thumbPosition;
const v = Math.min(Math.max(delta, 0), containerHeight - thumbHeight);
content.scrollTop = v * state.scrollingFactor;
}
}
}, [state.thumbIsDraging]);
useEffect(() => {
var _a, _b;
if (contentRef.current) {
const content = contentRef.current;
observer.current = new ResizeObserver(() => {
calculateThumb();
});
observer.current.observe(content);
(_a = thumbRef.current) == null ? void 0 : _a.addEventListener("mousedown", handleThumbMousedown);
(_b = contentRef.current) == null ? void 0 : _b.addEventListener("scroll", handleScrollingContent);
return () => {
var _a2, _b2, _c;
(_a2 = observer.current) == null ? void 0 : _a2.unobserve(content);
(_b2 = thumbRef.current) == null ? void 0 : _b2.removeEventListener("mousedown", handleThumbMousedown);
(_c = contentRef.current) == null ? void 0 : _c.removeEventListener("scroll", handleScrollingContent);
};
}
}, [handleScrollingContent]);
useEffect(() => {
document.addEventListener("mouseup", stopScrolling);
document.addEventListener("mousemove", startDragingThumb);
return () => {
document.removeEventListener("mousemove", startDragingThumb);
document.removeEventListener("mouseup", stopScrolling);
};
}, [startDragingThumb, stopScrolling]);
return /* @__PURE__ */ React.createElement("div", { className: "s-container", ref: containerRef }, /* @__PURE__ */ React.createElement("div", { className: "s-content", ref: contentRef }, children), /* @__PURE__ */ React.createElement("div", { className: "s-scrollbar " + (state.thumbHeight === 0 ? "s-hide" : "") }, /* @__PURE__ */ React.createElement("div", { className: "s-track", ref: trackRef }, /* @__PURE__ */ React.createElement(
"div",
{
className: "s-thumb",
ref: thumbRef,
style: {
height: `${state.thumbHeight}px`
}
}
))));
};
var scrollbar_default = Scrollbar;
export {
scrollbar_default as Scrollbar
};