UNPKG

react-sticky-kit

Version:

A lightweight, flexible React sticky container and item component library supporting multiple sticky modes, advanced layouts, and edge cases.

158 lines (157 loc) 5.57 kB
import { jsx as w } from "react/jsx-runtime"; import { createContext as Z, useContext as L, useRef as k, useState as T, useEffect as E, useCallback as v, useMemo as _ } from "react"; const O = 20, A = 200, $ = Z(null); function z() { return L($); } function M({ mode: f, children: y, className: h, ...I }) { const e = z(), g = k(null), S = k(null), [m, o] = T(!1), l = k({ fixedOffsetTop: (e == null ? void 0 : e.fixedOffsetTop) || 0, isSticky: m, baseZIndex: (e == null ? void 0 : e.baseZIndex) || O }); return l.current.baseZIndex = (e == null ? void 0 : e.baseZIndex) || O, l.current.fixedOffsetTop = (e == null ? void 0 : e.fixedOffsetTop) || 0, l.current.isSticky = m, E(() => { const t = S.current, a = g.current, n = f || (e == null ? void 0 : e.mode); if (!e || !a || !t || n === "none" || !n || !e.register) { o(!1); return; } const x = { el: a, update: (B, c, s, r, u) => { if (!B || c > s) return l.current.isSticky && o(!1), 0; const i = t.getBoundingClientRect().height; let p = s; if (n === "replace" && typeof r < "u") { const d = r - (s + i); if (d < 0 && (p = s + d, d + i < 0)) return l.current.isSticky && o(!1), 0; } return a.style.height = `${i}px`, l.current.isSticky || o(!0), t.style.top = `${p}px`, t.style.width = `${a.offsetWidth}px`, t.style.zIndex = `${l.current.baseZIndex + (n === "replace" ? -u : u)}`, n === "replace" ? 0 : i; } }; return e.register(x); }, [e == null ? void 0 : e.mode, e == null ? void 0 : e.register, f]), E(() => { const t = g.current, a = S.current, n = e == null ? void 0 : e.updateStickyItemsHeight; if (!m || !t || !a || !n) return; const H = n(a.getBoundingClientRect().height); return () => { N(t, ["height"]), N(a, ["top", "z-index", "width"]), H(); }; }, [m, e == null ? void 0 : e.updateStickyItemsHeight]), /* @__PURE__ */ w("div", { className: "oe-sticky-item " + (h || ""), ...I, ref: g, children: /* @__PURE__ */ w("div", { className: "oe-sticky-content " + (m ? "is-sticky" : ""), ref: S, children: y }) }); } function N(f, y) { y.forEach((h) => { f.style.removeProperty(h); }); } function P({ children: f, offsetTop: y = 0, baseZIndex: h, onStickyItemsHeightChange: I, defaultMode: e = "replace", constraint: g, ...S }) { const m = k(null), o = k([]), l = k(null), t = k({ fixedOffsetTop: y, defaultMode: e, stickyItemsHeight: 0, onStickyItemsHeightChange: I, lastCanSticky: !1 }); t.current.onStickyItemsHeightChange = I; const a = v(() => { var c; return g === "none" ? { top: -1 / 0, bottom: 1 / 0, left: -1 / 0, right: 1 / 0, width: 1 / 0, height: 1 / 0, x: 0, y: 0, toJSON: () => ({}) } : ((c = m.current) == null ? void 0 : c.getBoundingClientRect()) || { top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0, x: 0, y: 0, toJSON: () => ({}) }; }, [g]), n = v(() => { const c = m.current; !c || l.current || (l.current = requestAnimationFrame(() => { l.current = null; const s = a(), { fixedOffsetTop: r, stickyItemsHeight: u } = t.current; let i; if (g === "none" ? i = !0 : i = !(s.top > r || s.bottom < r), !i) { t.current.lastCanSticky !== i && (c.classList.toggle("can-sticky", !1), o.current.forEach((C) => C.update(!1, 0, 0, 0, 0)), t.current.lastCanSticky = i); return; } t.current.lastCanSticky = i, u > 0 && c.classList.toggle("can-sticky", !0); let p, d = 0; p = r, d = s.bottom - (r + u), d > 0 && (d = 0); const b = o.current.map((C) => C.el.getBoundingClientRect().top); o.current.forEach((C, R) => { p += C.update(i, b[R], p + d, b[R + 1], R); }); })); }, [a, g]), H = v((c) => { const s = t.current.stickyItemsHeight + c; return t.current.stickyItemsHeight = s, setTimeout(() => { var r, u; (u = (r = t.current).onStickyItemsHeightChange) == null || u.call(r, s); }, 0), () => { const r = t.current.stickyItemsHeight - c; t.current.stickyItemsHeight = r, setTimeout(() => { var u, i; (i = (u = t.current).onStickyItemsHeightChange) == null || i.call(u, r); }, 0); }; }, []); E(() => { t.current.fixedOffsetTop = y, t.current.defaultMode = e, setTimeout(() => { n(); }, 10); }, [y, e, n]); const x = v((c) => (o.current.push(c), o.current = W(o.current), n(), () => { o.current = o.current.filter((s) => s !== c), n(); }), [n]); E(() => (window.addEventListener("scroll", n, { passive: !0 }), window.addEventListener("resize", n, { passive: !0 }), () => { window.removeEventListener("scroll", n), window.removeEventListener("resize", n); }), [n]); const B = _(() => D(h), [h]); return /* @__PURE__ */ w( $.Provider, { value: { register: x, baseZIndex: B, updateStickyItemsHeight: H, fixedOffsetTop: y, mode: e }, children: /* @__PURE__ */ w( "div", { ...S, ref: m, className: "oe-sticky-container", children: f } ) } ); } function D(f) { return typeof f > "u" ? A : Math.max(Number(f) || 0, O); } function W(f) { return f.sort((y, h) => { const I = y.el.getBoundingClientRect(), e = h.el.getBoundingClientRect(); return I.top - e.top; }); } export { P as StickyContainer, M as StickyItem };