react-sticky-kit 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,15 +4,24 @@
4
4
  <a href="https://www.npmjs.com/package/react-sticky-kit" target="_blank">
5
5
  <img src="https://img.shields.io/npm/v/react-sticky-kit.svg?style=flat-square" alt="NPM Version" />
6
6
  </a>
7
+ <a href="https://img.shields.io/npm/dm/react-sticky-kit?style=flat-square" target="_blank">
8
+ <img src="https://img.shields.io/npm/dm/react-sticky-kit?style=flat-square" alt="NPM Downloads" />
9
+ </a>
7
10
  <a href="https://github.com/oe/react-sticky-kit" target="_blank">
8
11
  <img src="https://img.shields.io/github/stars/oe/react-sticky-kit?style=flat-square" alt="GitHub Stars" />
9
12
  </a>
10
13
  <a href="https://github.com/oe/react-sticky-kit/actions" target="_blank">
11
- <img src="https://github.com/oe/react-sticky-kit/actions/workflows/ci.yml/badge.svg" alt="Build Status" />
14
+ <img src="https://img.shields.io/github/actions/workflow/status/oe/react-sticky-kit/ci.yml?style=flat-square" alt="Build Status" />
12
15
  </a>
13
16
  <a href="https://github.com/oe/react-sticky-kit/blob/main/LICENSE" target="_blank">
14
17
  <img src="https://img.shields.io/github/license/oe/react-sticky-kit?style=flat-square" alt="License" />
15
18
  </a>
19
+ <a href="https://www.typescriptlang.org/" target="_blank">
20
+ <img src="https://img.shields.io/badge/TypeScript-5.0%2B-blue?logo=typescript&style=flat-square" alt="TypeScript" />
21
+ </a>
22
+ <a href="#ssr-ssg-support" target="_blank">
23
+ <img src="https://img.shields.io/badge/SSR%2FSSG-Compatible-brightgreen?style=flat-square" alt="SSR/SSG Compatible" />
24
+ </a>
16
25
  </p>
17
26
 
18
27
  A lightweight, flexible React sticky container and item component library. Easily create sticky headers, sections, and advanced sticky layouts with support for multiple modes and edge cases.
@@ -22,6 +31,7 @@ A lightweight, flexible React sticky container and item component library. Easil
22
31
  - 📦 Simple API: `<StickyContainer>` and `<StickyItem>`
23
32
  - 🧩 Supports `replace`, `stack`, and `none` sticky modes
24
33
  - 🏷️ Customizable offset, z-index (baseZIndex), and sticky logic
34
+ - 🔄 Supports SSR/SSG (Next.js, Gatsby, Astro, etc.)
25
35
  - 🧪 Handles edge cases: empty sections, dynamic heights, zero-height headers, long headers, etc.
26
36
  - ⚡️ Written in TypeScript, fully typed
27
37
  - 🧪 Includes demo pages for real-world scenarios
@@ -36,6 +46,9 @@ yarn add react-sticky-kit
36
46
  pnpm add react-sticky-kit
37
47
  ```
38
48
 
49
+ ## Demo
50
+ - [Apple iOS Contact App](https://codesandbox.io/p/sandbox/dreamy-hofstadter-v9dzfz)
51
+
39
52
  ## Usage
40
53
 
41
54
  ```tsx
