UNPKG

@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
// 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 };