react-sticky-kit 0.1.5 → 0.2.0
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 +110 -10
- package/dist/index.d.ts +7 -1
- package/dist/react-sticky.es.js +106 -94
- package/dist/react-sticky.umd.js +1 -1
- package/package.json +24 -13
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://
|
|
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,8 @@ 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
|
+
- 🖥️ Flexible reference containers (window, DOM elements, refs)
|
|
35
|
+
- 🔄 Supports SSR/SSG (Next.js, Gatsby, Astro, etc.)
|
|
25
36
|
- 🧪 Handles edge cases: empty sections, dynamic heights, zero-height headers, long headers, etc.
|
|
26
37
|
- ⚡️ Written in TypeScript, fully typed
|
|
27
38
|
- 🧪 Includes demo pages for real-world scenarios
|
|
@@ -36,6 +47,9 @@ yarn add react-sticky-kit
|
|
|
36
47
|
pnpm add react-sticky-kit
|
|
37
48
|
```
|
|
38
49
|
|
|
50
|
+
## Demo
|
|
51
|
+
- [Apple iOS Contact App](https://codesandbox.io/p/sandbox/dreamy-hofstadter-v9dzfz)
|
|
52
|
+
|
|
39
53
|
## Usage
|
|
40
54
|
|
|
41
55
|
```tsx
|
|
@@ -63,13 +77,14 @@ export default function Example() {
|
|
|
63
77
|
## Props
|
|
64
78
|
|
|
65
79
|
### `<StickyContainer />`
|
|
66
|
-
| Prop | Type
|
|
67
|
-
|
|
68
|
-
| `offsetTop` | `number`
|
|
69
|
-
| `defaultMode` | `'replace' \| 'stack' \| 'none'`
|
|
70
|
-
| `baseZIndex` | `number`
|
|
71
|
-
| |
|
|
72
|
-
| `onStickyItemsHeightChange` | `(height: number) => void`
|
|
80
|
+
| Prop | Type | Default | Description |
|
|
81
|
+
|-----------------------------|------------------------------------------------------|-------------|---------------------------------------------------------------------------------------------|
|
|
82
|
+
| `offsetTop` | `number` | `0` | Offset from the top of the viewport |
|
|
83
|
+
| `defaultMode` | `'replace' \| 'stack' \| 'none'` | `'replace'` | Default sticky mode for all items |
|
|
84
|
+
| `baseZIndex` | `number` | `200` | Base z-index for sticky items. Should be greater than the number of sticky items. |
|
|
85
|
+
| | | | In `replace` mode, z-index = baseZIndex - index; in `stack` mode, z-index = baseZIndex + index. |
|
|
86
|
+
| `onStickyItemsHeightChange` | `(height: number) => void` | | Callback when total sticky height changes |
|
|
87
|
+
| `constraint` | `'none'` | | Define the constraint for sticky behavior. 'none' = no constraints (CSS-like behavior) |
|
|
73
88
|
|
|
74
89
|
### `<StickyItem />`
|
|
75
90
|
| Prop | Type | Default | Description |
|
|
@@ -81,6 +96,44 @@ export default function Example() {
|
|
|
81
96
|
- **stack**: Sticky items stack on top of each other.
|
|
82
97
|
- **none**: Sticky is disabled for this item.
|
|
83
98
|
|
|
99
|
+
## Constraint Behavior
|
|
100
|
+
|
|
101
|
+
The `constraint` prop allows you to control when sticky items should stop being sticky:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import React from 'react';
|
|
105
|
+
import { StickyContainer, StickyItem } from 'react-sticky-kit';
|
|
106
|
+
|
|
107
|
+
function Example() {
|
|
108
|
+
return (
|
|
109
|
+
<div>
|
|
110
|
+
{/* 1. Default: Container-based constraint */}
|
|
111
|
+
<StickyContainer>
|
|
112
|
+
<StickyItem><div>Sticky stops when container leaves viewport</div></StickyItem>
|
|
113
|
+
<div>Content...</div>
|
|
114
|
+
</StickyContainer>
|
|
115
|
+
|
|
116
|
+
{/* 2. No constraints (like CSS position:sticky) */}
|
|
117
|
+
<StickyContainer constraint="none">
|
|
118
|
+
<StickyItem><div>Always sticky like CSS position:sticky</div></StickyItem>
|
|
119
|
+
<div>Content...</div>
|
|
120
|
+
</StickyContainer>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
<div>Content...</div>
|
|
125
|
+
</StickyContainer>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Special behavior of `constraint="none"`
|
|
132
|
+
|
|
133
|
+
When you set `constraint="none"`, the sticky items will always stick when they reach the top of the viewport, regardless of their parent container's visibility. This exactly matches the behavior of native CSS `position: sticky`.
|
|
134
|
+
|
|
135
|
+
In contrast, the default behavior (without specifying a constraint) only makes items sticky when their parent container is visible in the viewport.
|
|
136
|
+
|
|
84
137
|
## Demo
|
|
85
138
|
|
|
86
139
|
Run the demo locally:
|
|
@@ -93,8 +146,55 @@ pnpm dev
|
|
|
93
146
|
Open [http://localhost:5173](http://localhost:5173) and switch between demo pages to explore all features and edge cases:
|
|
94
147
|
* [iOS Contact](http://localhost:5173/#ios-contact) a dead simple iOS contact clone
|
|
95
148
|
* [Mixed mode](http://localhost:5173/#mixed-mode) mix replace/stack/none mode
|
|
96
|
-
* [Nested](http://localhost:5173/#
|
|
97
|
-
* [Dynamic sticky items](http://localhost:5173/#
|
|
149
|
+
* [Nested](http://localhost:5173/#nested) nest sticky containers
|
|
150
|
+
* [Dynamic sticky items](http://localhost:5173/#dynamic) dynamic sticky items(add/remove)
|
|
151
|
+
* [Dynamic offsetTop](http://localhost:5173/#dynamic-offset) dynamic offsetTop that adapts to header height changes
|
|
152
|
+
* [Constraint Demo](http://localhost:5173/#constraint) demo showing different constraint options
|
|
153
|
+
|
|
154
|
+
## SSR/SSG Support
|
|
155
|
+
|
|
156
|
+
React Sticky Kit supports Server-Side Rendering (SSR) and Static Site Generation (SSG), working seamlessly with frameworks like Next.js, Gatsby, Astro, and more.
|
|
157
|
+
|
|
158
|
+
### Next.js Example
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
// pages/index.tsx
|
|
162
|
+
import { StickyContainer, StickyItem } from 'react-sticky-kit'
|
|
163
|
+
import 'react-sticky-kit/dist/style.css'
|
|
164
|
+
|
|
165
|
+
export default function Home() {
|
|
166
|
+
return (
|
|
167
|
+
<StickyContainer>
|
|
168
|
+
<StickyItem>
|
|
169
|
+
<header>Sticky Header</header>
|
|
170
|
+
</StickyItem>
|
|
171
|
+
<main>Content...</main>
|
|
172
|
+
</StickyContainer>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Astro Example
|
|
178
|
+
|
|
179
|
+
```astro
|
|
180
|
+
---
|
|
181
|
+
// src/pages/index.astro
|
|
182
|
+
import { StickyContainer, StickyItem } from 'react-sticky-kit'
|
|
183
|
+
import 'react-sticky-kit/dist/style.css'
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
<html>
|
|
187
|
+
<head>...</head>
|
|
188
|
+
<body>
|
|
189
|
+
<StickyContainer client:load>
|
|
190
|
+
<StickyItem>
|
|
191
|
+
<header>Sticky Header</header>
|
|
192
|
+
</StickyItem>
|
|
193
|
+
<div>Content...</div>
|
|
194
|
+
</StickyContainer>
|
|
195
|
+
</body>
|
|
196
|
+
</html>
|
|
197
|
+
```
|
|
98
198
|
|
|
99
199
|
## License
|
|
100
200
|
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export declare interface IStickyContainerProps extends default_2.HTMLAttributes<
|
|
|
25
25
|
* @param height The total height of all sticky items inside the container.
|
|
26
26
|
*/
|
|
27
27
|
onStickyItemsHeightChange?: (height: number) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Define the constraint for sticky behavior
|
|
30
|
+
* - undefined (default): Sticky items will stop being sticky when StickyContainer leaves viewport
|
|
31
|
+
* - 'none': No constraints, similar to CSS position:sticky behavior
|
|
32
|
+
*/
|
|
33
|
+
constraint?: 'none';
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export declare interface IStickyItemProps extends default_2.HTMLAttributes<HTMLDivElement> {
|
|
@@ -43,7 +49,7 @@ export declare interface IStickyItemProps extends default_2.HTMLAttributes<HTMLD
|
|
|
43
49
|
*/
|
|
44
50
|
export declare type IStickyMode = 'replace' | 'stack' | 'none';
|
|
45
51
|
|
|
46
|
-
export declare function StickyContainer({ children, offsetTop, baseZIndex, onStickyItemsHeightChange, defaultMode, ...rest }: IStickyContainerProps): JSX.Element;
|
|
52
|
+
export declare function StickyContainer({ children, offsetTop, baseZIndex, onStickyItemsHeightChange, defaultMode, constraint, ...rest }: IStickyContainerProps): JSX.Element;
|
|
47
53
|
|
|
48
54
|
export declare function StickyItem({ mode, children, className, ...rest }: IStickyItemProps): JSX.Element;
|
|
49
55
|
|
package/dist/react-sticky.es.js
CHANGED
|
@@ -1,142 +1,154 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { createContext as
|
|
3
|
-
const
|
|
4
|
-
function
|
|
5
|
-
return
|
|
1
|
+
import { jsx as w } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as Z, useContext as L, useRef as k, useState as T, useEffect as E, useCallback as v, useMemo as _ } from "react";
|
|
3
|
+
const O = 20, A = 200, $ = Z(null);
|
|
4
|
+
function z() {
|
|
5
|
+
return L($);
|
|
6
6
|
}
|
|
7
|
-
function M({ mode:
|
|
8
|
-
const e =
|
|
7
|
+
function M({ mode: f, children: y, className: h, ...I }) {
|
|
8
|
+
const e = z(), g = k(null), S = k(null), [m, o] = T(!1), l = k({
|
|
9
9
|
fixedOffsetTop: (e == null ? void 0 : e.fixedOffsetTop) || 0,
|
|
10
|
-
isSticky:
|
|
11
|
-
baseZIndex: (e == null ? void 0 : e.baseZIndex) ||
|
|
10
|
+
isSticky: m,
|
|
11
|
+
baseZIndex: (e == null ? void 0 : e.baseZIndex) || O
|
|
12
12
|
});
|
|
13
|
-
return
|
|
14
|
-
const
|
|
15
|
-
if (!e || !
|
|
16
|
-
|
|
13
|
+
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(() => {
|
|
14
|
+
const t = S.current, a = g.current, n = f || (e == null ? void 0 : e.mode);
|
|
15
|
+
if (!e || !a || !t || n === "none" || !n || !e.register) {
|
|
16
|
+
o(!1);
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
-
const
|
|
20
|
-
el:
|
|
21
|
-
update: (
|
|
22
|
-
if (!
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 x = {
|
|
20
|
+
el: a,
|
|
21
|
+
update: (B, c, s, r, u) => {
|
|
22
|
+
if (!B || c > s)
|
|
23
|
+
return l.current.isSticky && o(!1), 0;
|
|
24
|
+
const i = t.getBoundingClientRect().height;
|
|
25
|
+
let p = s;
|
|
26
|
+
if (n === "replace" && typeof r < "u") {
|
|
27
|
+
const d = r - (s + i);
|
|
28
|
+
if (d < 0 && (p = s + d, d + i < 0))
|
|
29
|
+
return l.current.isSticky && o(!1), 0;
|
|
30
|
+
}
|
|
31
|
+
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;
|
|
36
32
|
}
|
|
37
33
|
};
|
|
38
|
-
return e.register(
|
|
39
|
-
}, [e == null ? void 0 : e.mode, e == null ? void 0 : e.register,
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
34
|
+
return e.register(x);
|
|
35
|
+
}, [e == null ? void 0 : e.mode, e == null ? void 0 : e.register, f]), E(() => {
|
|
36
|
+
const t = g.current, a = S.current, n = e == null ? void 0 : e.updateStickyItemsHeight;
|
|
37
|
+
if (!m || !t || !a || !n)
|
|
42
38
|
return;
|
|
43
|
-
const
|
|
39
|
+
const H = n(a.getBoundingClientRect().height);
|
|
44
40
|
return () => {
|
|
45
|
-
|
|
41
|
+
N(t, ["height"]), N(a, ["top", "z-index", "width"]), H();
|
|
46
42
|
};
|
|
47
|
-
}, [
|
|
43
|
+
}, [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 }) });
|
|
48
44
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
function N(f, y) {
|
|
46
|
+
y.forEach((h) => {
|
|
47
|
+
f.style.removeProperty(h);
|
|
52
48
|
});
|
|
53
49
|
}
|
|
54
50
|
function P({
|
|
55
|
-
children:
|
|
56
|
-
offsetTop:
|
|
57
|
-
baseZIndex:
|
|
58
|
-
onStickyItemsHeightChange:
|
|
51
|
+
children: f,
|
|
52
|
+
offsetTop: y = 0,
|
|
53
|
+
baseZIndex: h,
|
|
54
|
+
onStickyItemsHeightChange: I,
|
|
59
55
|
defaultMode: e = "replace",
|
|
60
|
-
|
|
56
|
+
constraint: g,
|
|
57
|
+
...S
|
|
61
58
|
}) {
|
|
62
|
-
const
|
|
63
|
-
fixedOffsetTop:
|
|
59
|
+
const m = k(null), o = k([]), l = k(null), t = k({
|
|
60
|
+
fixedOffsetTop: y,
|
|
64
61
|
defaultMode: e,
|
|
65
62
|
stickyItemsHeight: 0,
|
|
66
|
-
onStickyItemsHeightChange:
|
|
63
|
+
onStickyItemsHeightChange: I,
|
|
67
64
|
lastCanSticky: !1
|
|
68
65
|
});
|
|
69
|
-
t.current.onStickyItemsHeightChange =
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
66
|
+
t.current.onStickyItemsHeightChange = I;
|
|
67
|
+
const a = v(() => {
|
|
68
|
+
var c;
|
|
69
|
+
return g === "none" ? {
|
|
70
|
+
top: -1 / 0,
|
|
71
|
+
bottom: 1 / 0,
|
|
72
|
+
left: -1 / 0,
|
|
73
|
+
right: 1 / 0,
|
|
74
|
+
width: 1 / 0,
|
|
75
|
+
height: 1 / 0,
|
|
76
|
+
x: 0,
|
|
77
|
+
y: 0,
|
|
78
|
+
toJSON: () => ({})
|
|
79
|
+
} : ((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: () => ({}) };
|
|
80
|
+
}, [g]), n = v(() => {
|
|
81
|
+
const c = m.current;
|
|
82
|
+
!c || l.current || (l.current = requestAnimationFrame(() => {
|
|
83
|
+
l.current = null;
|
|
84
|
+
const s = a(), { fixedOffsetTop: r, stickyItemsHeight: u } = t.current;
|
|
85
|
+
let i;
|
|
86
|
+
if (g === "none" ? i = !0 : i = !(s.top > r || s.bottom < r), !i) {
|
|
87
|
+
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);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
t.current.lastCanSticky = i, u > 0 && c.classList.toggle("can-sticky", !0);
|
|
91
|
+
let p, d = 0;
|
|
92
|
+
p = r, d = s.bottom - (r + u), d > 0 && (d = 0);
|
|
93
|
+
const b = o.current.map((C) => C.el.getBoundingClientRect().top);
|
|
94
|
+
o.current.forEach((C, R) => {
|
|
95
|
+
p += C.update(i, b[R], p + d, b[R + 1], R);
|
|
96
|
+
});
|
|
97
|
+
}));
|
|
98
|
+
}, [a, g]), H = v((c) => {
|
|
99
|
+
const s = t.current.stickyItemsHeight + c;
|
|
100
|
+
return t.current.stickyItemsHeight = s, setTimeout(() => {
|
|
101
|
+
var r, u;
|
|
102
|
+
(u = (r = t.current).onStickyItemsHeightChange) == null || u.call(r, s);
|
|
91
103
|
}, 0), () => {
|
|
92
|
-
const
|
|
93
|
-
t.current.stickyItemsHeight =
|
|
94
|
-
var
|
|
95
|
-
(
|
|
104
|
+
const r = t.current.stickyItemsHeight - c;
|
|
105
|
+
t.current.stickyItemsHeight = r, setTimeout(() => {
|
|
106
|
+
var u, i;
|
|
107
|
+
(i = (u = t.current).onStickyItemsHeightChange) == null || i.call(u, r);
|
|
96
108
|
}, 0);
|
|
97
109
|
};
|
|
98
110
|
}, []);
|
|
99
111
|
E(() => {
|
|
100
|
-
t.current.fixedOffsetTop =
|
|
112
|
+
t.current.fixedOffsetTop = y, t.current.defaultMode = e, setTimeout(() => {
|
|
101
113
|
n();
|
|
102
114
|
}, 10);
|
|
103
|
-
}, [
|
|
104
|
-
const
|
|
105
|
-
|
|
115
|
+
}, [y, e, n]);
|
|
116
|
+
const x = v((c) => (o.current.push(c), o.current = W(o.current), n(), () => {
|
|
117
|
+
o.current = o.current.filter((s) => s !== c), n();
|
|
106
118
|
}), [n]);
|
|
107
119
|
E(() => (window.addEventListener("scroll", n, { passive: !0 }), window.addEventListener("resize", n, { passive: !0 }), () => {
|
|
108
120
|
window.removeEventListener("scroll", n), window.removeEventListener("resize", n);
|
|
109
121
|
}), [n]);
|
|
110
|
-
const
|
|
111
|
-
return /* @__PURE__ */
|
|
122
|
+
const B = _(() => D(h), [h]);
|
|
123
|
+
return /* @__PURE__ */ w(
|
|
112
124
|
$.Provider,
|
|
113
125
|
{
|
|
114
126
|
value: {
|
|
115
|
-
register:
|
|
116
|
-
baseZIndex:
|
|
117
|
-
updateStickyItemsHeight:
|
|
118
|
-
fixedOffsetTop:
|
|
127
|
+
register: x,
|
|
128
|
+
baseZIndex: B,
|
|
129
|
+
updateStickyItemsHeight: H,
|
|
130
|
+
fixedOffsetTop: y,
|
|
119
131
|
mode: e
|
|
120
132
|
},
|
|
121
|
-
children: /* @__PURE__ */
|
|
133
|
+
children: /* @__PURE__ */ w(
|
|
122
134
|
"div",
|
|
123
135
|
{
|
|
124
|
-
...
|
|
125
|
-
ref:
|
|
136
|
+
...S,
|
|
137
|
+
ref: m,
|
|
126
138
|
className: "oe-sticky-container",
|
|
127
|
-
children:
|
|
139
|
+
children: f
|
|
128
140
|
}
|
|
129
141
|
)
|
|
130
142
|
}
|
|
131
143
|
);
|
|
132
144
|
}
|
|
133
|
-
function
|
|
134
|
-
return typeof
|
|
145
|
+
function D(f) {
|
|
146
|
+
return typeof f > "u" ? A : Math.max(Number(f) || 0, O);
|
|
135
147
|
}
|
|
136
|
-
function
|
|
137
|
-
return
|
|
138
|
-
const
|
|
139
|
-
return
|
|
148
|
+
function W(f) {
|
|
149
|
+
return f.sort((y, h) => {
|
|
150
|
+
const I = y.el.getBoundingClientRect(), e = h.el.getBoundingClientRect();
|
|
151
|
+
return I.top - e.top;
|
|
140
152
|
});
|
|
141
153
|
}
|
|
142
154
|
export {
|
package/dist/react-sticky.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(g,
|
|
1
|
+
(function(g,I){typeof exports=="object"&&typeof module<"u"?I(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],I):(g=typeof globalThis<"u"?globalThis:g||self,I(g.ReactSticky={},g.jsxRuntime,g.React))})(this,function(g,I,n){"use strict";const N=n.createContext(null);function w(){return n.useContext(N)}const L="";function O({mode:l,children:h,className:k,...E}){const e=w(),m=n.useRef(null),C=n.useRef(null),[p,o]=n.useState(!1),d=n.useRef({fixedOffsetTop:(e==null?void 0:e.fixedOffsetTop)||0,isSticky:p,baseZIndex:(e==null?void 0:e.baseZIndex)||20});return d.current.baseZIndex=(e==null?void 0:e.baseZIndex)||20,d.current.fixedOffsetTop=(e==null?void 0:e.fixedOffsetTop)||0,d.current.isSticky=p,n.useEffect(()=>{const t=C.current,a=m.current,i=l||(e==null?void 0:e.mode);if(!e||!a||!t||i==="none"||!i||!e.register){o(!1);return}const v={el:a,update:(B,c,u,r,f)=>{if(!B||c>u)return d.current.isSticky&&o(!1),0;const s=t.getBoundingClientRect().height;let S=u;if(i==="replace"&&typeof r<"u"){const y=r-(u+s);if(y<0&&(S=u+y,y+s<0))return d.current.isSticky&&o(!1),0}return a.style.height=`${s}px`,d.current.isSticky||o(!0),t.style.top=`${S}px`,t.style.width=`${a.offsetWidth}px`,t.style.zIndex=`${d.current.baseZIndex+(i==="replace"?-f:f)}`,i==="replace"?0:s}};return e.register(v)},[e==null?void 0:e.mode,e==null?void 0:e.register,l]),n.useEffect(()=>{const t=m.current,a=C.current,i=e==null?void 0:e.updateStickyItemsHeight;if(!p||!t||!a||!i)return;const _=i(a.getBoundingClientRect().height);return()=>{b(t,["height"]),b(a,["top","z-index","width"]),_()}},[p,e==null?void 0:e.updateStickyItemsHeight]),I.jsx("div",{className:"oe-sticky-item "+(k||""),...E,ref:m,children:I.jsx("div",{className:"oe-sticky-content "+(p?"is-sticky":""),ref:C,children:h})})}function b(l,h){h.forEach(k=>{l.style.removeProperty(k)})}function Z({children:l,offsetTop:h=0,baseZIndex:k,onStickyItemsHeightChange:E,defaultMode:e="replace",constraint:m,...C}){const p=n.useRef(null),o=n.useRef([]),d=n.useRef(null),t=n.useRef({fixedOffsetTop:h,defaultMode:e,stickyItemsHeight:0,onStickyItemsHeightChange:E,lastCanSticky:!1});t.current.onStickyItemsHeightChange=E;const a=n.useCallback(()=>{var c;return m==="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=p.current)==null?void 0:c.getBoundingClientRect())||{top:0,bottom:0,left:0,right:0,width:0,height:0,x:0,y:0,toJSON:()=>({})}},[m]),i=n.useCallback(()=>{const c=p.current;!c||d.current||(d.current=requestAnimationFrame(()=>{d.current=null;const u=a(),{fixedOffsetTop:r,stickyItemsHeight:f}=t.current;let s;if(m==="none"?s=!0:s=!(u.top>r||u.bottom<r),!s){t.current.lastCanSticky!==s&&(c.classList.toggle("can-sticky",!1),o.current.forEach(R=>R.update(!1,0,0,0,0)),t.current.lastCanSticky=s);return}t.current.lastCanSticky=s,f>0&&c.classList.toggle("can-sticky",!0);let S,y=0;S=r,y=u.bottom-(r+f),y>0&&(y=0);const x=o.current.map(R=>R.el.getBoundingClientRect().top);o.current.forEach((R,H)=>{S+=R.update(s,x[H],S+y,x[H+1],H)})}))},[a,m]),_=n.useCallback(c=>{const u=t.current.stickyItemsHeight+c;return t.current.stickyItemsHeight=u,setTimeout(()=>{var r,f;(f=(r=t.current).onStickyItemsHeightChange)==null||f.call(r,u)},0),()=>{const r=t.current.stickyItemsHeight-c;t.current.stickyItemsHeight=r,setTimeout(()=>{var f,s;(s=(f=t.current).onStickyItemsHeightChange)==null||s.call(f,r)},0)}},[]);n.useEffect(()=>{t.current.fixedOffsetTop=h,t.current.defaultMode=e,setTimeout(()=>{i()},10)},[h,e,i]);const v=n.useCallback(c=>(o.current.push(c),o.current=A(o.current),i(),()=>{o.current=o.current.filter(u=>u!==c),i()}),[i]);n.useEffect(()=>(window.addEventListener("scroll",i,{passive:!0}),window.addEventListener("resize",i,{passive:!0}),()=>{window.removeEventListener("scroll",i),window.removeEventListener("resize",i)}),[i]);const B=n.useMemo(()=>T(k),[k]);return I.jsx(N.Provider,{value:{register:v,baseZIndex:B,updateStickyItemsHeight:_,fixedOffsetTop:h,mode:e},children:I.jsx("div",{...C,ref:p,className:"oe-sticky-container",children:l})})}function T(l){return typeof l>"u"?200:Math.max(Number(l)||0,20)}function A(l){return l.sort((h,k)=>{const E=h.el.getBoundingClientRect(),e=k.el.getBoundingClientRect();return E.top-e.top})}g.StickyContainer=Z,g.StickyItem=O,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-sticky-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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": [
|
|
@@ -20,14 +20,6 @@
|
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://github.com/oe/react-sticky-kit#readme",
|
|
22
22
|
"license": "MIT",
|
|
23
|
-
"scripts": {
|
|
24
|
-
"dev": "vite",
|
|
25
|
-
"build": "vite build",
|
|
26
|
-
"preview": "vite preview",
|
|
27
|
-
"type-check": "tsc --noEmit",
|
|
28
|
-
"lint": "eslint src demo --ext .ts,.tsx,.js,.jsx",
|
|
29
|
-
"pub": "tsx ./scripts/publish.ts"
|
|
30
|
-
},
|
|
31
23
|
"files": [
|
|
32
24
|
"dist"
|
|
33
25
|
],
|
|
@@ -36,9 +28,9 @@
|
|
|
36
28
|
"types": "dist/index.d.ts",
|
|
37
29
|
"exports": {
|
|
38
30
|
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
39
32
|
"import": "./dist/react-sticky.es.js",
|
|
40
|
-
"require": "./dist/react-sticky.umd.js"
|
|
41
|
-
"types": "./dist/index.d.ts"
|
|
33
|
+
"require": "./dist/react-sticky.umd.js"
|
|
42
34
|
},
|
|
43
35
|
"./style": "./dist/style.css",
|
|
44
36
|
"./dist/style.css": "./dist/style.css"
|
|
@@ -49,25 +41,44 @@
|
|
|
49
41
|
},
|
|
50
42
|
"devDependencies": {
|
|
51
43
|
"@eslint/js": "^9.25.0",
|
|
44
|
+
"@testing-library/jest-dom": "^6.1.6",
|
|
45
|
+
"@testing-library/react": "^14.1.2",
|
|
46
|
+
"@testing-library/user-event": "^14.4.3",
|
|
52
47
|
"@types/node": "^22.15.18",
|
|
53
48
|
"@types/react": "^19.1.2",
|
|
54
49
|
"@types/react-dom": "^19.1.2",
|
|
55
50
|
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
|
56
51
|
"@typescript-eslint/parser": "^8.32.1",
|
|
57
52
|
"@vitejs/plugin-react": "^4.0.0",
|
|
53
|
+
"@vitest/coverage-v8": "^3.1.3",
|
|
58
54
|
"eslint": "^9.26.0",
|
|
59
55
|
"eslint-config-prettier": "^10.1.5",
|
|
60
56
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
|
61
57
|
"eslint-plugin-react": "^7.33.2",
|
|
62
58
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
63
59
|
"globals": "^16.0.0",
|
|
60
|
+
"identity-obj-proxy": "^3.0.0",
|
|
61
|
+
"jsdom": "^26.1.0",
|
|
64
62
|
"react": "^19.1.0",
|
|
65
63
|
"react-dom": "^19.1.0",
|
|
66
64
|
"sass": "^1.88.0",
|
|
65
|
+
"ts-node": "^10.9.2",
|
|
67
66
|
"tsx": "^4.19.4",
|
|
68
67
|
"typescript": "^5.0.0",
|
|
69
68
|
"typescript-eslint": "^8.30.1",
|
|
70
69
|
"vite": "^4.0.0",
|
|
71
|
-
"vite-plugin-dts": "^4.5.4"
|
|
70
|
+
"vite-plugin-dts": "^4.5.4",
|
|
71
|
+
"vitest": "^3.1.3"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"dev": "vite",
|
|
75
|
+
"build": "vite build",
|
|
76
|
+
"preview": "vite preview",
|
|
77
|
+
"type-check": "tsc --noEmit",
|
|
78
|
+
"lint": "eslint src demo --ext .ts,.tsx,.js,.jsx",
|
|
79
|
+
"test": "vitest run",
|
|
80
|
+
"test:watch": "vitest",
|
|
81
|
+
"coverage": "vitest run --coverage",
|
|
82
|
+
"pub": "tsx ./scripts/publish.ts"
|
|
72
83
|
}
|
|
73
|
-
}
|
|
84
|
+
}
|