@@ -95,6 +108,52 @@ Open [http://localhost:5173](http://localhost:5173) and switch between demo page
95
108
  * [Mixed mode](http://localhost:5173/#mixed-mode) mix replace/stack/none mode
96
109
  * [Nested](http://localhost:5173/#dynamic-height) nest sticky containers
97
110
  * [Dynamic sticky items](http://localhost:5173/#nested) dynamic sticky items(add/remove)
111
+ * [Dynamic offsetTop](http://localhost:5173/#dynamic-offset) dynamic offsetTop that adapts to header height changes
112
+
113
+ ## SSR/SSG Support
114
+
115
+ React Sticky Kit supports Server-Side Rendering (SSR) and Static Site Generation (SSG), working seamlessly with frameworks like Next.js, Gatsby, Astro, and more.
116
+
117
+ ### Next.js Example
118
+
119
+ ```tsx
120
+ // pages/index.tsx
121
+ import { StickyContainer, StickyItem } from 'react-sticky-kit'
122
+ import 'react-sticky-kit/dist/style.css'
123
+
124
+ export default function Home() {
125
+ return (
126
+ <StickyContainer>
127
+ <StickyItem>
128
+ <header>Sticky Header</header>
129
+ </StickyItem>
130
+ <main>Content...</main>
131
+ </StickyContainer>
132
+ )
133
+ }
134
+ ```
135
+
136
+ ### Astro Example
137
+
138
+ ```astro
139
+ ---
140
+ // src/pages/index.astro
141
+ import { StickyContainer, StickyItem } from 'react-sticky-kit'
142
+ import 'react-sticky-kit/dist/style.css'
143
+ ---
144
+
145
+ <html>
146
+ <head>...</head>
147
+ <body>
148
+ <StickyContainer client:load>
149
+ <StickyItem>
150
+ <header>Sticky Header</header>
151
+ </StickyItem>
152
+ <div>Content...</div>
153
+ </StickyContainer>
154
+ </body>
155
+ </html>
156
+ ```
98
157
 
99
158
  ## License
100
159
 
@@ -1,66 +1,62 @@
1
1
  import { jsx as R } from "react/jsx-runtime";
2
- import { createContext as b, useContext as T, useRef as S, useState as Z, useEffect as E, useCallback as w, useMemo as L } from "react";
3
- const x = 20, N = 200, $ = b(null);
2
+ import { createContext as b, useContext as Z, useRef as h, useState as L, useEffect as E, useCallback as w, useMemo as N } from "react";
3
+ const x = 20, T = 200, $ = b(null);
4
4
  function _() {
5
- return T($);
5
+ return Z($);
6
6
  }
7
- function M({ mode: c, children: o, className: p, ...k }) {
8
- const e = _(), C = S(null), I = S(null), [r, t] = Z(!1), n = S({
7
+ function F({ mode: u, children: f, className: g, ...k }) {
8
+ const e = _(), S = h(null), I = h(null), [r, m] = L(!1), t = h({
9
9
  fixedOffsetTop: (e == null ? void 0 : e.fixedOffsetTop) || 0,
10
10
  isSticky: r,
11
11
  baseZIndex: (e == null ? void 0 : e.baseZIndex) || x
12
12
  });
13
- return n.current.baseZIndex = (e == null ? void 0 : e.baseZIndex) || x, n.current.fixedOffsetTop = (e == null ? void 0 : e.fixedOffsetTop) || 0, n.current.isSticky = r, E(() => {
14
- const u = I.current, f = C.current, a = c || (e == null ? void 0 : e.mode);
15
- if (!e || !f || !u || a === "none" || !a || !e.register) {
16
- t(!1);
13
+ return t.current.baseZIndex = (e == null ? void 0 : e.baseZIndex) || x, t.current.fixedOffsetTop = (e == null ? void 0 : e.fixedOffsetTop) || 0, t.current.isSticky = r, E(() => {
14
+ const n = I.current, a = S.current, l = u || (e == null ? void 0 : e.mode);
15
+ if (!e || !a || !n || l === "none" || !l || !e.register) {
16
+ m(!1);
17
17
  return;
18
18
  }
19
- const l = {
20
- el: f,
21
- update: (d, i, y, m, H) => {
22
- if (!d)
23
- return n.current.isSticky && t(!1), 0;
24
- if (i <= y) {
25
- const h = u.getBoundingClientRect().height;
26
- let v = y;
27
- if (a === "replace" && typeof m < "u") {
28
- const g = m - (y + h);
29
- if (g < 0 && (v = y + g, g + h < 0))
30
- return n.current.isSticky && t(!1), 0;
31
- }
32
- return f.style.height = `${h}px`, n.current.isSticky || t(!0), u.style.top = `${v}px`, u.style.width = `${f.offsetWidth}px`, u.style.zIndex = `${n.current.baseZIndex + (a === "replace" ? -H : H)}`, a === "replace" ? 0 : h;
33
- } else
34
- t(!1);
35
- return 0;
19
+ const s = {
20
+ el: a,
21
+ update: (d, c, i, o, C) => {
22
+ if (!d || c > i)
23
+ return t.current.isSticky && m(!1), 0;
24
+ const p = n.getBoundingClientRect().height;
25
+ let H = i;
26
+ if (l === "replace" && typeof o < "u") {
27
+ const y = o - (i + p);
28
+ if (y < 0 && (H = i + y, y + p < 0))
29
+ return t.current.isSticky && m(!1), 0;
30
+ }
31
+ return a.style.height = `${p}px`, t.current.isSticky || m(!0), n.style.top = `${H}px`, n.style.width = `${a.offsetWidth}px`, n.style.zIndex = `${t.current.baseZIndex + (l === "replace" ? -C : C)}`, l === "replace" ? 0 : p;
36
32
  }
37
33
  };
38
- return e.register(l);
39
- }, [e == null ? void 0 : e.mode, e == null ? void 0 : e.register, c]), E(() => {
40
- const u = C.current, f = I.current, a = e == null ? void 0 : e.updateStickyItemsHeight;
41
- if (!r || !u || !f || !a)
34
+ return e.register(s);
35
+ }, [e == null ? void 0 : e.mode, e == null ? void 0 : e.register, u]), E(() => {
36
+ const n = S.current, a = I.current, l = e == null ? void 0 : e.updateStickyItemsHeight;
37
+ if (!r || !n || !a || !l)
42
38
  return;
43
- const s = a(f.getBoundingClientRect().height);
39
+ const v = l(a.getBoundingClientRect().height);
44
40
  return () => {
45
- O(u, ["height"]), O(f, ["top", "z-index", "width"]), s();
41
+ O(n, ["height"]), O(a, ["top", "z-index", "width"]), v();
46
42
  };
47
- }, [r, e == null ? void 0 : e.updateStickyItemsHeight]), /* @__PURE__ */ R("div", { className: "oe-sticky-item " + (p || ""), ...k, ref: C, children: /* @__PURE__ */ R("div", { className: "oe-sticky-content " + (r ? "is-sticky" : ""), ref: I, children: o }) });
43
+ }, [r, e == null ? void 0 : e.updateStickyItemsHeight]), /* @__PURE__ */ R("div", { className: "oe-sticky-item " + (g || ""), ...k, ref: S, children: /* @__PURE__ */ R("div", { className: "oe-sticky-content " + (r ? "is-sticky" : ""), ref: I, children: f }) });
48
44
  }
49
- function O(c, o) {
50
- o.forEach((p) => {
51
- c.style.removeProperty(p);
45
+ function O(u, f) {
46
+ f.forEach((g) => {
47
+ u.style.removeProperty(g);
52
48
  });
53
49
  }
54
- function P({
55
- children: c,
56
- offsetTop: o = 0,
57
- baseZIndex: p,
50
+ function M({
51
+ children: u,
52
+ offsetTop: f = 0,
53
+ baseZIndex: g,
58
54
  onStickyItemsHeightChange: k,
59
55
  defaultMode: e = "replace",
60
- ...C
56
+ ...S
61
57
  }) {
62
- const I = S(null), r = S([]), t = S({
63
- fixedOffsetTop: o,
58
+ const I = h(null), r = h([]), m = h(null), t = h({
59
+ fixedOffsetTop: f,
64
60
  defaultMode: e,
65
61
  stickyItemsHeight: 0,
66
62
  onStickyItemsHeightChange: k,
@@ -69,77 +65,78 @@ function P({
69
65
  t.current.onStickyItemsHeightChange = k;
70
66
  const n = w(() => {
71
67
  const s = I.current;
72
- if (!s)
73
- return;
74
- const l = s.getBoundingClientRect(), d = t.current, i = d.fixedOffsetTop, y = d.stickyItemsHeight, m = !(l.top > i || l.bottom < i);
75
- if (!m) {
76
- t.current.lastCanSticky !== m && (s.classList.toggle("can-sticky", !1), r.current.forEach((g) => g.update(!1, 0, 0, 0, 0)), t.current.lastCanSticky = m);
77
- return;
78
- }
79
- t.current.lastCanSticky = m, y > 0 && s.classList.toggle("can-sticky", !0);
80
- let H = i, h = l.bottom - (i + y);
81
- h > 0 && (h = 0);
82
- const v = r.current.map((g) => g.el.getBoundingClientRect().top);
83
- r.current.forEach((g, B) => {
84
- H += g.update(m, v[B], H + h, v[B + 1], B);
85
- });
86
- }, []), u = w((s) => {
87
- const l = t.current.stickyItemsHeight + s;
88
- return t.current.stickyItemsHeight = l, setTimeout(() => {
89
- var d, i;
90
- (i = (d = t.current).onStickyItemsHeightChange) == null || i.call(d, l);
68
+ !s || m.current || (m.current = requestAnimationFrame(() => {
69
+ m.current = null;
70
+ const d = s.getBoundingClientRect(), { fixedOffsetTop: c, stickyItemsHeight: i } = t.current, o = !(d.top > c || d.bottom < c);
71
+ if (!o) {
72
+ t.current.lastCanSticky !== o && (s.classList.toggle("can-sticky", !1), r.current.forEach((y) => y.update(!1, 0, 0, 0, 0)), t.current.lastCanSticky = o);
73
+ return;
74
+ }
75
+ t.current.lastCanSticky = o, i > 0 && s.classList.toggle("can-sticky", !0);
76
+ let C = c, p = d.bottom - (c + i);
77
+ p > 0 && (p = 0);
78
+ const H = r.current.map((y) => y.el.getBoundingClientRect().top);
79
+ r.current.forEach((y, B) => {
80
+ C += y.update(o, H[B], C + p, H[B + 1], B);
81
+ });
82
+ }));
83
+ }, []), a = w((s) => {
84
+ const d = t.current.stickyItemsHeight + s;
85
+ return t.current.stickyItemsHeight = d, setTimeout(() => {
86
+ var c, i;
87
+ (i = (c = t.current).onStickyItemsHeightChange) == null || i.call(c, d);
91
88
  }, 0), () => {
92
- const d = t.current.stickyItemsHeight - s;
93
- t.current.stickyItemsHeight = d, setTimeout(() => {
94
- var i, y;
95
- (y = (i = t.current).onStickyItemsHeightChange) == null || y.call(i, d);
89
+ const c = t.current.stickyItemsHeight - s;
90
+ t.current.stickyItemsHeight = c, setTimeout(() => {
91
+ var i, o;
92
+ (o = (i = t.current).onStickyItemsHeightChange) == null || o.call(i, c);
96
93
  }, 0);
97
94
  };
98
95
  }, []);
99
96
  E(() => {
100
- t.current.fixedOffsetTop = o, t.current.defaultMode = e, setTimeout(() => {
97
+ t.current.fixedOffsetTop = f, t.current.defaultMode = e, setTimeout(() => {
101
98
  n();
102
99
  }, 10);
103
- }, [o, e, n]);
104
- const f = w((s) => (r.current.push(s), r.current = A(r.current), n(), () => {
105
- r.current = r.current.filter((l) => l !== s), n();
100
+ }, [f, e, n]);
101
+ const l = w((s) => (r.current.push(s), r.current = z(r.current), n(), () => {
102
+ r.current = r.current.filter((d) => d !== s), n();
106
103
  }), [n]);
107
104
  E(() => (window.addEventListener("scroll", n, { passive: !0 }), window.addEventListener("resize", n, { passive: !0 }), () => {
108
105
  window.removeEventListener("scroll", n), window.removeEventListener("resize", n);
109
106
  }), [n]);
110
- const a = L(() => z(p), [p]);
107
+ const v = N(() => A(g), [g]);
111
108
  return /* @__PURE__ */ R(
112
109
  $.Provider,
113
110
  {
114
111
  value: {
115
- register: f,
116
- baseZIndex: a,
117
- updateStickyItemsHeight: u,
118
- fixedOffsetTop: o,
112
+ register: l,
113
+ baseZIndex: v,
114
+ updateStickyItemsHeight: a,
115
+ fixedOffsetTop: f,
119
116
  mode: e
120
117
  },
121
118
  children: /* @__PURE__ */ R(
122
119
  "div",
123
120
  {
124
- ...C,
121
+ ...S,
125
122
  ref: I,
126
123
  className: "oe-sticky-container",
127
- children: c
124
+ children: u
128
125
  }
129
126
  )
130
127
  }
131
128
  );
132
129
  }
133
- function z(c) {
134
- return typeof c > "u" ? N : Math.max(Number(c) || 0, x);
130
+ function A(u) {
131
+ return typeof u > "u" ? T : Math.max(Number(u) || 0, x);
135
132
  }
136
- function A(c) {
137
- return c.sort((o, p) => {
138
- const k = o.el.getBoundingClientRect(), e = p.el.getBoundingClientRect();
133
+ function z(u) {
134
+ return u.sort((f, g) => {
135
+ const k = f.el.getBoundingClientRect(), e = g.el.getBoundingClientRect();
139
136
  return k.top - e.top;
140
137
  });
141
138
  }
142
139
  export {
143
- P as StickyContainer,
144
- M as StickyItem
140
+ M as StickyContainer,
141
+ F as StickyItem
145
142
  };
@@ -1 +1 @@
1
- (function(g,k){typeof exports=="object"&&typeof module<"u"?k(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],k):(g=typeof globalThis<"u"?globalThis:g||self,k(g.ReactSticky={},g.jsxRuntime,g.React))})(this,function(g,k,s){"use strict";const B=s.createContext(null);function N(){return s.useContext(B)}const $="";function O({mode:u,children:o,className:m,...E}){const e=N(),R=s.useRef(null),C=s.useRef(null),[i,t]=s.useState(!1),n=s.useRef({fixedOffsetTop:(e==null?void 0:e.fixedOffsetTop)||0,isSticky:i,baseZIndex:(e==null?void 0:e.baseZIndex)||20});return n.current.baseZIndex=(e==null?void 0:e.baseZIndex)||20,n.current.fixedOffsetTop=(e==null?void 0:e.fixedOffsetTop)||0,n.current.isSticky=i,s.useEffect(()=>{const f=C.current,l=R.current,d=u||(e==null?void 0:e.mode);if(!e||!l||!f||d==="none"||!d||!e.register){t(!1);return}const a={el:l,update:(y,c,p,I,H)=>{if(!y)return n.current.isSticky&&t(!1),0;if(c<=p){const S=f.getBoundingClientRect().height;let _=p;if(d==="replace"&&typeof I<"u"){const h=I-(p+S);if(h<0&&(_=p+h,h+S<0))return n.current.isSticky&&t(!1),0}return l.style.height=`${S}px`,n.current.isSticky||t(!0),f.style.top=`${_}px`,f.style.width=`${l.offsetWidth}px`,f.style.zIndex=`${n.current.baseZIndex+(d==="replace"?-H:H)}`,d==="replace"?0:S}else t(!1);return 0}};return e.register(a)},[e==null?void 0:e.mode,e==null?void 0:e.register,u]),s.useEffect(()=>{const f=R.current,l=C.current,d=e==null?void 0:e.updateStickyItemsHeight;if(!i||!f||!l||!d)return;const r=d(l.getBoundingClientRect().height);return()=>{x(f,["height"]),x(l,["top","z-index","width"]),r()}},[i,e==null?void 0:e.updateStickyItemsHeight]),k.jsx("div",{className:"oe-sticky-item "+(m||""),...E,ref:R,children:k.jsx("div",{className:"oe-sticky-content "+(i?"is-sticky":""),ref:C,children:o})})}function x(u,o){o.forEach(m=>{u.style.removeProperty(m)})}function T({children:u,offsetTop:o=0,baseZIndex:m,onStickyItemsHeightChange:E,defaultMode:e="replace",...R}){const C=s.useRef(null),i=s.useRef([]),t=s.useRef({fixedOffsetTop:o,defaultMode:e,stickyItemsHeight:0,onStickyItemsHeightChange:E,lastCanSticky:!1});t.current.onStickyItemsHeightChange=E;const n=s.useCallback(()=>{const r=C.current;if(!r)return;const a=r.getBoundingClientRect(),y=t.current,c=y.fixedOffsetTop,p=y.stickyItemsHeight,I=!(a.top>c||a.bottom<c);if(!I){t.current.lastCanSticky!==I&&(r.classList.toggle("can-sticky",!1),i.current.forEach(h=>h.update(!1,0,0,0,0)),t.current.lastCanSticky=I);return}t.current.lastCanSticky=I,p>0&&r.classList.toggle("can-sticky",!0);let H=c,S=a.bottom-(c+p);S>0&&(S=0);const _=i.current.map(h=>h.el.getBoundingClientRect().top);i.current.forEach((h,v)=>{H+=h.update(I,_[v],H+S,_[v+1],v)})},[]),f=s.useCallback(r=>{const a=t.current.stickyItemsHeight+r;return t.current.stickyItemsHeight=a,setTimeout(()=>{var y,c;(c=(y=t.current).onStickyItemsHeightChange)==null||c.call(y,a)},0),()=>{const y=t.current.stickyItemsHeight-r;t.current.stickyItemsHeight=y,setTimeout(()=>{var c,p;(p=(c=t.current).onStickyItemsHeightChange)==null||p.call(c,y)},0)}},[]);s.useEffect(()=>{t.current.fixedOffsetTop=o,t.current.defaultMode=e,setTimeout(()=>{n()},10)},[o,e,n]);const l=s.useCallback(r=>(i.current.push(r),i.current=b(i.current),n(),()=>{i.current=i.current.filter(a=>a!==r),n()}),[n]);s.useEffect(()=>(window.addEventListener("scroll",n,{passive:!0}),window.addEventListener("resize",n,{passive:!0}),()=>{window.removeEventListener("scroll",n),window.removeEventListener("resize",n)}),[n]);const d=s.useMemo(()=>Z(m),[m]);return k.jsx(B.Provider,{value:{register:l,baseZIndex:d,updateStickyItemsHeight:f,fixedOffsetTop:o,mode:e},children:k.jsx("div",{...R,ref:C,className:"oe-sticky-container",children:u})})}function Z(u){return typeof u>"u"?200:Math.max(Number(u)||0,20)}function b(u){return u.sort((o,m)=>{const E=o.el.getBoundingClientRect(),e=m.el.getBoundingClientRect();return E.top-e.top})}g.StickyContainer=T,g.StickyItem=O,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})});
1
+ (function(p,m){typeof exports=="object"&&typeof module<"u"?m(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],m):(p=typeof globalThis<"u"?globalThis:p||self,m(p.ReactSticky={},p.jsxRuntime,p.React))})(this,function(p,m,r){"use strict";const B=r.createContext(null);function x(){return r.useContext(B)}const $="";function Z({mode:o,children:l,className:h,...S}){const e=x(),C=r.useRef(null),E=r.useRef(null),[s,k]=r.useState(!1),t=r.useRef({fixedOffsetTop:(e==null?void 0:e.fixedOffsetTop)||0,isSticky:s,baseZIndex:(e==null?void 0:e.baseZIndex)||20});return t.current.baseZIndex=(e==null?void 0:e.baseZIndex)||20,t.current.fixedOffsetTop=(e==null?void 0:e.fixedOffsetTop)||0,t.current.isSticky=s,r.useEffect(()=>{const n=E.current,d=C.current,a=o||(e==null?void 0:e.mode);if(!e||!d||!n||a==="none"||!a||!e.register){k(!1);return}const c={el:d,update:(y,u,i,f,R)=>{if(!y||u>i)return t.current.isSticky&&k(!1),0;const I=n.getBoundingClientRect().height;let _=i;if(a==="replace"&&typeof f<"u"){const g=f-(i+I);if(g<0&&(_=i+g,g+I<0))return t.current.isSticky&&k(!1),0}return d.style.height=`${I}px`,t.current.isSticky||k(!0),n.style.top=`${_}px`,n.style.width=`${d.offsetWidth}px`,n.style.zIndex=`${t.current.baseZIndex+(a==="replace"?-R:R)}`,a==="replace"?0:I}};return e.register(c)},[e==null?void 0:e.mode,e==null?void 0:e.register,o]),r.useEffect(()=>{const n=C.current,d=E.current,a=e==null?void 0:e.updateStickyItemsHeight;if(!s||!n||!d||!a)return;const H=a(d.getBoundingClientRect().height);return()=>{N(n,["height"]),N(d,["top","z-index","width"]),H()}},[s,e==null?void 0:e.updateStickyItemsHeight]),m.jsx("div",{className:"oe-sticky-item "+(h||""),...S,ref:C,children:m.jsx("div",{className:"oe-sticky-content "+(s?"is-sticky":""),ref:E,children:l})})}function N(o,l){l.forEach(h=>{o.style.removeProperty(h)})}function b({children:o,offsetTop:l=0,baseZIndex:h,onStickyItemsHeightChange:S,defaultMode:e="replace",...C}){const E=r.useRef(null),s=r.useRef([]),k=r.useRef(null),t=r.useRef({fixedOffsetTop:l,defaultMode:e,stickyItemsHeight:0,onStickyItemsHeightChange:S,lastCanSticky:!1});t.current.onStickyItemsHeightChange=S;const n=r.useCallback(()=>{const c=E.current;!c||k.current||(k.current=requestAnimationFrame(()=>{k.current=null;const y=c.getBoundingClientRect(),{fixedOffsetTop:u,stickyItemsHeight:i}=t.current,f=!(y.top>u||y.bottom<u);if(!f){t.current.lastCanSticky!==f&&(c.classList.toggle("can-sticky",!1),s.current.forEach(g=>g.update(!1,0,0,0,0)),t.current.lastCanSticky=f);return}t.current.lastCanSticky=f,i>0&&c.classList.toggle("can-sticky",!0);let R=u,I=y.bottom-(u+i);I>0&&(I=0);const _=s.current.map(g=>g.el.getBoundingClientRect().top);s.current.forEach((g,v)=>{R+=g.update(f,_[v],R+I,_[v+1],v)})}))},[]),d=r.useCallback(c=>{const y=t.current.stickyItemsHeight+c;return t.current.stickyItemsHeight=y,setTimeout(()=>{var u,i;(i=(u=t.current).onStickyItemsHeightChange)==null||i.call(u,y)},0),()=>{const u=t.current.stickyItemsHeight-c;t.current.stickyItemsHeight=u,setTimeout(()=>{var i,f;(f=(i=t.current).onStickyItemsHeightChange)==null||f.call(i,u)},0)}},[]);r.useEffect(()=>{t.current.fixedOffsetTop=l,t.current.defaultMode=e,setTimeout(()=>{n()},10)},[l,e,n]);const a=r.useCallback(c=>(s.current.push(c),s.current=O(s.current),n(),()=>{s.current=s.current.filter(y=>y!==c),n()}),[n]);r.useEffect(()=>(window.addEventListener("scroll",n,{passive:!0}),window.addEventListener("resize",n,{passive:!0}),()=>{window.removeEventListener("scroll",n),window.removeEventListener("resize",n)}),[n]);const H=r.useMemo(()=>w(h),[h]);return m.jsx(B.Provider,{value:{register:a,baseZIndex:H,updateStickyItemsHeight:d,fixedOffsetTop:l,mode:e},children:m.jsx("div",{...C,ref:E,className:"oe-sticky-container",children:o})})}function w(o){return typeof o>"u"?200:Math.max(Number(o)||0,20)}function O(o){return o.sort((l,h)=>{const S=l.el.getBoundingClientRect(),e=h.el.getBoundingClientRect();return S.top-e.top})}p.StickyContainer=b,p.StickyItem=Z,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-sticky-kit",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "A lightweight, flexible React sticky container and item component library supporting multiple sticky modes, advanced layouts, and edge cases.",
6
6
  "keywords": [
@@ -26,6 +26,9 @@
26
26
  "preview": "vite preview",
27
27
  "type-check": "tsc --noEmit",
28
28
  "lint": "eslint src demo --ext .ts,.tsx,.js,.jsx",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "coverage": "vitest run --coverage",
29
32
  "pub": "tsx ./scripts/publish.ts"
30
33
  },
31
34
  "files": [
@@ -36,9 +39,9 @@
36
39
  "types": "dist/index.d.ts",
37
40
  "exports": {
38
41
  ".": {
42
+ "types": "./dist/index.d.ts",
39
43
  "import": "./dist/react-sticky.es.js",
40
- "require": "./dist/react-sticky.umd.js",
41
- "types": "./dist/index.d.ts"
44
+ "require": "./dist/react-sticky.umd.js"
42
45
  },
43
46
  "./style": "./dist/style.css",
44
47
  "./dist/style.css": "./dist/style.css"
@@ -49,25 +52,33 @@
49
52
  },
50
53
  "devDependencies": {
51
54
  "@eslint/js": "^9.25.0",
55
+ "@testing-library/jest-dom": "^6.1.6",
56
+ "@testing-library/react": "^14.1.2",
57
+ "@testing-library/user-event": "^14.4.3",
52
58
  "@types/node": "^22.15.18",
53
59
  "@types/react": "^19.1.2",
54
60
  "@types/react-dom": "^19.1.2",
55
61
  "@typescript-eslint/eslint-plugin": "^8.32.1",
56
62
  "@typescript-eslint/parser": "^8.32.1",
57
63
  "@vitejs/plugin-react": "^4.0.0",
64
+ "@vitest/coverage-v8": "^3.1.3",
58
65
  "eslint": "^9.26.0",
59
66
  "eslint-config-prettier": "^10.1.5",
60
67
  "eslint-plugin-jsx-a11y": "^6.8.0",
61
68
  "eslint-plugin-react": "^7.33.2",
62
69
  "eslint-plugin-react-hooks": "^5.2.0",
63
70
  "globals": "^16.0.0",
71
+ "identity-obj-proxy": "^3.0.0",
72
+ "jsdom": "^26.1.0",
64
73
  "react": "^19.1.0",
65
74
  "react-dom": "^19.1.0",
66
75
  "sass": "^1.88.0",
76
+ "ts-node": "^10.9.2",
67
77
  "tsx": "^4.19.4",
68
78
  "typescript": "^5.0.0",
69
79
  "typescript-eslint": "^8.30.1",
70
80
  "vite": "^4.0.0",
71
- "vite-plugin-dts": "^4.5.4"
81
+ "vite-plugin-dts": "^4.5.4",
82
+ "vitest": "^3.1.3"
72
83
  }
73
84
  }