react-sticky-kit 0.2.1 โ 0.3.1
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 +147 -57
- package/dist/context.d.cts +29 -0
- package/dist/context.d.ts +29 -0
- package/dist/index.d.cts +34 -0
- package/dist/index.d.ts +34 -56
- package/dist/layout-observer.d.cts +2 -0
- package/dist/layout-observer.d.ts +2 -0
- package/dist/react-sticky.cjs +1 -0
- package/dist/react-sticky.es.js +323 -151
- package/dist/react-sticky.umd.js +1 -1
- package/dist/resize-observer.d.cts +3 -0
- package/dist/resize-observer.d.ts +3 -0
- package/dist/scheduler.d.cts +10 -0
- package/dist/scheduler.d.ts +10 -0
- package/dist/sticky-item.d.cts +8 -0
- package/dist/sticky-item.d.ts +8 -0
- package/dist/style.css +2 -1
- package/package.json +62 -37
package/README.md
CHANGED
|
@@ -24,56 +24,90 @@
|
|
|
24
24
|
</a>
|
|
25
25
|
</p>
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
Coordinate multiple sticky section headers in React. Stack headers using their actual
|
|
28
|
+
heights, replace a section header as the next one arrives, or mix both behaviors
|
|
29
|
+
under a shared viewport offset.
|
|
30
|
+
|
|
31
|
+
**Use native CSS `position: sticky` for a single header or a straightforward grouped
|
|
32
|
+
list.** Choose React Sticky Kit when you need dynamic-height stacking, mixed
|
|
33
|
+
stack/replace behavior, or the total sticky height without maintaining your own
|
|
34
|
+
measurement and scroll logic.
|
|
35
|
+
|
|
36
|
+
## See the behavior
|
|
37
|
+
|
|
38
|
+
| Replace section headings | Stack reached headings |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
|  |  |
|
|
41
|
+
|
|
42
|
+
- **Replace:** an alphabetical contact list updates its section heading as you scroll.
|
|
43
|
+
- **Stack:** previously reached headings accumulate below the shared offset; their
|
|
44
|
+
heights are measured automatically, including when content changes.
|
|
45
|
+
- **Mixed:** keep a page title stacked while section headings replace below it.
|
|
46
|
+
|
|
47
|
+
[Open the live demo](https://app.evecalm.com/react-sticky-kit/) โ no installation required.
|
|
48
|
+
Try [replace](https://app.evecalm.com/react-sticky-kit/#replace),
|
|
49
|
+
[stack](https://app.evecalm.com/react-sticky-kit/#stack), or
|
|
50
|
+
[mixed modes](https://app.evecalm.com/react-sticky-kit/#mixed-mode).
|
|
51
|
+
The demo also includes dynamic heights, nested groups and container boundaries.
|
|
52
|
+
To edit an example, use the [contact-list sandbox](https://codesandbox.io/p/sandbox/dreamy-hofstadter-v9dzfz);
|
|
53
|
+
see [running the demo](#run-the-demo) for local development.
|
|
39
54
|
|
|
40
55
|
## Installation
|
|
41
56
|
|
|
42
57
|
```bash
|
|
43
58
|
npm install react-sticky-kit
|
|
44
|
-
# or
|
|
45
|
-
yarn add react-sticky-kit
|
|
46
|
-
# or
|
|
47
|
-
pnpm add react-sticky-kit
|
|
59
|
+
# or: pnpm add react-sticky-kit
|
|
48
60
|
```
|
|
49
61
|
|
|
50
|
-
##
|
|
51
|
-
- [Apple iOS Contact App](https://codesandbox.io/p/sandbox/dreamy-hofstadter-v9dzfz)
|
|
62
|
+
## Quick start: replacing section headers
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
Import the stylesheet once. Give sections enough content to scroll; headers replace
|
|
65
|
+
one another within the container's boundary.
|
|
54
66
|
|
|
55
67
|
```tsx
|
|
56
68
|
import { StickyContainer, StickyItem } from 'react-sticky-kit';
|
|
57
|
-
|
|
58
|
-
import 'react-sticky-kit/dist/style.css';
|
|
59
|
-
// or `import 'react-sticky-kit/style';` for more clean style path(require modern bundler tools)
|
|
69
|
+
import 'react-sticky-kit/style';
|
|
60
70
|
|
|
61
|
-
export default function
|
|
71
|
+
export default function Sections() {
|
|
62
72
|
return (
|
|
63
|
-
<StickyContainer
|
|
64
|
-
<StickyItem>
|
|
65
|
-
|
|
66
|
-
</StickyItem>
|
|
67
|
-
<
|
|
68
|
-
<StickyItem mode="replace">
|
|
69
|
-
<div>Another Sticky Header (replace mode)</div>
|
|
70
|
-
</StickyItem>
|
|
71
|
-
<div>More Content...</div>
|
|
73
|
+
<StickyContainer defaultMode="replace" offsetTop={48}>
|
|
74
|
+
<StickyItem><h2 style={{ margin: 0, padding: 12, background: '#fff' }}>Overview</h2></StickyItem>
|
|
75
|
+
<section style={{ minHeight: 600 }}>Overview content</section>
|
|
76
|
+
<StickyItem><h2 style={{ margin: 0, padding: 12, background: '#fff' }}>Details</h2></StickyItem>
|
|
77
|
+
<section style={{ minHeight: 600 }}>Details content</section>
|
|
72
78
|
</StickyContainer>
|
|
73
79
|
);
|
|
74
80
|
}
|
|
75
81
|
```
|
|
76
82
|
|
|
83
|
+
Change `defaultMode` to `"stack"` to retain previously reached headings. To keep a
|
|
84
|
+
page title above replacing section headings, add a first `<StickyItem mode="stack">`.
|
|
85
|
+
Offsets are relative to the **viewport**, including when scrolling an inner element.
|
|
86
|
+
For offsets relative to a scrollable panel, prefer native CSS sticky positioning.
|
|
87
|
+
|
|
88
|
+
## Which approach should I choose?
|
|
89
|
+
|
|
90
|
+
| Need | Start with |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| A single sticky navigation bar or simple grouped list | Native CSS `position: sticky` |
|
|
93
|
+
| A sidebar taller than the viewport | A sidebar-focused solution such as `react-sticky-box` |
|
|
94
|
+
| Dynamic-height headers that stack automatically | React Sticky Kit, `stack` mode |
|
|
95
|
+
| A persistent title above replacing section headings | React Sticky Kit, mixed modes |
|
|
96
|
+
| The current total sticky height | `onStickyItemsHeightChange` |
|
|
97
|
+
| Table columns, built-in virtualization, or hide-on-scroll navigation | A solution designed for that specific interaction |
|
|
98
|
+
|
|
99
|
+
React Sticky Kit uses fixed positioning while active. It is not a drop-in
|
|
100
|
+
replacement for scroll-container-relative sticky behavior. Ancestor transforms
|
|
101
|
+
and overflow clipping can affect fixed positioning.
|
|
102
|
+
|
|
103
|
+
React 17/18/19, SSR and TypeScript 5 NodeNext consumers are covered by package
|
|
104
|
+
checks. The ESM artifact is approximately **3.48 kB gzip**, excluding React and CSS;
|
|
105
|
+
there are no additional runtime dependencies. See the
|
|
106
|
+
[performance audit](docs/performance.md) for methods and tradeoffs.
|
|
107
|
+
|
|
108
|
+
For practical examples and existing-library migration, see the
|
|
109
|
+
[patterns and migration guide](docs/patterns-and-migration.md).
|
|
110
|
+
|
|
77
111
|
## Props
|
|
78
112
|
|
|
79
113
|
### `<StickyContainer />`
|
|
@@ -84,7 +118,7 @@ export default function Example() {
|
|
|
84
118
|
| `baseZIndex` | `number` | `200` | Base z-index for sticky items. Should be greater than the number of sticky items. |
|
|
85
119
|
| | | | In `replace` mode, z-index = baseZIndex - index; in `stack` mode, z-index = baseZIndex + index. |
|
|
86
120
|
| `onStickyItemsHeightChange` | `(height: number) => void` | | Callback when total sticky height changes |
|
|
87
|
-
| `constraint` | `'none'` | | Define the constraint for sticky behavior. 'none' = no
|
|
121
|
+
| `constraint` | `'none'` | | Define the constraint for sticky behavior. 'none' = no container boundary |
|
|
88
122
|
|
|
89
123
|
### `<StickyItem />`
|
|
90
124
|
| Prop | Type | Default | Description |
|
|
@@ -113,14 +147,9 @@ function Example() {
|
|
|
113
147
|
<div>Content...</div>
|
|
114
148
|
</StickyContainer>
|
|
115
149
|
|
|
116
|
-
{/* 2. No
|
|
150
|
+
{/* 2. No container boundary */}
|
|
117
151
|
<StickyContainer constraint="none">
|
|
118
|
-
<StickyItem><div>
|
|
119
|
-
<div>Content...</div>
|
|
120
|
-
</StickyContainer>
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
152
|
+
<StickyItem><div>Sticky even after the container leaves</div></StickyItem>
|
|
124
153
|
<div>Content...</div>
|
|
125
154
|
</StickyContainer>
|
|
126
155
|
</div>
|
|
@@ -130,11 +159,15 @@ function Example() {
|
|
|
130
159
|
|
|
131
160
|
### Special behavior of `constraint="none"`
|
|
132
161
|
|
|
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.
|
|
162
|
+
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. Offsets remain relative to the viewport; this differs from native CSS `position: sticky`, which is constrained by its scrolling ancestor.
|
|
134
163
|
|
|
135
164
|
In contrast, the default behavior (without specifying a constraint) only makes items sticky when their parent container is visible in the viewport.
|
|
136
165
|
|
|
137
|
-
##
|
|
166
|
+
## Run the demo
|
|
167
|
+
|
|
168
|
+
The demo builds as a standalone static site with `pnpm build:demo`. Its relative
|
|
169
|
+
asset paths work when hosted under a repository subdirectory.
|
|
170
|
+
See [deployment instructions](docs/demo-deployment.md) for GitHub Pages setup.
|
|
138
171
|
|
|
139
172
|
Run the demo locally:
|
|
140
173
|
|
|
@@ -158,7 +191,7 @@ React Sticky Kit supports Server-Side Rendering (SSR) and Static Site Generation
|
|
|
158
191
|
### Next.js Example
|
|
159
192
|
|
|
160
193
|
```tsx
|
|
161
|
-
//
|
|
194
|
+
// app/page.tsx (Next.js App Router)
|
|
162
195
|
import { StickyContainer, StickyItem } from 'react-sticky-kit'
|
|
163
196
|
import 'react-sticky-kit/dist/style.css'
|
|
164
197
|
|
|
@@ -174,28 +207,85 @@ export default function Home() {
|
|
|
174
207
|
}
|
|
175
208
|
```
|
|
176
209
|
|
|
210
|
+
For Pages Router, import the global stylesheet from `pages/_app.tsx`.
|
|
211
|
+
|
|
177
212
|
### Astro Example
|
|
178
213
|
|
|
214
|
+
Create a React component containing both `StickyContainer` and `StickyItem`, then
|
|
215
|
+
hydrate it from Astro:
|
|
216
|
+
|
|
179
217
|
```astro
|
|
180
218
|
---
|
|
181
|
-
|
|
182
|
-
import
|
|
183
|
-
import 'react-sticky-kit/dist/style.css'
|
|
219
|
+
import StickyLayout from '../components/StickyLayout.tsx'
|
|
220
|
+
import 'react-sticky-kit/style'
|
|
184
221
|
---
|
|
222
|
+
<StickyLayout client:load />
|
|
223
|
+
```
|
|
185
224
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
## Compatibility and updates
|
|
226
|
+
|
|
227
|
+
React 17 and later are supported. Import the stylesheet explicitly, using either
|
|
228
|
+
`react-sticky-kit/style` or `react-sticky-kit/dist/style.css`. ESM, CommonJS and the
|
|
229
|
+
UMD browser build are available; UMD requires a global `React`.
|
|
230
|
+
|
|
231
|
+
The built entry points include `"use client"` for Next.js App Router. Pages Router
|
|
232
|
+
and server rendering remain supported; sticky layout is applied after mounting.
|
|
233
|
+
For Astro, wrap the sticky layout in a React component and hydrate that component
|
|
234
|
+
as a single island.
|
|
235
|
+
|
|
236
|
+
Sticky offsets use the viewport, even when an event comes from a nested scrolling
|
|
237
|
+
element. Ancestor transforms and overflow clipping can affect fixed positioning;
|
|
238
|
+
use native CSS `position: sticky` if you need offsets relative to a scrolling ancestor.
|
|
239
|
+
|
|
240
|
+
When available, `ResizeObserver` updates dynamic content heights and widths, and
|
|
241
|
+
tracks ancestor and preceding sibling sizes that can move the container. Shared,
|
|
242
|
+
scoped `MutationObserver` subscriptions refresh these observations when the surrounding
|
|
243
|
+
DOM structure or relevant classes/styles change. Observers and global scroll/resize
|
|
244
|
+
listeners are shared across containers. No continuous polling is used.
|
|
245
|
+
Without `ResizeObserver`, measurements update on scroll, window resize and component
|
|
246
|
+
commits. Native browser scroll anchoring can move the viewport when content above
|
|
247
|
+
it changes; sticky positions follow the resulting viewport.
|
|
248
|
+
|
|
249
|
+
Wrapper padding, borders and `box-sizing` are accounted for when preserving the
|
|
250
|
+
placeholder and content width. Definite inline heights are retained. Fixed headers
|
|
251
|
+
also follow horizontal scrolling. Per-item layout metrics are cached and invalidated
|
|
252
|
+
by observed dimension changes, window resize, relevant DOM changes and React
|
|
253
|
+
commits. Inactive or fully replaced headers do not have their content heights measured during scrolling.
|
|
254
|
+
`onStickyItemsHeightChange` reports the final total once per animation frame, after
|
|
255
|
+
all scheduled containers have applied their styles, when it changes, including zero when no items remain sticky. It does not emit transient
|
|
256
|
+
per-item totals or callbacks after the container unmounts.
|
|
257
|
+
|
|
258
|
+
## Performance
|
|
259
|
+
|
|
260
|
+
Scheduled containers share one animation frame. Their geometry reads finish before
|
|
261
|
+
any sticky styles are written, and unchanged layouts skip repeated style handling.
|
|
262
|
+
Inactive content heights are not measured.
|
|
263
|
+
Prefer grouping related sections in one container rather than mounting a container
|
|
264
|
+
for every row. Large numbers of simultaneously stacked headers still require
|
|
265
|
+
per-frame geometry reads. See the [performance audit](docs/performance.md) for
|
|
266
|
+
stress-test findings and limitations.
|
|
267
|
+
|
|
268
|
+
## Development
|
|
269
|
+
|
|
270
|
+
Use Node.js 22.22.2+ or 24.15.0+ and the pnpm version pinned in `package.json`.
|
|
271
|
+
The development tools require these versions; the published library keeps its
|
|
272
|
+
React 17+ peer dependency range and targets ES2018.
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
pnpm install --frozen-lockfile
|
|
276
|
+
pnpm type-check
|
|
277
|
+
pnpm lint
|
|
278
|
+
pnpm test
|
|
279
|
+
pnpm build
|
|
280
|
+
pnpm check:package
|
|
281
|
+
pnpm check:compatibility # installs the tarball with React 17, 18 and 19
|
|
282
|
+
pnpm exec playwright install --with-deps chromium firefox webkit
|
|
283
|
+
pnpm test:browser
|
|
197
284
|
```
|
|
198
285
|
|
|
286
|
+
TypeScript 6 is used for development while the ESLint TypeScript plugin supports
|
|
287
|
+
versions below 6.1. Generated declarations use the existing public component API.
|
|
288
|
+
|
|
199
289
|
## Publish steps
|
|
200
290
|
|
|
201
291
|
`pnpm pub`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const MIN_BASE_Z_INDEX = 20;
|
|
2
|
+
export declare const DEFAULT_BASE_Z_INDEX = 200;
|
|
3
|
+
/** Replace the previous item, stack below it, or disable sticky behavior. */
|
|
4
|
+
export type IStickyMode = 'replace' | 'stack' | 'none';
|
|
5
|
+
export interface IStickyItemMeasurement {
|
|
6
|
+
height: number;
|
|
7
|
+
wrapperHeight: number;
|
|
8
|
+
width: number;
|
|
9
|
+
left: number;
|
|
10
|
+
}
|
|
11
|
+
export interface IStickyItemHandle {
|
|
12
|
+
el: HTMLElement;
|
|
13
|
+
content: HTMLElement;
|
|
14
|
+
mode: Exclude<IStickyMode, 'none'>;
|
|
15
|
+
measure: (rect: DOMRect) => IStickyItemMeasurement;
|
|
16
|
+
invalidate: () => void;
|
|
17
|
+
apply: (layout: (IStickyItemMeasurement & {
|
|
18
|
+
top: number;
|
|
19
|
+
index: number;
|
|
20
|
+
}) | null) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface IStickyGroupContextValue {
|
|
23
|
+
baseZIndex: number;
|
|
24
|
+
register: (handle: IStickyItemHandle) => () => void;
|
|
25
|
+
scheduleUpdate: () => void;
|
|
26
|
+
mode: IStickyMode;
|
|
27
|
+
}
|
|
28
|
+
export declare const StickyGroupContext: import("react").Context<IStickyGroupContextValue | null>;
|
|
29
|
+
export declare function useStickyContext(): IStickyGroupContextValue | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const MIN_BASE_Z_INDEX = 20;
|
|
2
|
+
export declare const DEFAULT_BASE_Z_INDEX = 200;
|
|
3
|
+
/** Replace the previous item, stack below it, or disable sticky behavior. */
|
|
4
|
+
export type IStickyMode = 'replace' | 'stack' | 'none';
|
|
5
|
+
export interface IStickyItemMeasurement {
|
|
6
|
+
height: number;
|
|
7
|
+
wrapperHeight: number;
|
|
8
|
+
width: number;
|
|
9
|
+
left: number;
|
|
10
|
+
}
|
|
11
|
+
export interface IStickyItemHandle {
|
|
12
|
+
el: HTMLElement;
|
|
13
|
+
content: HTMLElement;
|
|
14
|
+
mode: Exclude<IStickyMode, 'none'>;
|
|
15
|
+
measure: (rect: DOMRect) => IStickyItemMeasurement;
|
|
16
|
+
invalidate: () => void;
|
|
17
|
+
apply: (layout: (IStickyItemMeasurement & {
|
|
18
|
+
top: number;
|
|
19
|
+
index: number;
|
|
20
|
+
}) | null) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface IStickyGroupContextValue {
|
|
23
|
+
baseZIndex: number;
|
|
24
|
+
register: (handle: IStickyItemHandle) => () => void;
|
|
25
|
+
scheduleUpdate: () => void;
|
|
26
|
+
mode: IStickyMode;
|
|
27
|
+
}
|
|
28
|
+
export declare const StickyGroupContext: import("react").Context<IStickyGroupContextValue | null>;
|
|
29
|
+
export declare function useStickyContext(): IStickyGroupContextValue | null;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type { IStickyMode } from './context.cjs';
|
|
3
|
+
export * from './sticky-item.cjs';
|
|
4
|
+
export interface IStickyContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* The offset from the top of the viewport for sticky elements. Default is 0.
|
|
8
|
+
*/
|
|
9
|
+
offsetTop?: number;
|
|
10
|
+
/**
|
|
11
|
+
* base z-index for sticky items. Default is 200. minimum z-index is 20.
|
|
12
|
+
* * - When using the `replace` mode, the z-index of a `StickyItem` is calculated as `baseZIndex` minus its index within the container.
|
|
13
|
+
* * - When using the `stack` mode, the z-index of a `StickyItem` is calculated as `baseZIndex` plus its index.
|
|
14
|
+
* * should be greater than number of sticky items in the container.
|
|
15
|
+
* * use it when you need nest StickyContainer or need to change z-index of sticky items.
|
|
16
|
+
*/
|
|
17
|
+
baseZIndex?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Default sticky mode for the group. 'none' disables sticky behavior.
|
|
20
|
+
*/
|
|
21
|
+
defaultMode?: 'replace' | 'stack' | 'none';
|
|
22
|
+
/**
|
|
23
|
+
* Callback triggered when the total height of all currently sticky items changes.
|
|
24
|
+
* @param height The total height of all sticky items inside the container.
|
|
25
|
+
*/
|
|
26
|
+
onStickyItemsHeightChange?: (height: number) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Define the constraint for sticky behavior
|
|
29
|
+
* - undefined (default): Sticky items will stop being sticky when StickyContainer leaves viewport
|
|
30
|
+
* - 'none': No container boundary; offsets remain viewport-relative
|
|
31
|
+
*/
|
|
32
|
+
constraint?: 'none';
|
|
33
|
+
}
|
|
34
|
+
export declare function StickyContainer({ children, offsetTop, baseZIndex, onStickyItemsHeightChange, defaultMode, constraint, className, ...rest }: IStickyContainerProps): React.ReactElement<any, any>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,56 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
children:
|
|
6
|
-
/**
|
|
7
|
-
* The offset from the top of the viewport for sticky elements. Default is 0.
|
|
8
|
-
*/
|
|
9
|
-
offsetTop?: number;
|
|
10
|
-
/**
|
|
11
|
-
* base z-index for sticky items. Default is 200. minimum z-index is 20.
|
|
12
|
-
* * - When using the `replace` mode, the z-index of a `StickyItem` is calculated as `baseZIndex` minus its index within the container.
|
|
13
|
-
* * - When using the `stack` mode, the z-index of a `StickyItem` is calculated as `baseZIndex` plus its index.
|
|
14
|
-
* * should be greater than number of sticky items in the container.
|
|
15
|
-
* *
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
* -
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export declare interface IStickyItemProps extends default_2.HTMLAttributes<HTMLDivElement> {
|
|
37
|
-
children: default_2.ReactNode;
|
|
38
|
-
/**
|
|
39
|
-
* Sticky mode for this item. Defaults to the StickyContainer's mode if not specified.
|
|
40
|
-
*/
|
|
41
|
-
mode?: IStickyMode;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Sticky mode:
|
|
46
|
-
* - 'replace': This item replaces the previous sticky item; its height is set to the previous sticky item's height.
|
|
47
|
-
* - 'stack': This item stacks on top of previous sticky items; its height is set to 0.
|
|
48
|
-
* - 'none': This item is not sticky.
|
|
49
|
-
*/
|
|
50
|
-
export declare type IStickyMode = 'replace' | 'stack' | 'none';
|
|
51
|
-
|
|
52
|
-
export declare function StickyContainer({ children, offsetTop, baseZIndex, onStickyItemsHeightChange, defaultMode, constraint, ...rest }: IStickyContainerProps): JSX.Element;
|
|
53
|
-
|
|
54
|
-
export declare function StickyItem({ mode, children, className, ...rest }: IStickyItemProps): JSX.Element;
|
|
55
|
-
|
|
56
|
-
export { }
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type { IStickyMode } from './context.js';
|
|
3
|
+
export * from './sticky-item.js';
|
|
4
|
+
export interface IStickyContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* The offset from the top of the viewport for sticky elements. Default is 0.
|
|
8
|
+
*/
|
|
9
|
+
offsetTop?: number;
|
|
10
|
+
/**
|
|
11
|
+
* base z-index for sticky items. Default is 200. minimum z-index is 20.
|
|
12
|
+
* * - When using the `replace` mode, the z-index of a `StickyItem` is calculated as `baseZIndex` minus its index within the container.
|
|
13
|
+
* * - When using the `stack` mode, the z-index of a `StickyItem` is calculated as `baseZIndex` plus its index.
|
|
14
|
+
* * should be greater than number of sticky items in the container.
|
|
15
|
+
* * use it when you need nest StickyContainer or need to change z-index of sticky items.
|
|
16
|
+
*/
|
|
17
|
+
baseZIndex?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Default sticky mode for the group. 'none' disables sticky behavior.
|
|
20
|
+
*/
|
|
21
|
+
defaultMode?: 'replace' | 'stack' | 'none';
|
|
22
|
+
/**
|
|
23
|
+
* Callback triggered when the total height of all currently sticky items changes.
|
|
24
|
+
* @param height The total height of all sticky items inside the container.
|
|
25
|
+
*/
|
|
26
|
+
onStickyItemsHeightChange?: (height: number) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Define the constraint for sticky behavior
|
|
29
|
+
* - undefined (default): Sticky items will stop being sticky when StickyContainer leaves viewport
|
|
30
|
+
* - 'none': No container boundary; offsets remain viewport-relative
|
|
31
|
+
*/
|
|
32
|
+
constraint?: 'none';
|
|
33
|
+
}
|
|
34
|
+
export declare function StickyContainer({ children, offsetTop, baseZIndex, onStickyItemsHeightChange, defaultMode, constraint, className, ...rest }: IStickyContainerProps): React.ReactElement<any, any>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("react");c=s(c,1);var l=(0,c.createContext)(null);function u(){return(0,c.useContext)(l)}var d=new Map,f=null;function p(e,t){if(typeof ResizeObserver>`u`)return()=>{};f!=null||(f=new ResizeObserver(e=>{let t=new Map;for(let r of e){var n;for(let e of(n=d.get(r.target))==null?[]:n){let n=t.get(e);n?n.push(r):t.set(e,[r])}}for(let[e,n]of t)e(n)}));let n=d.get(e);return n||(n=new Set,d.set(e,n),f.observe(e)),n.add(t),()=>{n.delete(t),n.size||(d.delete(e),f==null||f.unobserve(e)),d.size||(f==null||f.disconnect(),f=null)}}var m=new Map,h=new Map,g=null,_=!1;function v(){for(let e of m.values())e()}function y(){_||(_=!0,Promise.resolve().then(()=>{_=!1,m.size&&b()}))}function b(){let e=new Set,t=new Set;for(let n of m.keys())for(let r=n;r!=null&&r.parentElement;r=r.parentElement){let n=r.parentElement;e.add(n);for(let t=r.previousElementSibling;t&&!e.has(t);t=t.previousElementSibling)e.add(t);if(t.has(n))break;t.add(n)}let n=[...h.keys()].filter(t=>!e.has(t));n.length&&(g==null||g.disconnect());for(let e of n)h.get(e)(),h.delete(e);for(let t of e){let e=!h.has(t);e&&h.set(t,p(t,v)),(e||n.length)&&(g==null||g.observe(t,{childList:!0,attributes:!0,attributeFilter:[`class`,`style`]}))}}function x(e,t){return m.set(e,t),!g&&typeof MutationObserver<`u`&&(g=new MutationObserver(e=>{e.some(e=>e.type===`childList`)&&y(),v()})),y(),()=>{if(m.delete(e),m.size)y();else{g==null||g.disconnect(),g=null;for(let e of h.values())e();h.clear()}}}var S=new Set,C=new Set,w=null;function T(e){S.has(e)&&(C.add(e),w!=null||(w=requestAnimationFrame(O)))}function E(){for(let e of S)T(e)}function D(){for(let e of S)e.resize(),T(e)}function O(){w=null;let e=[...C];C.clear();let t=[],n=[],r=[];for(let n of e)if(S.has(n))try{let e=n.read();e&&t.push({client:n,write:e})}catch(e){r.push(e)}for(let{client:e,write:i}of t)if(S.has(e))try{let t=i();t&&n.push({client:e,notify:t})}catch(e){r.push(e)}for(let{client:e,notify:t}of n)if(S.has(e))try{t()}catch(e){r.push(e)}if(r.length)throw r[0]}function k(e,t){let n={read:e,resize:t};return S.size||(window.addEventListener(`scroll`,E,{passive:!0,capture:!0}),window.addEventListener(`resize`,D,{passive:!0})),S.add(n),T(n),{schedule:()=>T(n),stop:()=>{S.delete(n),C.delete(n),!C.size&&w!==null&&(cancelAnimationFrame(w),w=null),S.size||(window.removeEventListener(`scroll`,E,!0),window.removeEventListener(`resize`,D))}}}function A({mode:e,children:t,className:n,...r}){var i;let a=u(),o=(0,c.useRef)(null),s=(0,c.useRef)(null),l=a==null?void 0:a.register,d=e==null?a==null?void 0:a.mode:e,f=a==null?void 0:a.baseZIndex,p=(i=r.style)==null?void 0:i.height,m=(0,c.useRef)(null),h=a==null?void 0:a.scheduleUpdate;return(0,c.useEffect)(()=>{let e=o.current,t=s.current;if(!e||!t)return;let n=typeof p==`number`?`${p}px`:p==null?``:p;if(j(e,`height`,n),!l||!d||d===`none`)return;let r=p===void 0||[`auto`,`initial`,`unset`,`revert`,`revert-layer`,`fit-content`,`min-content`,`max-content`].includes(String(p)),i=!1,a=null,c=()=>{if(i){i=!1,a=null,t.classList.remove(`is-sticky`),e.style.height=n;for(let e of[`top`,`left`,`width`,`z-index`])t.style.removeProperty(e)}},u=null,h={el:e,content:t,mode:d,apply:n=>{if(n===null){c();return}let{top:o,wrapperHeight:s,width:l,left:u,index:p}=n;a&&a.top===o&&a.wrapperHeight===s&&a.width===l&&a.left===u&&a.index===p||(a=n,r&&j(e,`height`,`${s}px`),j(t,`top`,`${o}px`),j(t,`left`,`${u}px`),j(t,`width`,`${l}px`),j(t,`zIndex`,`${(f==null?200:f)+(d===`replace`?-p:p)}`),i||(i=!0,t.classList.add(`is-sticky`)))},measure:n=>{u!=null||(u=M(e,t));let r=t.getBoundingClientRect().height;return{height:r,wrapperHeight:r+u.heightInset,width:Math.max(0,n.width-u.widthInset),left:n.left+u.leftInset}},invalidate:()=>{u=null,a=null}};m.current=h;let g=l(h);return()=>{m.current=null,g(),c()}},[l,d,f,p]),(0,c.useEffect)(()=>{var e;(e=m.current)==null||e.invalidate(),h==null||h()}),c.default.createElement(`div`,{...r,className:[`oe-sticky-item`,n].filter(Boolean).join(` `),ref:o},c.default.createElement(`div`,{className:`oe-sticky-content`,ref:s},t))}function j(e,t,n){e.style[t]!==n&&(e.style[t]=n)}function M(e,t){let n=getComputedStyle(e),r=getComputedStyle(t),i=e=>Number.parseFloat(e)||0,a=e=>i(e.paddingLeft)+i(e.paddingRight)+i(e.borderLeftWidth)+i(e.borderRightWidth);return{heightInset:n.boxSizing===`border-box`?i(n.paddingTop)+i(n.paddingBottom)+i(n.borderTopWidth)+i(n.borderBottomWidth):0,widthInset:a(n)+(r.boxSizing===`border-box`?0:a(r)),leftInset:i(n.paddingLeft)+i(n.borderLeftWidth)}}function N({children:e,offsetTop:t=0,baseZIndex:n,onStickyItemsHeightChange:r,defaultMode:i=`replace`,constraint:a,className:o,...s}){let u=(0,c.useRef)(null),d=(0,c.useRef)(new Set),f=(0,c.useRef)(null),m=(0,c.useRef)(new Map),h=(0,c.useRef)(!1),g=(0,c.useRef)(null),_=(0,c.useRef)(new Map),v=(0,c.useRef)(0),y=(0,c.useRef)({offsetTop:t,constraint:a,onStickyItemsHeightChange:r}),b=(0,c.useCallback)(()=>{var e;return(e=g.current)==null?void 0:e.schedule()},[]),S=(0,c.useCallback)(()=>{var e;let t=u.current;if(!t)return;let n=y.current,r=t.getBoundingClientRect();if(!(n.constraint===`none`||r.top<=n.offsetTop&&r.bottom>=n.offsetTop))return()=>{if(h.current){t.classList.remove(`can-sticky`);for(let e of d.current)e.apply(null);h.current=!1}if(v.current!==0)return v.current=0,()=>{var e;return(e=n.onStickyItemsHeightChange)==null?void 0:e.call(n,0)}};let i=((e=f.current)==null?f.current=[...d.current]:e).map(e=>({item:e,rect:e.el.getBoundingClientRect()})).sort((e,t)=>{let n=e.rect.top-t.rect.top;return n===0?e.item.el.compareDocumentPosition(t.item.el)&Node.DOCUMENT_POSITION_FOLLOWING?-1:1:n}),a=n.offsetTop,o=0,s=i.map(({item:e,rect:t},n)=>{var r;if(t.top>a)return null;let s=(r=i[n+1])==null?void 0:r.rect.top;if(e.mode===`replace`&&s!==void 0&&s<a)return null;let c=e.measure(t),l=e.mode===`replace`&&s!==void 0?Math.min(a,s-c.height):a;return o+=c.height,e.mode===`stack`&&(a+=c.height),{...c,top:l,index:n}}),c=n.constraint===`none`?0:Math.min(0,r.bottom-n.offsetTop-o);return()=>{if(h.current=s.some(e=>e!==null),t.classList.toggle(`can-sticky`,h.current),i.forEach(({item:e},t)=>{let n=s[t];n&&(n.top+=c),e.apply(n)}),v.current!==o)return v.current=o,()=>{var e;return(e=n.onStickyItemsHeightChange)==null?void 0:e.call(n,o)}}},[]),C=(0,c.useCallback)(e=>{var t;for(let n of e)(t=m.current.get(n.target))==null||t.invalidate();b()},[b]),w=(0,c.useCallback)(()=>{for(let e of d.current)e.invalidate()},[]),T=(0,c.useCallback)(e=>(d.current.add(e),f.current=null,m.current.set(e.el,e),m.current.set(e.content,e),g.current&&(_.current.set(e.el,p(e.el,C)),_.current.set(e.content,p(e.content,C))),b(),()=>{var t,n;(t=_.current.get(e.el))==null||t(),(n=_.current.get(e.content))==null||n(),_.current.delete(e.el),_.current.delete(e.content),m.current.delete(e.el),m.current.delete(e.content),d.current.delete(e),f.current=null,b()}),[b,C]);(0,c.useEffect)(()=>{g.current=k(S,w);let e;if(typeof ResizeObserver<`u`){u.current&&(_.current.set(u.current,p(u.current,C)),e=x(u.current,()=>{w(),b()}));for(let e of d.current)_.current.set(e.el,p(e.el,C)),_.current.set(e.content,p(e.content,C))}let t=_.current;return()=>{var n;(n=g.current)==null||n.stop(),g.current=null,e==null||e();for(let e of t.values())e();t.clear()}},[S,w,b,C]),(0,c.useEffect)(()=>{y.current={offsetTop:t,constraint:a,onStickyItemsHeightChange:r};for(let e of d.current)e.invalidate();b()});let E=n===void 0?200:Math.max(Number(n)||0,20),D=(0,c.useMemo)(()=>({register:T,scheduleUpdate:b,baseZIndex:E,mode:i}),[T,b,E,i]);return c.default.createElement(l.Provider,{value:D},c.default.createElement(`div`,{...s,ref:u,className:[`oe-sticky-container`,o].filter(Boolean).join(` `)},e))}exports.StickyContainer=N,exports.StickyItem=A;
|
package/dist/react-sticky.es.js
CHANGED
|
@@ -1,157 +1,329 @@
|
|
|
1
|
-
|
|
2
|
-
import { createContext as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import e, { createContext as t, useCallback as n, useContext as r, useEffect as i, useMemo as a, useRef as o } from "react";
|
|
3
|
+
//#region src/context.ts
|
|
4
|
+
var s = t(null);
|
|
5
|
+
function c() {
|
|
6
|
+
return r(s);
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
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)
|
|
38
|
-
return;
|
|
39
|
-
const H = n(a.getBoundingClientRect().height);
|
|
40
|
-
return () => {
|
|
41
|
-
N(t, ["height"]), N(a, ["top", "z-index", "width"]), H();
|
|
42
|
-
};
|
|
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 }) });
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/resize-observer.ts
|
|
10
|
+
var l = /* @__PURE__ */ new Map(), u = null;
|
|
11
|
+
function d(e, t) {
|
|
12
|
+
if (typeof ResizeObserver > "u") return () => {};
|
|
13
|
+
u != null || (u = new ResizeObserver((e) => {
|
|
14
|
+
let t = /* @__PURE__ */ new Map();
|
|
15
|
+
for (let r of e) {
|
|
16
|
+
var n;
|
|
17
|
+
for (let e of (n = l.get(r.target)) == null ? [] : n) {
|
|
18
|
+
let n = t.get(e);
|
|
19
|
+
n ? n.push(r) : t.set(e, [r]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (let [e, n] of t) e(n);
|
|
23
|
+
}));
|
|
24
|
+
let n = l.get(e);
|
|
25
|
+
return n || (n = /* @__PURE__ */ new Set(), l.set(e, n), u.observe(e)), n.add(t), () => {
|
|
26
|
+
n.delete(t), n.size || (l.delete(e), u == null || u.unobserve(e)), l.size || (u == null || u.disconnect(), u = null);
|
|
27
|
+
};
|
|
44
28
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/layout-observer.ts
|
|
31
|
+
var f = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map(), m = null, h = !1;
|
|
32
|
+
function g() {
|
|
33
|
+
for (let e of f.values()) e();
|
|
49
34
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
onStickyItemsHeightChange: I,
|
|
55
|
-
defaultMode: e = "replace",
|
|
56
|
-
constraint: g,
|
|
57
|
-
...S
|
|
58
|
-
}) {
|
|
59
|
-
const m = k(null), o = k([]), l = k(null), t = k({
|
|
60
|
-
fixedOffsetTop: y,
|
|
61
|
-
defaultMode: e,
|
|
62
|
-
stickyItemsHeight: 0,
|
|
63
|
-
onStickyItemsHeightChange: I,
|
|
64
|
-
lastCanSticky: !1
|
|
65
|
-
});
|
|
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);
|
|
103
|
-
}, 0), () => {
|
|
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);
|
|
108
|
-
}, 0);
|
|
109
|
-
};
|
|
110
|
-
}, []);
|
|
111
|
-
E(() => {
|
|
112
|
-
t.current.fixedOffsetTop = y, t.current.defaultMode = e, setTimeout(() => {
|
|
113
|
-
n();
|
|
114
|
-
}, 10);
|
|
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();
|
|
118
|
-
}), [n]);
|
|
119
|
-
E(() => (window.addEventListener("scroll", n, { passive: !0 }), window.addEventListener("resize", n, { passive: !0 }), () => {
|
|
120
|
-
window.removeEventListener("scroll", n), window.removeEventListener("resize", n);
|
|
121
|
-
}), [n]);
|
|
122
|
-
const B = _(() => D(h), [h]);
|
|
123
|
-
return /* @__PURE__ */ w(
|
|
124
|
-
$.Provider,
|
|
125
|
-
{
|
|
126
|
-
value: {
|
|
127
|
-
register: x,
|
|
128
|
-
baseZIndex: B,
|
|
129
|
-
updateStickyItemsHeight: H,
|
|
130
|
-
fixedOffsetTop: y,
|
|
131
|
-
mode: e
|
|
132
|
-
},
|
|
133
|
-
children: /* @__PURE__ */ w(
|
|
134
|
-
"div",
|
|
135
|
-
{
|
|
136
|
-
...S,
|
|
137
|
-
ref: m,
|
|
138
|
-
className: "oe-sticky-container",
|
|
139
|
-
children: f
|
|
140
|
-
}
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
);
|
|
35
|
+
function _() {
|
|
36
|
+
h || (h = !0, Promise.resolve().then(() => {
|
|
37
|
+
h = !1, f.size && v();
|
|
38
|
+
}));
|
|
144
39
|
}
|
|
145
|
-
function
|
|
146
|
-
|
|
40
|
+
function v() {
|
|
41
|
+
let e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set();
|
|
42
|
+
for (let n of f.keys()) for (let r = n; r != null && r.parentElement; r = r.parentElement) {
|
|
43
|
+
let n = r.parentElement;
|
|
44
|
+
e.add(n);
|
|
45
|
+
for (let t = r.previousElementSibling; t && !e.has(t); t = t.previousElementSibling) e.add(t);
|
|
46
|
+
if (t.has(n)) break;
|
|
47
|
+
t.add(n);
|
|
48
|
+
}
|
|
49
|
+
let n = [...p.keys()].filter((t) => !e.has(t));
|
|
50
|
+
n.length && (m == null || m.disconnect());
|
|
51
|
+
for (let e of n) p.get(e)(), p.delete(e);
|
|
52
|
+
for (let t of e) {
|
|
53
|
+
let e = !p.has(t);
|
|
54
|
+
e && p.set(t, d(t, g)), (e || n.length) && (m == null || m.observe(t, {
|
|
55
|
+
childList: !0,
|
|
56
|
+
attributes: !0,
|
|
57
|
+
attributeFilter: ["class", "style"]
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
147
60
|
}
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
61
|
+
function y(e, t) {
|
|
62
|
+
return f.set(e, t), !m && typeof MutationObserver < "u" && (m = new MutationObserver((e) => {
|
|
63
|
+
e.some((e) => e.type === "childList") && _(), g();
|
|
64
|
+
})), _(), () => {
|
|
65
|
+
if (f.delete(e), f.size) _();
|
|
66
|
+
else {
|
|
67
|
+
m == null || m.disconnect(), m = null;
|
|
68
|
+
for (let e of p.values()) e();
|
|
69
|
+
p.clear();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
153
72
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/scheduler.ts
|
|
75
|
+
var b = /* @__PURE__ */ new Set(), x = /* @__PURE__ */ new Set(), S = null;
|
|
76
|
+
function C(e) {
|
|
77
|
+
b.has(e) && (x.add(e), S != null || (S = requestAnimationFrame(E)));
|
|
78
|
+
}
|
|
79
|
+
function w() {
|
|
80
|
+
for (let e of b) C(e);
|
|
81
|
+
}
|
|
82
|
+
function T() {
|
|
83
|
+
for (let e of b) e.resize(), C(e);
|
|
84
|
+
}
|
|
85
|
+
function E() {
|
|
86
|
+
S = null;
|
|
87
|
+
let e = [...x];
|
|
88
|
+
x.clear();
|
|
89
|
+
let t = [], n = [], r = [];
|
|
90
|
+
for (let n of e) if (b.has(n)) try {
|
|
91
|
+
let e = n.read();
|
|
92
|
+
e && t.push({
|
|
93
|
+
client: n,
|
|
94
|
+
write: e
|
|
95
|
+
});
|
|
96
|
+
} catch (e) {
|
|
97
|
+
r.push(e);
|
|
98
|
+
}
|
|
99
|
+
for (let { client: e, write: i } of t) if (b.has(e)) try {
|
|
100
|
+
let t = i();
|
|
101
|
+
t && n.push({
|
|
102
|
+
client: e,
|
|
103
|
+
notify: t
|
|
104
|
+
});
|
|
105
|
+
} catch (e) {
|
|
106
|
+
r.push(e);
|
|
107
|
+
}
|
|
108
|
+
for (let { client: e, notify: t } of n) if (b.has(e)) try {
|
|
109
|
+
t();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
r.push(e);
|
|
112
|
+
}
|
|
113
|
+
if (r.length) throw r[0];
|
|
114
|
+
}
|
|
115
|
+
function D(e, t) {
|
|
116
|
+
let n = {
|
|
117
|
+
read: e,
|
|
118
|
+
resize: t
|
|
119
|
+
};
|
|
120
|
+
return b.size || (window.addEventListener("scroll", w, {
|
|
121
|
+
passive: !0,
|
|
122
|
+
capture: !0
|
|
123
|
+
}), window.addEventListener("resize", T, { passive: !0 })), b.add(n), C(n), {
|
|
124
|
+
schedule: () => C(n),
|
|
125
|
+
stop: () => {
|
|
126
|
+
b.delete(n), x.delete(n), !x.size && S !== null && (cancelAnimationFrame(S), S = null), b.size || (window.removeEventListener("scroll", w, !0), window.removeEventListener("resize", T));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/sticky-item.tsx
|
|
132
|
+
function O({ mode: t, children: n, className: r, ...a }) {
|
|
133
|
+
var s;
|
|
134
|
+
let l = c(), u = o(null), d = o(null), f = l == null ? void 0 : l.register, p = t == null ? l == null ? void 0 : l.mode : t, m = l == null ? void 0 : l.baseZIndex, h = (s = a.style) == null ? void 0 : s.height, g = o(null), _ = l == null ? void 0 : l.scheduleUpdate;
|
|
135
|
+
return i(() => {
|
|
136
|
+
let e = u.current, t = d.current;
|
|
137
|
+
if (!e || !t) return;
|
|
138
|
+
let n = typeof h == "number" ? `${h}px` : h == null ? "" : h;
|
|
139
|
+
if (k(e, "height", n), !f || !p || p === "none") return;
|
|
140
|
+
let r = h === void 0 || [
|
|
141
|
+
"auto",
|
|
142
|
+
"initial",
|
|
143
|
+
"unset",
|
|
144
|
+
"revert",
|
|
145
|
+
"revert-layer",
|
|
146
|
+
"fit-content",
|
|
147
|
+
"min-content",
|
|
148
|
+
"max-content"
|
|
149
|
+
].includes(String(h)), i = !1, a = null, o = () => {
|
|
150
|
+
if (i) {
|
|
151
|
+
i = !1, a = null, t.classList.remove("is-sticky"), e.style.height = n;
|
|
152
|
+
for (let e of [
|
|
153
|
+
"top",
|
|
154
|
+
"left",
|
|
155
|
+
"width",
|
|
156
|
+
"z-index"
|
|
157
|
+
]) t.style.removeProperty(e);
|
|
158
|
+
}
|
|
159
|
+
}, s = null, c = {
|
|
160
|
+
el: e,
|
|
161
|
+
content: t,
|
|
162
|
+
mode: p,
|
|
163
|
+
apply: (n) => {
|
|
164
|
+
if (n === null) {
|
|
165
|
+
o();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
let { top: s, wrapperHeight: c, width: l, left: u, index: d } = n;
|
|
169
|
+
a && a.top === s && a.wrapperHeight === c && a.width === l && a.left === u && a.index === d || (a = n, r && k(e, "height", `${c}px`), k(t, "top", `${s}px`), k(t, "left", `${u}px`), k(t, "width", `${l}px`), k(t, "zIndex", `${(m == null ? 200 : m) + (p === "replace" ? -d : d)}`), i || (i = !0, t.classList.add("is-sticky")));
|
|
170
|
+
},
|
|
171
|
+
measure: (n) => {
|
|
172
|
+
s != null || (s = A(e, t));
|
|
173
|
+
let r = t.getBoundingClientRect().height;
|
|
174
|
+
return {
|
|
175
|
+
height: r,
|
|
176
|
+
wrapperHeight: r + s.heightInset,
|
|
177
|
+
width: Math.max(0, n.width - s.widthInset),
|
|
178
|
+
left: n.left + s.leftInset
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
invalidate: () => {
|
|
182
|
+
s = null, a = null;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
g.current = c;
|
|
186
|
+
let l = f(c);
|
|
187
|
+
return () => {
|
|
188
|
+
g.current = null, l(), o();
|
|
189
|
+
};
|
|
190
|
+
}, [
|
|
191
|
+
f,
|
|
192
|
+
p,
|
|
193
|
+
m,
|
|
194
|
+
h
|
|
195
|
+
]), i(() => {
|
|
196
|
+
var e;
|
|
197
|
+
(e = g.current) == null || e.invalidate(), _ == null || _();
|
|
198
|
+
}), /* @__PURE__ */ e.createElement("div", {
|
|
199
|
+
...a,
|
|
200
|
+
className: ["oe-sticky-item", r].filter(Boolean).join(" "),
|
|
201
|
+
ref: u
|
|
202
|
+
}, /* @__PURE__ */ e.createElement("div", {
|
|
203
|
+
className: "oe-sticky-content",
|
|
204
|
+
ref: d
|
|
205
|
+
}, n));
|
|
206
|
+
}
|
|
207
|
+
function k(e, t, n) {
|
|
208
|
+
e.style[t] !== n && (e.style[t] = n);
|
|
209
|
+
}
|
|
210
|
+
function A(e, t) {
|
|
211
|
+
let n = getComputedStyle(e), r = getComputedStyle(t), i = (e) => Number.parseFloat(e) || 0, a = (e) => i(e.paddingLeft) + i(e.paddingRight) + i(e.borderLeftWidth) + i(e.borderRightWidth);
|
|
212
|
+
return {
|
|
213
|
+
heightInset: n.boxSizing === "border-box" ? i(n.paddingTop) + i(n.paddingBottom) + i(n.borderTopWidth) + i(n.borderBottomWidth) : 0,
|
|
214
|
+
widthInset: a(n) + (r.boxSizing === "border-box" ? 0 : a(r)),
|
|
215
|
+
leftInset: i(n.paddingLeft) + i(n.borderLeftWidth)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/index.tsx
|
|
220
|
+
function j({ children: t, offsetTop: r = 0, baseZIndex: c, onStickyItemsHeightChange: l, defaultMode: u = "replace", constraint: f, className: p, ...m }) {
|
|
221
|
+
let h = o(null), g = o(/* @__PURE__ */ new Set()), _ = o(null), v = o(/* @__PURE__ */ new Map()), b = o(!1), x = o(null), S = o(/* @__PURE__ */ new Map()), C = o(0), w = o({
|
|
222
|
+
offsetTop: r,
|
|
223
|
+
constraint: f,
|
|
224
|
+
onStickyItemsHeightChange: l
|
|
225
|
+
}), T = n(() => {
|
|
226
|
+
var e;
|
|
227
|
+
return (e = x.current) == null ? void 0 : e.schedule();
|
|
228
|
+
}, []), E = n(() => {
|
|
229
|
+
var e;
|
|
230
|
+
let t = h.current;
|
|
231
|
+
if (!t) return;
|
|
232
|
+
let n = w.current, r = t.getBoundingClientRect();
|
|
233
|
+
if (!(n.constraint === "none" || r.top <= n.offsetTop && r.bottom >= n.offsetTop)) return () => {
|
|
234
|
+
if (b.current) {
|
|
235
|
+
t.classList.remove("can-sticky");
|
|
236
|
+
for (let e of g.current) e.apply(null);
|
|
237
|
+
b.current = !1;
|
|
238
|
+
}
|
|
239
|
+
if (C.current !== 0) return C.current = 0, () => {
|
|
240
|
+
var e;
|
|
241
|
+
return (e = n.onStickyItemsHeightChange) == null ? void 0 : e.call(n, 0);
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
let i = ((e = _.current) == null ? _.current = [...g.current] : e).map((e) => ({
|
|
245
|
+
item: e,
|
|
246
|
+
rect: e.el.getBoundingClientRect()
|
|
247
|
+
})).sort((e, t) => {
|
|
248
|
+
let n = e.rect.top - t.rect.top;
|
|
249
|
+
return n === 0 ? e.item.el.compareDocumentPosition(t.item.el) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1 : n;
|
|
250
|
+
}), a = n.offsetTop, o = 0, s = i.map(({ item: e, rect: t }, n) => {
|
|
251
|
+
var r;
|
|
252
|
+
if (t.top > a) return null;
|
|
253
|
+
let s = (r = i[n + 1]) == null ? void 0 : r.rect.top;
|
|
254
|
+
if (e.mode === "replace" && s !== void 0 && s < a) return null;
|
|
255
|
+
let c = e.measure(t), l = e.mode === "replace" && s !== void 0 ? Math.min(a, s - c.height) : a;
|
|
256
|
+
return o += c.height, e.mode === "stack" && (a += c.height), {
|
|
257
|
+
...c,
|
|
258
|
+
top: l,
|
|
259
|
+
index: n
|
|
260
|
+
};
|
|
261
|
+
}), c = n.constraint === "none" ? 0 : Math.min(0, r.bottom - n.offsetTop - o);
|
|
262
|
+
return () => {
|
|
263
|
+
if (b.current = s.some((e) => e !== null), t.classList.toggle("can-sticky", b.current), i.forEach(({ item: e }, t) => {
|
|
264
|
+
let n = s[t];
|
|
265
|
+
n && (n.top += c), e.apply(n);
|
|
266
|
+
}), C.current !== o) return C.current = o, () => {
|
|
267
|
+
var e;
|
|
268
|
+
return (e = n.onStickyItemsHeightChange) == null ? void 0 : e.call(n, o);
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
}, []), O = n((e) => {
|
|
272
|
+
var t;
|
|
273
|
+
for (let n of e) (t = v.current.get(n.target)) == null || t.invalidate();
|
|
274
|
+
T();
|
|
275
|
+
}, [T]), k = n(() => {
|
|
276
|
+
for (let e of g.current) e.invalidate();
|
|
277
|
+
}, []), A = n((e) => (g.current.add(e), _.current = null, v.current.set(e.el, e), v.current.set(e.content, e), x.current && (S.current.set(e.el, d(e.el, O)), S.current.set(e.content, d(e.content, O))), T(), () => {
|
|
278
|
+
var t, n;
|
|
279
|
+
(t = S.current.get(e.el)) == null || t(), (n = S.current.get(e.content)) == null || n(), S.current.delete(e.el), S.current.delete(e.content), v.current.delete(e.el), v.current.delete(e.content), g.current.delete(e), _.current = null, T();
|
|
280
|
+
}), [T, O]);
|
|
281
|
+
i(() => {
|
|
282
|
+
x.current = D(E, k);
|
|
283
|
+
let e;
|
|
284
|
+
if (typeof ResizeObserver < "u") {
|
|
285
|
+
h.current && (S.current.set(h.current, d(h.current, O)), e = y(h.current, () => {
|
|
286
|
+
k(), T();
|
|
287
|
+
}));
|
|
288
|
+
for (let e of g.current) S.current.set(e.el, d(e.el, O)), S.current.set(e.content, d(e.content, O));
|
|
289
|
+
}
|
|
290
|
+
let t = S.current;
|
|
291
|
+
return () => {
|
|
292
|
+
var n;
|
|
293
|
+
(n = x.current) == null || n.stop(), x.current = null, e == null || e();
|
|
294
|
+
for (let e of t.values()) e();
|
|
295
|
+
t.clear();
|
|
296
|
+
};
|
|
297
|
+
}, [
|
|
298
|
+
E,
|
|
299
|
+
k,
|
|
300
|
+
T,
|
|
301
|
+
O
|
|
302
|
+
]), i(() => {
|
|
303
|
+
w.current = {
|
|
304
|
+
offsetTop: r,
|
|
305
|
+
constraint: f,
|
|
306
|
+
onStickyItemsHeightChange: l
|
|
307
|
+
};
|
|
308
|
+
for (let e of g.current) e.invalidate();
|
|
309
|
+
T();
|
|
310
|
+
});
|
|
311
|
+
let j = c === void 0 ? 200 : Math.max(Number(c) || 0, 20), M = a(() => ({
|
|
312
|
+
register: A,
|
|
313
|
+
scheduleUpdate: T,
|
|
314
|
+
baseZIndex: j,
|
|
315
|
+
mode: u
|
|
316
|
+
}), [
|
|
317
|
+
A,
|
|
318
|
+
T,
|
|
319
|
+
j,
|
|
320
|
+
u
|
|
321
|
+
]);
|
|
322
|
+
return /* @__PURE__ */ e.createElement(s.Provider, { value: M }, /* @__PURE__ */ e.createElement("div", {
|
|
323
|
+
...m,
|
|
324
|
+
ref: h,
|
|
325
|
+
className: ["oe-sticky-container", p].filter(Boolean).join(" ")
|
|
326
|
+
}, t));
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
export { j as StickyContainer, O as StickyItem };
|
package/dist/react-sticky.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
"use client";(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react")):typeof define==`function`&&define.amd?define([`exports`,`react`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ReactSticky={},e.React))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule||!s.call(e,`default`)?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t,1);var l=(0,t.createContext)(null);function u(){return(0,t.useContext)(l)}var d=new Map,f=null;function p(e,t){if(typeof ResizeObserver>`u`)return()=>{};f!=null||(f=new ResizeObserver(e=>{let t=new Map;for(let r of e){var n;for(let e of(n=d.get(r.target))==null?[]:n){let n=t.get(e);n?n.push(r):t.set(e,[r])}}for(let[e,n]of t)e(n)}));let n=d.get(e);return n||(n=new Set,d.set(e,n),f.observe(e)),n.add(t),()=>{n.delete(t),n.size||(d.delete(e),f==null||f.unobserve(e)),d.size||(f==null||f.disconnect(),f=null)}}var m=new Map,h=new Map,g=null,_=!1;function v(){for(let e of m.values())e()}function y(){_||(_=!0,Promise.resolve().then(()=>{_=!1,m.size&&b()}))}function b(){let e=new Set,t=new Set;for(let n of m.keys())for(let r=n;r!=null&&r.parentElement;r=r.parentElement){let n=r.parentElement;e.add(n);for(let t=r.previousElementSibling;t&&!e.has(t);t=t.previousElementSibling)e.add(t);if(t.has(n))break;t.add(n)}let n=[...h.keys()].filter(t=>!e.has(t));n.length&&(g==null||g.disconnect());for(let e of n)h.get(e)(),h.delete(e);for(let t of e){let e=!h.has(t);e&&h.set(t,p(t,v)),(e||n.length)&&(g==null||g.observe(t,{childList:!0,attributes:!0,attributeFilter:[`class`,`style`]}))}}function x(e,t){return m.set(e,t),!g&&typeof MutationObserver<`u`&&(g=new MutationObserver(e=>{e.some(e=>e.type===`childList`)&&y(),v()})),y(),()=>{if(m.delete(e),m.size)y();else{g==null||g.disconnect(),g=null;for(let e of h.values())e();h.clear()}}}var S=new Set,C=new Set,w=null;function T(e){S.has(e)&&(C.add(e),w!=null||(w=requestAnimationFrame(O)))}function E(){for(let e of S)T(e)}function D(){for(let e of S)e.resize(),T(e)}function O(){w=null;let e=[...C];C.clear();let t=[],n=[],r=[];for(let n of e)if(S.has(n))try{let e=n.read();e&&t.push({client:n,write:e})}catch(e){r.push(e)}for(let{client:e,write:i}of t)if(S.has(e))try{let t=i();t&&n.push({client:e,notify:t})}catch(e){r.push(e)}for(let{client:e,notify:t}of n)if(S.has(e))try{t()}catch(e){r.push(e)}if(r.length)throw r[0]}function k(e,t){let n={read:e,resize:t};return S.size||(window.addEventListener(`scroll`,E,{passive:!0,capture:!0}),window.addEventListener(`resize`,D,{passive:!0})),S.add(n),T(n),{schedule:()=>T(n),stop:()=>{S.delete(n),C.delete(n),!C.size&&w!==null&&(cancelAnimationFrame(w),w=null),S.size||(window.removeEventListener(`scroll`,E,!0),window.removeEventListener(`resize`,D))}}}function A({mode:e,children:n,className:r,...i}){var a;let o=u(),s=(0,t.useRef)(null),c=(0,t.useRef)(null),l=o==null?void 0:o.register,d=e==null?o==null?void 0:o.mode:e,f=o==null?void 0:o.baseZIndex,p=(a=i.style)==null?void 0:a.height,m=(0,t.useRef)(null),h=o==null?void 0:o.scheduleUpdate;return(0,t.useEffect)(()=>{let e=s.current,t=c.current;if(!e||!t)return;let n=typeof p==`number`?`${p}px`:p==null?``:p;if(j(e,`height`,n),!l||!d||d===`none`)return;let r=p===void 0||[`auto`,`initial`,`unset`,`revert`,`revert-layer`,`fit-content`,`min-content`,`max-content`].includes(String(p)),i=!1,a=null,o=()=>{if(i){i=!1,a=null,t.classList.remove(`is-sticky`),e.style.height=n;for(let e of[`top`,`left`,`width`,`z-index`])t.style.removeProperty(e)}},u=null,h={el:e,content:t,mode:d,apply:n=>{if(n===null){o();return}let{top:s,wrapperHeight:c,width:l,left:u,index:p}=n;a&&a.top===s&&a.wrapperHeight===c&&a.width===l&&a.left===u&&a.index===p||(a=n,r&&j(e,`height`,`${c}px`),j(t,`top`,`${s}px`),j(t,`left`,`${u}px`),j(t,`width`,`${l}px`),j(t,`zIndex`,`${(f==null?200:f)+(d===`replace`?-p:p)}`),i||(i=!0,t.classList.add(`is-sticky`)))},measure:n=>{u!=null||(u=M(e,t));let r=t.getBoundingClientRect().height;return{height:r,wrapperHeight:r+u.heightInset,width:Math.max(0,n.width-u.widthInset),left:n.left+u.leftInset}},invalidate:()=>{u=null,a=null}};m.current=h;let g=l(h);return()=>{m.current=null,g(),o()}},[l,d,f,p]),(0,t.useEffect)(()=>{var e;(e=m.current)==null||e.invalidate(),h==null||h()}),t.default.createElement(`div`,{...i,className:[`oe-sticky-item`,r].filter(Boolean).join(` `),ref:s},t.default.createElement(`div`,{className:`oe-sticky-content`,ref:c},n))}function j(e,t,n){e.style[t]!==n&&(e.style[t]=n)}function M(e,t){let n=getComputedStyle(e),r=getComputedStyle(t),i=e=>Number.parseFloat(e)||0,a=e=>i(e.paddingLeft)+i(e.paddingRight)+i(e.borderLeftWidth)+i(e.borderRightWidth);return{heightInset:n.boxSizing===`border-box`?i(n.paddingTop)+i(n.paddingBottom)+i(n.borderTopWidth)+i(n.borderBottomWidth):0,widthInset:a(n)+(r.boxSizing===`border-box`?0:a(r)),leftInset:i(n.paddingLeft)+i(n.borderLeftWidth)}}function N({children:e,offsetTop:n=0,baseZIndex:r,onStickyItemsHeightChange:i,defaultMode:a=`replace`,constraint:o,className:s,...c}){let u=(0,t.useRef)(null),d=(0,t.useRef)(new Set),f=(0,t.useRef)(null),m=(0,t.useRef)(new Map),h=(0,t.useRef)(!1),g=(0,t.useRef)(null),_=(0,t.useRef)(new Map),v=(0,t.useRef)(0),y=(0,t.useRef)({offsetTop:n,constraint:o,onStickyItemsHeightChange:i}),b=(0,t.useCallback)(()=>{var e;return(e=g.current)==null?void 0:e.schedule()},[]),S=(0,t.useCallback)(()=>{var e;let t=u.current;if(!t)return;let n=y.current,r=t.getBoundingClientRect();if(!(n.constraint===`none`||r.top<=n.offsetTop&&r.bottom>=n.offsetTop))return()=>{if(h.current){t.classList.remove(`can-sticky`);for(let e of d.current)e.apply(null);h.current=!1}if(v.current!==0)return v.current=0,()=>{var e;return(e=n.onStickyItemsHeightChange)==null?void 0:e.call(n,0)}};let i=((e=f.current)==null?f.current=[...d.current]:e).map(e=>({item:e,rect:e.el.getBoundingClientRect()})).sort((e,t)=>{let n=e.rect.top-t.rect.top;return n===0?e.item.el.compareDocumentPosition(t.item.el)&Node.DOCUMENT_POSITION_FOLLOWING?-1:1:n}),a=n.offsetTop,o=0,s=i.map(({item:e,rect:t},n)=>{var r;if(t.top>a)return null;let s=(r=i[n+1])==null?void 0:r.rect.top;if(e.mode===`replace`&&s!==void 0&&s<a)return null;let c=e.measure(t),l=e.mode===`replace`&&s!==void 0?Math.min(a,s-c.height):a;return o+=c.height,e.mode===`stack`&&(a+=c.height),{...c,top:l,index:n}}),c=n.constraint===`none`?0:Math.min(0,r.bottom-n.offsetTop-o);return()=>{if(h.current=s.some(e=>e!==null),t.classList.toggle(`can-sticky`,h.current),i.forEach(({item:e},t)=>{let n=s[t];n&&(n.top+=c),e.apply(n)}),v.current!==o)return v.current=o,()=>{var e;return(e=n.onStickyItemsHeightChange)==null?void 0:e.call(n,o)}}},[]),C=(0,t.useCallback)(e=>{var t;for(let n of e)(t=m.current.get(n.target))==null||t.invalidate();b()},[b]),w=(0,t.useCallback)(()=>{for(let e of d.current)e.invalidate()},[]),T=(0,t.useCallback)(e=>(d.current.add(e),f.current=null,m.current.set(e.el,e),m.current.set(e.content,e),g.current&&(_.current.set(e.el,p(e.el,C)),_.current.set(e.content,p(e.content,C))),b(),()=>{var t,n;(t=_.current.get(e.el))==null||t(),(n=_.current.get(e.content))==null||n(),_.current.delete(e.el),_.current.delete(e.content),m.current.delete(e.el),m.current.delete(e.content),d.current.delete(e),f.current=null,b()}),[b,C]);(0,t.useEffect)(()=>{g.current=k(S,w);let e;if(typeof ResizeObserver<`u`){u.current&&(_.current.set(u.current,p(u.current,C)),e=x(u.current,()=>{w(),b()}));for(let e of d.current)_.current.set(e.el,p(e.el,C)),_.current.set(e.content,p(e.content,C))}let t=_.current;return()=>{var n;(n=g.current)==null||n.stop(),g.current=null,e==null||e();for(let e of t.values())e();t.clear()}},[S,w,b,C]),(0,t.useEffect)(()=>{y.current={offsetTop:n,constraint:o,onStickyItemsHeightChange:i};for(let e of d.current)e.invalidate();b()});let E=r===void 0?200:Math.max(Number(r)||0,20),D=(0,t.useMemo)(()=>({register:T,scheduleUpdate:b,baseZIndex:E,mode:a}),[T,b,E,a]);return t.default.createElement(l.Provider,{value:D},t.default.createElement(`div`,{...c,ref:u,className:[`oe-sticky-container`,s].filter(Boolean).join(` `)},e))}e.StickyContainer=N,e.StickyItem=A});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type Write = () => (() => void) | void;
|
|
2
|
+
type Client = {
|
|
3
|
+
read: () => Write | undefined;
|
|
4
|
+
resize: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function subscribeUpdates(read: Client['read'], invalidate: () => void): {
|
|
7
|
+
schedule: () => void;
|
|
8
|
+
stop: () => void;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type Write = () => (() => void) | void;
|
|
2
|
+
type Client = {
|
|
3
|
+
read: () => Write | undefined;
|
|
4
|
+
resize: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function subscribeUpdates(read: Client['read'], invalidate: () => void): {
|
|
7
|
+
schedule: () => void;
|
|
8
|
+
stop: () => void;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type IStickyMode } from './context.cjs';
|
|
3
|
+
export interface IStickyItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
/** Sticky mode for this item. Defaults to the StickyContainer's mode. */
|
|
6
|
+
mode?: IStickyMode;
|
|
7
|
+
}
|
|
8
|
+
export declare function StickyItem({ mode, children, className, ...rest }: IStickyItemProps): React.ReactElement<any, any>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type IStickyMode } from './context.js';
|
|
3
|
+
export interface IStickyItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
/** Sticky mode for this item. Defaults to the StickyContainer's mode. */
|
|
6
|
+
mode?: IStickyMode;
|
|
7
|
+
}
|
|
8
|
+
export declare function StickyItem({ mode, children, className, ...rest }: IStickyItemProps): React.ReactElement<any, any>;
|
package/dist/style.css
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
.oe-sticky-container{position:relative}.oe-sticky-
|
|
1
|
+
.oe-sticky-container,.oe-sticky-item{position:relative}.oe-sticky-content.is-sticky{position:fixed}
|
|
2
|
+
/*$vite$:1*/
|
package/package.json
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-sticky-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "React components for stacked and replacing sticky section headers, with automatic height coordination and viewport offsets.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"react",
|
|
8
8
|
"sticky",
|
|
9
|
+
"sticky-header",
|
|
10
|
+
"sticky-headers",
|
|
11
|
+
"sticky-section",
|
|
12
|
+
"stacked-headers",
|
|
9
13
|
"react-sticky",
|
|
10
14
|
"sticky-container",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
15
|
+
"typescript",
|
|
16
|
+
"nextjs"
|
|
13
17
|
],
|
|
14
18
|
"repository": {
|
|
15
19
|
"type": "git",
|
|
@@ -18,30 +22,50 @@
|
|
|
18
22
|
"bugs": {
|
|
19
23
|
"url": "https://github.com/oe/react-sticky-kit/issues"
|
|
20
24
|
},
|
|
21
|
-
"homepage": "https://
|
|
25
|
+
"homepage": "https://app.evecalm.com/react-sticky-kit/",
|
|
22
26
|
"license": "MIT",
|
|
23
27
|
"scripts": {
|
|
24
28
|
"dev": "vite",
|
|
25
|
-
"build": "vite build",
|
|
29
|
+
"build": "vite build && tsc -p tsconfig.build.json && node scripts/build-types.mjs",
|
|
26
30
|
"preview": "vite preview",
|
|
27
31
|
"type-check": "tsc --noEmit",
|
|
28
|
-
"lint": "eslint src demo
|
|
32
|
+
"lint": "eslint src demo test scripts vite.config.ts vite.demo.config.ts vitest.config.ts playwright.config.ts",
|
|
29
33
|
"test": "vitest run",
|
|
30
34
|
"test:watch": "vitest",
|
|
31
35
|
"coverage": "vitest run --coverage",
|
|
32
|
-
"pub": "tsx ./scripts/publish.ts"
|
|
36
|
+
"pub": "tsx ./scripts/publish.ts",
|
|
37
|
+
"check:package": "node scripts/check-package.mjs",
|
|
38
|
+
"test:browser": "playwright test",
|
|
39
|
+
"prepublishOnly": "pnpm type-check && pnpm lint && pnpm test && pnpm build && pnpm check:package",
|
|
40
|
+
"check:compatibility": "node scripts/check-compatibility.mjs",
|
|
41
|
+
"build:demo": "vite build --config vite.demo.config.ts"
|
|
33
42
|
},
|
|
34
43
|
"files": [
|
|
35
44
|
"dist"
|
|
36
45
|
],
|
|
37
|
-
"main": "dist/react-sticky.
|
|
46
|
+
"main": "dist/react-sticky.cjs",
|
|
38
47
|
"module": "dist/react-sticky.es.js",
|
|
39
48
|
"types": "dist/index.d.ts",
|
|
40
49
|
"exports": {
|
|
41
50
|
".": {
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
"node": {
|
|
52
|
+
"import": {
|
|
53
|
+
"types": "./dist/index.d.cts",
|
|
54
|
+
"default": "./dist/react-sticky.cjs"
|
|
55
|
+
},
|
|
56
|
+
"require": {
|
|
57
|
+
"types": "./dist/index.d.cts",
|
|
58
|
+
"default": "./dist/react-sticky.cjs"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"import": {
|
|
62
|
+
"types": "./dist/index.d.ts",
|
|
63
|
+
"default": "./dist/react-sticky.es.js"
|
|
64
|
+
},
|
|
65
|
+
"require": {
|
|
66
|
+
"types": "./dist/index.d.cts",
|
|
67
|
+
"default": "./dist/react-sticky.cjs"
|
|
68
|
+
}
|
|
45
69
|
},
|
|
46
70
|
"./style": "./dist/style.css",
|
|
47
71
|
"./dist/style.css": "./dist/style.css"
|
|
@@ -52,33 +76,34 @@
|
|
|
52
76
|
},
|
|
53
77
|
"devDependencies": {
|
|
54
78
|
"@eslint/js": "^9.25.0",
|
|
55
|
-
"@
|
|
56
|
-
"@testing-library/
|
|
57
|
-
"@testing-library/
|
|
58
|
-
"@types/node": "^
|
|
59
|
-
"@types/react": "^19.
|
|
60
|
-
"@types/react-dom": "^19.
|
|
61
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
62
|
-
"@typescript-eslint/parser": "^8.
|
|
63
|
-
"@vitejs/plugin-react": "^
|
|
64
|
-
"@vitest/coverage-v8": "^
|
|
65
|
-
"eslint": "^9.
|
|
79
|
+
"@playwright/test": "^1.63.0",
|
|
80
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
81
|
+
"@testing-library/react": "^16.3.3",
|
|
82
|
+
"@types/node": "^24.19.1",
|
|
83
|
+
"@types/react": "^19.3.0",
|
|
84
|
+
"@types/react-dom": "^19.3.0",
|
|
85
|
+
"@typescript-eslint/eslint-plugin": "^8.71.0",
|
|
86
|
+
"@typescript-eslint/parser": "^8.71.0",
|
|
87
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
88
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
89
|
+
"eslint": "^9.39.4",
|
|
66
90
|
"eslint-config-prettier": "^10.1.5",
|
|
67
91
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
|
68
92
|
"eslint-plugin-react": "^7.33.2",
|
|
69
|
-
"eslint-plugin-react-hooks": "^
|
|
93
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
70
94
|
"globals": "^16.0.0",
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"react": "^19.
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
95
|
+
"jsdom": "^30.1.1",
|
|
96
|
+
"react": "^19.3.0",
|
|
97
|
+
"react-dom": "^19.3.0",
|
|
98
|
+
"sass": "^1.105.1",
|
|
99
|
+
"tsx": "^4.23.15",
|
|
100
|
+
"typescript": "^6.0.3",
|
|
101
|
+
"vite": "^8.3.2",
|
|
102
|
+
"vitest": "^5.0.3"
|
|
103
|
+
},
|
|
104
|
+
"packageManager": "pnpm@12.8.1",
|
|
105
|
+
"sideEffects": [
|
|
106
|
+
"**/*.css",
|
|
107
|
+
"**/*.scss"
|
|
108
|
+
]
|
|
84
109
|
}
|