UNPKG

@shashi_srinath/react-ui-scrollspy

Version:
161 lines (154 loc) 7.71 kB
Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespace(React); var throttle = function (callback, limit) { var tick = false; return function () { if (!tick) { callback(); tick = true; setTimeout(function () { tick = false; }, limit); } }; }; var ScrollSpy = function (_a) { var children = _a.children, // refs navContainerRef = _a.navContainerRef, parentScrollContainerRef = _a.parentScrollContainerRef, // throttle _b = _a.scrollThrottle, // throttle scrollThrottle = _b === void 0 ? 300 : _b, // callback onUpdateCallback = _a.onUpdateCallback, // offsets _c = _a.offsetTop, // offsets offsetTop = _c === void 0 ? 0 : _c, _d = _a.offsetBottom, offsetBottom = _d === void 0 ? 0 : _d, // customize attributes _e = _a.useDataAttribute, // customize attributes useDataAttribute = _e === void 0 ? "to-scrollspy-id" : _e, _f = _a.activeClass, activeClass = _f === void 0 ? "active-scroll-spy" : _f, _g = _a.useBoxMethod, useBoxMethod = _g === void 0 ? true : _g, _h = _a.updateHistoryStack, updateHistoryStack = _h === void 0 ? true : _h; var scrollContainerRef = React.useRef(null); var _j = React.useState(), navContainerItems = _j[0], setNavContainerItems = _j[1]; // prettier-ignore // keeps track of the Id in navcontainer which is active // so as to not update classLists unless it has been updated var prevIdTracker = React.useRef(""); // To get the nav container items depending on whether the parent ref for the nav container is passed or not React.useEffect(function () { var _a; navContainerRef ? setNavContainerItems((_a = navContainerRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll("[data-".concat(useDataAttribute, "]"))) : setNavContainerItems(document.querySelectorAll("[data-".concat(useDataAttribute, "]"))); // eslint-disable-next-line react-hooks/exhaustive-deps }, [navContainerRef]); // fire once after nav container items are set React.useEffect(function () { checkAndUpdateActiveScrollSpy(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [navContainerItems]); var isVisible = function (el) { var rectInView = el.getBoundingClientRect(); if (useBoxMethod) { var useHeight = (parentScrollContainerRef === null || parentScrollContainerRef === void 0 ? void 0 : parentScrollContainerRef.current) ? parentScrollContainerRef === null || parentScrollContainerRef === void 0 ? void 0 : parentScrollContainerRef.current.offsetHeight : window.innerHeight; var hitbox_top = useHeight; var element_top = rectInView.top; var element_bottom = rectInView.top + useHeight; return (hitbox_top < element_bottom + offsetBottom && hitbox_top > element_top - offsetTop); } else { // this decides how much of the element should be visible var leniency = (parentScrollContainerRef === null || parentScrollContainerRef === void 0 ? void 0 : parentScrollContainerRef.current) ? (parentScrollContainerRef === null || parentScrollContainerRef === void 0 ? void 0 : parentScrollContainerRef.current.offsetHeight) * 0.5 : window.innerHeight * 0.5; var useHeight = (parentScrollContainerRef === null || parentScrollContainerRef === void 0 ? void 0 : parentScrollContainerRef.current) ? parentScrollContainerRef === null || parentScrollContainerRef === void 0 ? void 0 : parentScrollContainerRef.current.offsetHeight : window.innerHeight; return (rectInView.top + leniency + offsetTop >= 0 && rectInView.bottom - leniency - offsetBottom <= useHeight); } }; var checkAndUpdateActiveScrollSpy = function () { var scrollParentContainer = scrollContainerRef.current; // if there are no children, return if (!(scrollParentContainer && navContainerItems)) return; var _loop_1 = function (i) { // get child element var useChild = scrollParentContainer.children.item(i); var elementIsVisible = isVisible(useChild); // check if the element is in the viewport if (elementIsVisible) { // if so, get its ID var changeHighlightedItemId_1 = useChild.id; // if the element was same as the one currently active ignore it if (prevIdTracker.current === changeHighlightedItemId_1) return { value: void 0 }; // now loop over each element in the nav Container navContainerItems.forEach(function (el) { var attrId = el.getAttribute("data-".concat(useDataAttribute)); // if the element contains 'active' the class remove it if (el.classList.contains(activeClass)) { el.classList.remove(activeClass); } // check if its ID matches the ID we got from the viewport // also make sure it does not already contain the 'active' class if (attrId === changeHighlightedItemId_1 && !el.classList.contains(activeClass)) { el.classList.add(activeClass); if (onUpdateCallback) { onUpdateCallback(changeHighlightedItemId_1); } prevIdTracker.current = changeHighlightedItemId_1; if (updateHistoryStack) { window.history.replaceState({}, "", "#".concat(changeHighlightedItemId_1)); } } }); return "break"; } }; // loop over all children in scroll container for (var i = 0; i < scrollParentContainer.children.length; i++) { var state_1 = _loop_1(i); if (typeof state_1 === "object") return state_1.value; if (state_1 === "break") break; } }; React.useEffect(function () { var _a; // listen for scroll event parentScrollContainerRef ? // if ref for scrollable div is provided (_a = parentScrollContainerRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener("scroll", throttle(checkAndUpdateActiveScrollSpy, scrollThrottle)) : // else listen for scroll in window window.addEventListener("scroll", throttle(checkAndUpdateActiveScrollSpy, scrollThrottle)); }); return React__namespace.createElement("div", { ref: scrollContainerRef }, children); }; exports["default"] = ScrollSpy; //# sourceMappingURL=index.js.map