@publikit/hooks 0.1.1 → 1.0.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/CHANGELOG.md +24 -0
- package/README.md +28 -5
- package/dist/index.cjs +113 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -74
- package/dist/index.d.ts +7 -74
- package/dist/index.js +111 -67
- package/dist/index.js.map +1 -1
- package/dist/use-infinite-scroll.cjs +53 -0
- package/dist/use-infinite-scroll.cjs.map +1 -0
- package/dist/use-infinite-scroll.d.cts +28 -0
- package/dist/use-infinite-scroll.d.ts +28 -0
- package/dist/use-infinite-scroll.js +51 -0
- package/dist/use-infinite-scroll.js.map +1 -0
- package/dist/use-live-timestamp.cjs +156 -0
- package/dist/use-live-timestamp.cjs.map +1 -0
- package/dist/use-live-timestamp.d.cts +6 -0
- package/dist/use-live-timestamp.d.ts +6 -0
- package/dist/use-live-timestamp.js +154 -0
- package/dist/use-live-timestamp.js.map +1 -0
- package/dist/use-media-query.cjs +24 -0
- package/dist/use-media-query.cjs.map +1 -0
- package/dist/use-media-query.d.cts +10 -0
- package/dist/use-media-query.d.ts +10 -0
- package/dist/use-media-query.js +22 -0
- package/dist/use-media-query.js.map +1 -0
- package/dist/use-mobile.cjs +37 -0
- package/dist/use-mobile.cjs.map +1 -0
- package/dist/use-mobile.d.cts +10 -0
- package/dist/use-mobile.d.ts +10 -0
- package/dist/use-mobile.js +34 -0
- package/dist/use-mobile.js.map +1 -0
- package/dist/use-pull-to-refresh.cjs +103 -0
- package/dist/use-pull-to-refresh.cjs.map +1 -0
- package/dist/use-pull-to-refresh.d.cts +22 -0
- package/dist/use-pull-to-refresh.d.ts +22 -0
- package/dist/use-pull-to-refresh.js +101 -0
- package/dist/use-pull-to-refresh.js.map +1 -0
- package/dist/use-scrollable-container.cjs +149 -0
- package/dist/use-scrollable-container.cjs.map +1 -0
- package/dist/use-scrollable-container.d.cts +38 -0
- package/dist/use-scrollable-container.d.ts +38 -0
- package/dist/use-scrollable-container.js +144 -0
- package/dist/use-scrollable-container.js.map +1 -0
- package/package.json +38 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial release of `@publikit/hooks` as a Publikit L0 React behaviour package.
|
|
8
|
+
- `useMediaQuery` for generic media-query subscriptions.
|
|
9
|
+
- `useIntersectionLoadMore` for headless infinite-scroll composition.
|
|
10
|
+
- `usePullToRefresh` for touch pull-to-refresh gestures.
|
|
11
|
+
- `useLiveTimestamp` for adaptive live relative timestamps.
|
|
12
|
+
- `useIsMobile` as a responsive breakpoint helper built on `useMediaQuery`.
|
|
13
|
+
- `useScrollableContainer` for scroll position persistence and scroll button state.
|
|
14
|
+
- Object options for `useScrollableContainer` (`axis`, `scrollAmount`,
|
|
15
|
+
`checkDelay`, and `storage` adapter).
|
|
16
|
+
- Subpath exports for individual hooks.
|
|
17
|
+
- `docs/PUBLISHING.md`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `useIsMobile` accepts a custom breakpoint while keeping the 768px default.
|
|
22
|
+
- `usePullToRefresh` accepts any `HTMLElement` as its scroll container.
|
|
23
|
+
- `useLiveTimestamp` attaches its visibility listener lazily and accepts an
|
|
24
|
+
`Element` or `null` for visibility tracking.
|
package/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# @publikit/hooks
|
|
2
2
|
|
|
3
|
-
Generic, reusable React hooks with no UI
|
|
3
|
+
Generic, reusable React behaviour hooks with no styled UI, Radix, Tailwind, or
|
|
4
|
+
shadcn dependencies.
|
|
4
5
|
|
|
5
6
|
- **Peer dep**: React 18/19 only — no `react-dom`, Radix, Tailwind, or shadcn
|
|
6
7
|
- **Tree-shakeable** — `sideEffects: false`
|
|
8
|
+
- **L0 foundation** — shared behaviour for headless L2 packages and apps
|
|
9
|
+
|
|
10
|
+
See the [package taxonomy](../docs/PACKAGE-TAXONOMY.md) for where L0 hooks fit
|
|
11
|
+
in Publikit.
|
|
7
12
|
|
|
8
13
|
## Installation
|
|
9
14
|
|
|
@@ -21,8 +26,9 @@ npm install react
|
|
|
21
26
|
|
|
22
27
|
| Hook | Description |
|
|
23
28
|
|------|-------------|
|
|
29
|
+
| `useMediaQuery` | Generic CSS media-query subscription |
|
|
24
30
|
| `useIsMobile` | Responsive breakpoint detection (768px) |
|
|
25
|
-
| `
|
|
31
|
+
| `useIntersectionLoadMore` | Headless IntersectionObserver-based infinite scroll |
|
|
26
32
|
| `usePullToRefresh` | Touch pull-to-refresh gesture |
|
|
27
33
|
| `useLiveTimestamp` | Adaptive live relative timestamps |
|
|
28
34
|
| `useScrollableContainer` | Scroll position persistence + scroll buttons |
|
|
@@ -32,14 +38,15 @@ npm install react
|
|
|
32
38
|
```tsx
|
|
33
39
|
import {
|
|
34
40
|
useIsMobile,
|
|
35
|
-
|
|
41
|
+
useIntersectionLoadMore,
|
|
36
42
|
usePullToRefresh,
|
|
37
43
|
useLiveTimestamp,
|
|
44
|
+
useScrollableContainer,
|
|
38
45
|
} from '@publikit/hooks';
|
|
39
46
|
|
|
40
47
|
function FeedList({ loadMore, hasMore, isLoading }) {
|
|
41
48
|
const isMobile = useIsMobile();
|
|
42
|
-
const {
|
|
49
|
+
const { sentinelRef } = useIntersectionLoadMore({
|
|
43
50
|
onLoadMore: loadMore,
|
|
44
51
|
hasNextPage: hasMore,
|
|
45
52
|
isFetchingNextPage: isLoading,
|
|
@@ -48,12 +55,28 @@ function FeedList({ loadMore, hasMore, isLoading }) {
|
|
|
48
55
|
return (
|
|
49
56
|
<div>
|
|
50
57
|
{items.map(renderItem)}
|
|
51
|
-
<
|
|
58
|
+
<div ref={sentinelRef} aria-hidden="true" />
|
|
52
59
|
</div>
|
|
53
60
|
);
|
|
54
61
|
}
|
|
55
62
|
```
|
|
56
63
|
|
|
64
|
+
## Headless Usage Notes
|
|
65
|
+
|
|
66
|
+
- `useScrollableContainer` accepts an options object with `scrollPositionKey`,
|
|
67
|
+
`axis`, `scrollAmount`, `checkDelay`, and `storage`.
|
|
68
|
+
- `useIntersectionLoadMore` returns `sentinelRef`; consumers own the sentinel
|
|
69
|
+
markup and styling.
|
|
70
|
+
- `usePullToRefresh` exposes a callback ref typed as `HTMLElement`, so it can be
|
|
71
|
+
attached to any scrollable element.
|
|
72
|
+
- `useIsMobile` is a convenience wrapper over `useMediaQuery` and accepts a
|
|
73
|
+
custom breakpoint.
|
|
74
|
+
|
|
75
|
+
## Publishing
|
|
76
|
+
|
|
77
|
+
Publish `@publikit/hooks` after `@publikit/utils` and before headless L2 packages.
|
|
78
|
+
See [docs/PUBLISHING.md](./docs/PUBLISHING.md).
|
|
79
|
+
|
|
57
80
|
## Related packages
|
|
58
81
|
|
|
59
82
|
- [`@publikit/utils`](../utils) — framework-agnostic utilities
|
package/dist/index.cjs
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
5
4
|
var utils = require('@publikit/utils');
|
|
6
5
|
|
|
7
|
-
// use-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (typeof window === "undefined") return false;
|
|
12
|
-
return window.matchMedia(MOBILE_MEDIA_QUERY).matches;
|
|
6
|
+
// use-media-query.ts
|
|
7
|
+
function getMatches(query, defaultValue) {
|
|
8
|
+
if (typeof window === "undefined") return defaultValue;
|
|
9
|
+
return window.matchMedia(query).matches;
|
|
13
10
|
}
|
|
14
|
-
function
|
|
15
|
-
const [
|
|
11
|
+
function useMediaQuery(query, { defaultValue = false } = {}) {
|
|
12
|
+
const [matches, setMatches] = react.useState(() => getMatches(query, defaultValue));
|
|
16
13
|
react.useEffect(() => {
|
|
17
|
-
const mql = window.matchMedia(
|
|
18
|
-
const onChange = () =>
|
|
14
|
+
const mql = window.matchMedia(query);
|
|
15
|
+
const onChange = () => setMatches(mql.matches);
|
|
16
|
+
setMatches(mql.matches);
|
|
19
17
|
mql.addEventListener("change", onChange);
|
|
20
18
|
return () => mql.removeEventListener("change", onChange);
|
|
21
|
-
}, []);
|
|
22
|
-
return
|
|
19
|
+
}, [query]);
|
|
20
|
+
return matches;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// use-mobile.ts
|
|
24
|
+
var MOBILE_BREAKPOINT = 768;
|
|
25
|
+
function getMobileMediaQuery(breakpoint) {
|
|
26
|
+
return `(max-width: ${breakpoint - 1}px)`;
|
|
23
27
|
}
|
|
24
|
-
function
|
|
28
|
+
function useIsMobile({
|
|
29
|
+
breakpoint = MOBILE_BREAKPOINT,
|
|
30
|
+
defaultValue = false
|
|
31
|
+
} = {}) {
|
|
32
|
+
return useMediaQuery(getMobileMediaQuery(breakpoint), { defaultValue });
|
|
33
|
+
}
|
|
34
|
+
function useIntersectionLoadMore({
|
|
25
35
|
onLoadMore,
|
|
26
36
|
hasNextPage,
|
|
27
37
|
isFetchingNextPage,
|
|
@@ -31,7 +41,7 @@ function useInfiniteScroll({
|
|
|
31
41
|
root
|
|
32
42
|
}) {
|
|
33
43
|
const observerRef = react.useRef(null);
|
|
34
|
-
const [
|
|
44
|
+
const [sentinelElement, setSentinelElement] = react.useState(null);
|
|
35
45
|
const handleIntersection = react.useCallback(
|
|
36
46
|
(entries) => {
|
|
37
47
|
const entry = entries[0];
|
|
@@ -52,30 +62,17 @@ function useInfiniteScroll({
|
|
|
52
62
|
rootMargin,
|
|
53
63
|
threshold
|
|
54
64
|
});
|
|
55
|
-
if (
|
|
56
|
-
observerRef.current.observe(
|
|
65
|
+
if (sentinelElement) {
|
|
66
|
+
observerRef.current.observe(sentinelElement);
|
|
57
67
|
}
|
|
58
68
|
return () => {
|
|
59
69
|
if (observerRef.current) {
|
|
60
70
|
observerRef.current.disconnect();
|
|
61
71
|
}
|
|
62
72
|
};
|
|
63
|
-
}, [
|
|
64
|
-
const Sentinel = react.useCallback(
|
|
65
|
-
({ className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
66
|
-
"div",
|
|
67
|
-
{
|
|
68
|
-
ref: setSentinelRef,
|
|
69
|
-
className,
|
|
70
|
-
"aria-hidden": "true",
|
|
71
|
-
style: { height: "1px" }
|
|
72
|
-
}
|
|
73
|
-
),
|
|
74
|
-
[]
|
|
75
|
-
);
|
|
73
|
+
}, [sentinelElement, handleIntersection, rootMargin, threshold, enabled, root]);
|
|
76
74
|
return {
|
|
77
|
-
|
|
78
|
-
sentinelRef: setSentinelRef
|
|
75
|
+
sentinelRef: setSentinelElement
|
|
79
76
|
};
|
|
80
77
|
}
|
|
81
78
|
function usePullToRefresh({
|
|
@@ -179,6 +176,7 @@ var rafId = null;
|
|
|
179
176
|
var intervalId = null;
|
|
180
177
|
var isTabVisible = true;
|
|
181
178
|
var pendingUpdate = false;
|
|
179
|
+
var visibilityListenerAttached = false;
|
|
182
180
|
function getUpdateInterval(date) {
|
|
183
181
|
const ageMs = Date.now() - date.getTime();
|
|
184
182
|
const ageMinutes = ageMs / 6e4;
|
|
@@ -261,7 +259,9 @@ function restartTimer() {
|
|
|
261
259
|
stopTimer();
|
|
262
260
|
ensureTimerRunning();
|
|
263
261
|
}
|
|
264
|
-
|
|
262
|
+
function ensureVisibilityListener() {
|
|
263
|
+
if (visibilityListenerAttached) return;
|
|
264
|
+
if (typeof document === "undefined") return;
|
|
265
265
|
document.addEventListener("visibilitychange", () => {
|
|
266
266
|
isTabVisible = !document.hidden;
|
|
267
267
|
if (isTabVisible && subscribers.size > 0) {
|
|
@@ -271,12 +271,15 @@ if (typeof window !== "undefined") {
|
|
|
271
271
|
stopTimer();
|
|
272
272
|
}
|
|
273
273
|
});
|
|
274
|
+
visibilityListenerAttached = true;
|
|
274
275
|
}
|
|
275
|
-
function useLiveTimestamp(date,
|
|
276
|
+
function useLiveTimestamp(date, element) {
|
|
276
277
|
const [, setTick] = react.useState(0);
|
|
277
278
|
const subscriberIdRef = react.useRef(/* @__PURE__ */ Symbol("live-timestamp-subscriber"));
|
|
278
279
|
react.useEffect(() => {
|
|
280
|
+
ensureVisibilityListener();
|
|
279
281
|
const subscriberId = subscriberIdRef.current;
|
|
282
|
+
const observedElement = element ?? null;
|
|
280
283
|
const callback = () => {
|
|
281
284
|
const isVisible = visibilityMap.get(subscriberId) ?? true;
|
|
282
285
|
if (isVisible) {
|
|
@@ -284,9 +287,9 @@ function useLiveTimestamp(date, elementRef) {
|
|
|
284
287
|
}
|
|
285
288
|
};
|
|
286
289
|
let observer = null;
|
|
287
|
-
if (
|
|
290
|
+
if (observedElement) {
|
|
288
291
|
observer = createObserver(subscriberId, callback);
|
|
289
|
-
observer.observe(
|
|
292
|
+
observer.observe(observedElement);
|
|
290
293
|
visibilityMap.set(subscriberId, false);
|
|
291
294
|
} else {
|
|
292
295
|
visibilityMap.set(subscriberId, true);
|
|
@@ -310,49 +313,82 @@ function useLiveTimestamp(date, elementRef) {
|
|
|
310
313
|
restartTimer();
|
|
311
314
|
}
|
|
312
315
|
};
|
|
313
|
-
}, [date.getTime(),
|
|
316
|
+
}, [date.getTime(), element]);
|
|
314
317
|
return globalNow;
|
|
315
318
|
}
|
|
316
319
|
var SCROLL_AMOUNT = 80;
|
|
317
320
|
var SCROLL_CHECK_DELAY = 100;
|
|
318
321
|
var DEFAULT_SCROLL_POSITION_KEY = "scrollable-container-position";
|
|
319
322
|
var STORAGE_DEBOUNCE_MS = 150;
|
|
320
|
-
function
|
|
323
|
+
function resolveStorage(storage) {
|
|
324
|
+
if (storage === false) return null;
|
|
325
|
+
if (storage) return storage;
|
|
326
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
327
|
+
return sessionStorage;
|
|
328
|
+
}
|
|
329
|
+
function getScrollPosition(element, axis) {
|
|
330
|
+
return axis === "x" ? element.scrollLeft : element.scrollTop;
|
|
331
|
+
}
|
|
332
|
+
function setScrollPosition(element, axis, position) {
|
|
333
|
+
if (axis === "x") {
|
|
334
|
+
element.scrollLeft = position;
|
|
335
|
+
} else {
|
|
336
|
+
element.scrollTop = position;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function getScrollSize(element, axis) {
|
|
340
|
+
return axis === "x" ? element.scrollWidth : element.scrollHeight;
|
|
341
|
+
}
|
|
342
|
+
function getClientSize(element, axis) {
|
|
343
|
+
return axis === "x" ? element.clientWidth : element.clientHeight;
|
|
344
|
+
}
|
|
345
|
+
function useScrollableContainer({
|
|
346
|
+
scrollPositionKey,
|
|
347
|
+
axis = "y",
|
|
348
|
+
scrollAmount = SCROLL_AMOUNT,
|
|
349
|
+
checkDelay = SCROLL_CHECK_DELAY,
|
|
350
|
+
storage
|
|
351
|
+
}) {
|
|
321
352
|
const containerRef = react.useRef(null);
|
|
322
353
|
const [containerElement, setContainerElement] = react.useState(null);
|
|
323
354
|
const isRestoringRef = react.useRef(false);
|
|
324
|
-
const [
|
|
325
|
-
const [
|
|
355
|
+
const [canScrollBackward, setCanScrollBackward] = react.useState(false);
|
|
356
|
+
const [canScrollForward, setCanScrollForward] = react.useState(false);
|
|
357
|
+
const storageAdapter = react.useMemo(() => resolveStorage(storage), [storage]);
|
|
326
358
|
const assignContainerRef = react.useCallback((node) => {
|
|
327
359
|
containerRef.current = node;
|
|
328
360
|
setContainerElement(node);
|
|
329
361
|
}, []);
|
|
330
362
|
const saveToStorage = react.useMemo(
|
|
331
363
|
() => utils.debounce((position) => {
|
|
364
|
+
if (!storageAdapter) return;
|
|
332
365
|
try {
|
|
333
|
-
|
|
366
|
+
storageAdapter.setItem(scrollPositionKey, String(position));
|
|
334
367
|
} catch {
|
|
335
368
|
}
|
|
336
369
|
}, STORAGE_DEBOUNCE_MS),
|
|
337
|
-
[scrollPositionKey]
|
|
370
|
+
[scrollPositionKey, storageAdapter]
|
|
338
371
|
);
|
|
339
372
|
const checkScrollability = react.useCallback(() => {
|
|
340
373
|
if (!containerRef.current || isRestoringRef.current) return;
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
374
|
+
const scrollPosition = getScrollPosition(containerRef.current, axis);
|
|
375
|
+
const scrollSize = getScrollSize(containerRef.current, axis);
|
|
376
|
+
const clientSize = getClientSize(containerRef.current, axis);
|
|
377
|
+
saveToStorage(scrollPosition);
|
|
378
|
+
setCanScrollBackward(scrollPosition > 0);
|
|
379
|
+
setCanScrollForward(scrollPosition < scrollSize - clientSize - 1);
|
|
380
|
+
}, [axis, saveToStorage]);
|
|
346
381
|
react.useLayoutEffect(() => {
|
|
347
382
|
if (!containerElement || isRestoringRef.current) return;
|
|
383
|
+
if (!storageAdapter) return;
|
|
348
384
|
try {
|
|
349
|
-
const savedPosition =
|
|
385
|
+
const savedPosition = storageAdapter.getItem(scrollPositionKey);
|
|
350
386
|
if (savedPosition !== null) {
|
|
351
387
|
const position = parseInt(savedPosition, 10);
|
|
352
|
-
const currentPosition = containerElement
|
|
388
|
+
const currentPosition = getScrollPosition(containerElement, axis);
|
|
353
389
|
if (position > 0 && !isNaN(position) && Math.abs(currentPosition - position) > 1) {
|
|
354
390
|
isRestoringRef.current = true;
|
|
355
|
-
containerElement
|
|
391
|
+
setScrollPosition(containerElement, axis, position);
|
|
356
392
|
requestAnimationFrame(() => {
|
|
357
393
|
isRestoringRef.current = false;
|
|
358
394
|
checkScrollability();
|
|
@@ -361,15 +397,18 @@ function useScrollableContainer(scrollPositionKey = DEFAULT_SCROLL_POSITION_KEY)
|
|
|
361
397
|
}
|
|
362
398
|
} catch {
|
|
363
399
|
}
|
|
364
|
-
}, [containerElement, scrollPositionKey, checkScrollability]);
|
|
400
|
+
}, [axis, containerElement, scrollPositionKey, checkScrollability, storageAdapter]);
|
|
365
401
|
const preserveScrollPosition = react.useCallback(() => {
|
|
366
|
-
if (containerRef.current) {
|
|
402
|
+
if (containerRef.current && storageAdapter) {
|
|
367
403
|
try {
|
|
368
|
-
|
|
404
|
+
storageAdapter.setItem(
|
|
405
|
+
scrollPositionKey,
|
|
406
|
+
String(getScrollPosition(containerRef.current, axis))
|
|
407
|
+
);
|
|
369
408
|
} catch {
|
|
370
409
|
}
|
|
371
410
|
}
|
|
372
|
-
}, [scrollPositionKey]);
|
|
411
|
+
}, [axis, scrollPositionKey, storageAdapter]);
|
|
373
412
|
react.useEffect(() => {
|
|
374
413
|
return () => {
|
|
375
414
|
saveToStorage.cancel();
|
|
@@ -377,7 +416,7 @@ function useScrollableContainer(scrollPositionKey = DEFAULT_SCROLL_POSITION_KEY)
|
|
|
377
416
|
}, [saveToStorage]);
|
|
378
417
|
react.useEffect(() => {
|
|
379
418
|
if (!containerElement) return;
|
|
380
|
-
const timeoutId = setTimeout(checkScrollability,
|
|
419
|
+
const timeoutId = setTimeout(checkScrollability, checkDelay);
|
|
381
420
|
const handleScroll = () => {
|
|
382
421
|
checkScrollability();
|
|
383
422
|
};
|
|
@@ -391,30 +430,37 @@ function useScrollableContainer(scrollPositionKey = DEFAULT_SCROLL_POSITION_KEY)
|
|
|
391
430
|
window.removeEventListener("resize", checkScrollability);
|
|
392
431
|
resizeObserver.disconnect();
|
|
393
432
|
};
|
|
394
|
-
}, [containerElement, checkScrollability]);
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}, []);
|
|
433
|
+
}, [checkDelay, containerElement, checkScrollability]);
|
|
434
|
+
const scrollBackward = react.useCallback(() => {
|
|
435
|
+
const delta = -scrollAmount;
|
|
436
|
+
containerRef.current?.scrollBy(
|
|
437
|
+
axis === "x" ? { left: delta, behavior: "smooth" } : { top: delta, behavior: "smooth" }
|
|
438
|
+
);
|
|
439
|
+
}, [axis, scrollAmount]);
|
|
440
|
+
const scrollForward = react.useCallback(() => {
|
|
441
|
+
containerRef.current?.scrollBy(
|
|
442
|
+
axis === "x" ? { left: scrollAmount, behavior: "smooth" } : { top: scrollAmount, behavior: "smooth" }
|
|
443
|
+
);
|
|
444
|
+
}, [axis, scrollAmount]);
|
|
401
445
|
return {
|
|
402
446
|
containerRef: assignContainerRef,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
447
|
+
canScrollBackward,
|
|
448
|
+
canScrollForward,
|
|
449
|
+
scrollBackward,
|
|
450
|
+
scrollForward,
|
|
407
451
|
checkScrollability,
|
|
408
452
|
preserveScrollPosition
|
|
409
453
|
};
|
|
410
454
|
}
|
|
411
455
|
|
|
412
456
|
exports.DEFAULT_SCROLL_POSITION_KEY = DEFAULT_SCROLL_POSITION_KEY;
|
|
457
|
+
exports.MOBILE_BREAKPOINT = MOBILE_BREAKPOINT;
|
|
413
458
|
exports.SCROLL_AMOUNT = SCROLL_AMOUNT;
|
|
414
459
|
exports.SCROLL_CHECK_DELAY = SCROLL_CHECK_DELAY;
|
|
415
|
-
exports.
|
|
460
|
+
exports.useIntersectionLoadMore = useIntersectionLoadMore;
|
|
416
461
|
exports.useIsMobile = useIsMobile;
|
|
417
462
|
exports.useLiveTimestamp = useLiveTimestamp;
|
|
463
|
+
exports.useMediaQuery = useMediaQuery;
|
|
418
464
|
exports.usePullToRefresh = usePullToRefresh;
|
|
419
465
|
exports.useScrollableContainer = useScrollableContainer;
|
|
420
466
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../use-mobile.tsx","../use-infinite-scroll.tsx","../use-pull-to-refresh.ts","../use-live-timestamp.ts","../use-scrollable-container.ts"],"names":["useState","useEffect","useRef","useCallback","jsx","useMemo","debounce","useLayoutEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useEffect, useState } from 'react';\r\n\r\nconst MOBILE_BREAKPOINT = 768;\r\nconst MOBILE_MEDIA_QUERY = `(max-width: ${MOBILE_BREAKPOINT - 1}px)` as const;\r\n\r\nfunction getIsMobile(): boolean {\r\n if (typeof window === 'undefined') return false;\r\n return window.matchMedia(MOBILE_MEDIA_QUERY).matches;\r\n}\r\n\r\nexport function useIsMobile(): boolean {\r\n const [isMobile, setIsMobile] = useState<boolean>(getIsMobile);\r\n\r\n useEffect(() => {\r\n const mql = window.matchMedia(MOBILE_MEDIA_QUERY);\r\n const onChange = () => setIsMobile(mql.matches);\r\n mql.addEventListener('change', onChange);\r\n return () => mql.removeEventListener('change', onChange);\r\n }, []);\r\n\r\n return isMobile;\r\n}\r\n","import { useEffect, useRef, useCallback, useState } from 'react';\r\n\r\nexport interface UseInfiniteScrollOptions {\r\n /** Callback when intersection occurs */\r\n onLoadMore: () => void;\r\n /** Whether more data can be loaded */\r\n hasNextPage: boolean;\r\n /** Whether currently fetching */\r\n isFetchingNextPage: boolean;\r\n /** Root margin for intersection observer */\r\n rootMargin?: string;\r\n /** Threshold for intersection */\r\n threshold?: number;\r\n /** Enable/disable the hook */\r\n enabled?: boolean;\r\n /** Custom root element for IntersectionObserver (e.g., ScrollArea viewport) */\r\n root?: HTMLElement | null;\r\n}\r\n\r\n/**\r\n * Hook for implementing infinite scroll using IntersectionObserver.\r\n */\r\nexport function useInfiniteScroll({\r\n onLoadMore,\r\n hasNextPage,\r\n isFetchingNextPage,\r\n rootMargin = '200px',\r\n threshold = 0,\r\n enabled = true,\r\n root,\r\n}: UseInfiniteScrollOptions) {\r\n const observerRef = useRef<IntersectionObserver | null>(null);\r\n const [sentinelRef, setSentinelRef] = useState<HTMLDivElement | null>(null);\r\n\r\n const handleIntersection = useCallback(\r\n (entries: IntersectionObserverEntry[]) => {\r\n const entry = entries[0];\r\n if (!entry?.isIntersecting) return;\r\n if (hasNextPage && !isFetchingNextPage && enabled) {\r\n onLoadMore();\r\n }\r\n },\r\n [onLoadMore, hasNextPage, isFetchingNextPage, enabled]\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) return;\r\n\r\n if (observerRef.current) {\r\n observerRef.current.disconnect();\r\n }\r\n\r\n observerRef.current = new IntersectionObserver(handleIntersection, {\r\n root: root || null,\r\n rootMargin,\r\n threshold,\r\n });\r\n\r\n if (sentinelRef) {\r\n observerRef.current.observe(sentinelRef);\r\n }\r\n\r\n return () => {\r\n if (observerRef.current) {\r\n observerRef.current.disconnect();\r\n }\r\n };\r\n }, [sentinelRef, handleIntersection, rootMargin, threshold, enabled, root]);\r\n\r\n const Sentinel = useCallback(\r\n ({ className }: { className?: string }) => (\r\n <div\r\n ref={setSentinelRef}\r\n className={className}\r\n aria-hidden=\"true\"\r\n style={{ height: '1px' }}\r\n />\r\n ),\r\n []\r\n );\r\n\r\n return {\r\n Sentinel,\r\n sentinelRef: setSentinelRef,\r\n };\r\n}\r\n","import { useState, useEffect, useRef, useCallback, type Ref } from 'react';\r\n\r\nexport interface UsePullToRefreshOptions {\r\n onRefresh: () => Promise<void>;\r\n threshold?: number;\r\n resistance?: number;\r\n disabled?: boolean;\r\n}\r\n\r\nexport interface UsePullToRefreshReturn {\r\n pullDistance: number;\r\n isRefreshing: boolean;\r\n isPulling: boolean;\r\n /** Callback ref — attach to the scrollable container element */\r\n containerRef: Ref<HTMLDivElement | null>;\r\n pullProgress: number;\r\n}\r\n\r\n/**\r\n * Hook to add pull-to-refresh gesture for mobile devices.\r\n */\r\nexport function usePullToRefresh({\r\n onRefresh,\r\n threshold = 80,\r\n resistance = 2.5,\r\n disabled = false,\r\n}: UsePullToRefreshOptions): UsePullToRefreshReturn {\r\n const [pullDistance, setPullDistance] = useState(0);\r\n const [isRefreshing, setIsRefreshing] = useState(false);\r\n const [isPulling, setIsPulling] = useState(false);\r\n const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);\r\n\r\n const containerRef = useRef<HTMLDivElement | null>(null);\r\n const startYRef = useRef<number | null>(null);\r\n const currentYRef = useRef<number | null>(null);\r\n const pullDistanceRef = useRef(0);\r\n\r\n pullDistanceRef.current = pullDistance;\r\n\r\n const assignContainerRef = useCallback((node: HTMLDivElement | null) => {\r\n containerRef.current = node;\r\n setContainerElement(node);\r\n }, []);\r\n\r\n const handleTouchStart = useCallback(\r\n (e: TouchEvent) => {\r\n if (disabled || isRefreshing) return;\r\n\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n if (container.scrollTop <= 0) {\r\n startYRef.current = e.touches[0].clientY;\r\n setIsPulling(true);\r\n }\r\n },\r\n [disabled, isRefreshing]\r\n );\r\n\r\n const handleTouchMove = useCallback(\r\n (e: TouchEvent) => {\r\n if (disabled || isRefreshing || startYRef.current === null) return;\r\n\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n if (container.scrollTop > 0) {\r\n startYRef.current = null;\r\n setPullDistance(0);\r\n setIsPulling(false);\r\n return;\r\n }\r\n\r\n currentYRef.current = e.touches[0].clientY;\r\n const diff = currentYRef.current - startYRef.current;\r\n\r\n if (diff > 0) {\r\n const distance = Math.min(diff / resistance, threshold * 1.5);\r\n setPullDistance(distance);\r\n\r\n if (distance > 5) {\r\n e.preventDefault();\r\n }\r\n }\r\n },\r\n [disabled, isRefreshing, resistance, threshold]\r\n );\r\n\r\n const handleTouchEnd = useCallback(async () => {\r\n if (disabled || isRefreshing) return;\r\n\r\n startYRef.current = null;\r\n currentYRef.current = null;\r\n setIsPulling(false);\r\n\r\n const distance = pullDistanceRef.current;\r\n\r\n if (distance >= threshold) {\r\n setIsRefreshing(true);\r\n setPullDistance(threshold * 0.5);\r\n\r\n try {\r\n await onRefresh();\r\n } finally {\r\n setIsRefreshing(false);\r\n setPullDistance(0);\r\n }\r\n } else {\r\n setPullDistance(0);\r\n }\r\n }, [disabled, isRefreshing, threshold, onRefresh]);\r\n\r\n useEffect(() => {\r\n const container = containerElement;\r\n if (!container) return;\r\n\r\n container.addEventListener('touchstart', handleTouchStart, { passive: true });\r\n container.addEventListener('touchmove', handleTouchMove, { passive: false });\r\n container.addEventListener('touchend', handleTouchEnd, { passive: true });\r\n\r\n return () => {\r\n container.removeEventListener('touchstart', handleTouchStart);\r\n container.removeEventListener('touchmove', handleTouchMove);\r\n container.removeEventListener('touchend', handleTouchEnd);\r\n };\r\n }, [containerElement, handleTouchStart, handleTouchMove, handleTouchEnd]);\r\n\r\n const pullProgress = Math.min(pullDistance / threshold, 1);\r\n\r\n return {\r\n pullDistance,\r\n isRefreshing,\r\n isPulling,\r\n containerRef: assignContainerRef,\r\n pullProgress,\r\n };\r\n}\r\n","import { useState, useEffect, useRef, type RefObject } from 'react';\r\n\r\nlet globalNow = new Date();\r\n\r\ntype SubscriberId = symbol;\r\n\r\ntype Subscriber = {\r\n callback: () => void;\r\n date: Date;\r\n observer: IntersectionObserver | null;\r\n};\r\n\r\nconst subscribers = new Map<SubscriberId, Subscriber>();\r\nconst visibilityMap = new Map<SubscriberId, boolean>();\r\n\r\nlet rafId: number | null = null;\r\nlet intervalId: ReturnType<typeof setTimeout> | null = null;\r\nlet isTabVisible = true;\r\nlet pendingUpdate = false;\r\n\r\nfunction getUpdateInterval(date: Date): number {\r\n const ageMs = Date.now() - date.getTime();\r\n const ageMinutes = ageMs / 60000;\r\n\r\n if (ageMinutes < 1) return 10000;\r\n if (ageMinutes < 60) return 30000;\r\n if (ageMinutes < 1440) return 300000;\r\n return 3600000;\r\n}\r\n\r\nfunction getMinInterval(): number {\r\n if (subscribers.size === 0) return 30000;\r\n const intervals = Array.from(subscribers.values()).map((sub) => getUpdateInterval(sub.date));\r\n return Math.max(Math.min(...intervals), 10000);\r\n}\r\n\r\nconst createObserver = (\r\n subscriberId: SubscriberId,\r\n callback: () => void\r\n): IntersectionObserver => {\r\n return new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach((entry) => {\r\n visibilityMap.set(subscriberId, entry.isIntersecting);\r\n if (entry.isIntersecting) {\r\n callback();\r\n }\r\n });\r\n },\r\n {\r\n root: null,\r\n rootMargin: '50px',\r\n threshold: 0.01,\r\n }\r\n );\r\n};\r\n\r\nfunction updateVisibleSubscribers() {\r\n if (!isTabVisible || subscribers.size === 0) {\r\n pendingUpdate = false;\r\n return;\r\n }\r\n\r\n globalNow = new Date();\r\n\r\n if (!pendingUpdate) {\r\n pendingUpdate = true;\r\n rafId = requestAnimationFrame(() => {\r\n subscribers.forEach((subscriber, subscriberId) => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n subscriber.callback();\r\n }\r\n });\r\n pendingUpdate = false;\r\n });\r\n }\r\n}\r\n\r\nfunction stopTimer() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n if (rafId) {\r\n cancelAnimationFrame(rafId);\r\n rafId = null;\r\n }\r\n pendingUpdate = false;\r\n}\r\n\r\nfunction scheduleNextTick() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n\r\n if (subscribers.size === 0 || !isTabVisible) {\r\n return;\r\n }\r\n\r\n intervalId = setTimeout(() => {\r\n updateVisibleSubscribers();\r\n scheduleNextTick();\r\n }, getMinInterval());\r\n}\r\n\r\nfunction ensureTimerRunning() {\r\n if (!intervalId && subscribers.size > 0 && isTabVisible) {\r\n scheduleNextTick();\r\n }\r\n}\r\n\r\nfunction restartTimer() {\r\n stopTimer();\r\n ensureTimerRunning();\r\n}\r\n\r\nif (typeof window !== 'undefined') {\r\n document.addEventListener('visibilitychange', () => {\r\n isTabVisible = !document.hidden;\r\n if (isTabVisible && subscribers.size > 0) {\r\n updateVisibleSubscribers();\r\n ensureTimerRunning();\r\n } else if (!isTabVisible) {\r\n stopTimer();\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Live-updating timestamp hook with visibility-aware adaptive intervals.\r\n */\r\nexport function useLiveTimestamp(date: Date, elementRef?: RefObject<Element>): Date {\r\n const [, setTick] = useState(0);\r\n const subscriberIdRef = useRef<SubscriberId>(Symbol('live-timestamp-subscriber'));\r\n\r\n useEffect(() => {\r\n const subscriberId = subscriberIdRef.current;\r\n\r\n const callback = () => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n setTick((t) => t + 1);\r\n }\r\n };\r\n\r\n let observer: IntersectionObserver | null = null;\r\n if (elementRef?.current) {\r\n observer = createObserver(subscriberId, callback);\r\n observer.observe(elementRef.current);\r\n visibilityMap.set(subscriberId, false);\r\n } else {\r\n visibilityMap.set(subscriberId, true);\r\n }\r\n\r\n subscribers.set(subscriberId, {\r\n callback,\r\n date,\r\n observer,\r\n });\r\n\r\n ensureTimerRunning();\r\n\r\n return () => {\r\n const subscriber = subscribers.get(subscriberId);\r\n if (subscriber?.observer) {\r\n subscriber.observer.disconnect();\r\n }\r\n subscribers.delete(subscriberId);\r\n visibilityMap.delete(subscriberId);\r\n\r\n if (subscribers.size === 0) {\r\n stopTimer();\r\n } else {\r\n restartTimer();\r\n }\r\n };\r\n // Key on the timestamp value, not the Date identity, so callers passing a\r\n // freshly constructed Date each render don't trigger a re-subscribe.\r\n }, [date.getTime(), elementRef]);\r\n\r\n return globalNow;\r\n}\r\n","import { useRef, useEffect, useLayoutEffect, useState, useCallback, useMemo, type Ref } from 'react';\r\nimport { debounce } from '@publikit/utils';\r\n\r\nexport const SCROLL_AMOUNT = 80;\r\nexport const SCROLL_CHECK_DELAY = 100;\r\nexport const DEFAULT_SCROLL_POSITION_KEY = 'scrollable-container-position';\r\nconst STORAGE_DEBOUNCE_MS = 150;\r\n\r\n/**\r\n * Scrollable container with position persistence and scroll button state.\r\n *\r\n * @param scrollPositionKey - sessionStorage key for scroll position\r\n */\r\nexport function useScrollableContainer(\r\n scrollPositionKey: string = DEFAULT_SCROLL_POSITION_KEY\r\n) {\r\n const containerRef = useRef<HTMLElement | null>(null);\r\n const [containerElement, setContainerElement] = useState<HTMLElement | null>(null);\r\n const isRestoringRef = useRef<boolean>(false);\r\n const [canScrollUp, setCanScrollUp] = useState(false);\r\n const [canScrollDown, setCanScrollDown] = useState(false);\r\n\r\n const assignContainerRef = useCallback((node: HTMLElement | null) => {\r\n containerRef.current = node;\r\n setContainerElement(node);\r\n }, []);\r\n\r\n const saveToStorage = useMemo(\r\n () =>\r\n debounce((position: number) => {\r\n try {\r\n sessionStorage.setItem(scrollPositionKey, String(position));\r\n } catch {\r\n // Ignore storage errors\r\n }\r\n }, STORAGE_DEBOUNCE_MS),\r\n [scrollPositionKey]\r\n );\r\n\r\n const checkScrollability = useCallback(() => {\r\n if (!containerRef.current || isRestoringRef.current) return;\r\n const { scrollTop, scrollHeight, clientHeight } = containerRef.current;\r\n\r\n saveToStorage(scrollTop);\r\n\r\n setCanScrollUp(scrollTop > 0);\r\n setCanScrollDown(scrollTop < scrollHeight - clientHeight - 1);\r\n }, [saveToStorage]);\r\n\r\n useLayoutEffect(() => {\r\n if (!containerElement || isRestoringRef.current) return;\r\n\r\n try {\r\n const savedPosition = sessionStorage.getItem(scrollPositionKey);\r\n if (savedPosition !== null) {\r\n const position = parseInt(savedPosition, 10);\r\n const currentPosition = containerElement.scrollTop;\r\n\r\n if (position > 0 && !isNaN(position) && Math.abs(currentPosition - position) > 1) {\r\n isRestoringRef.current = true;\r\n containerElement.scrollTop = position;\r\n requestAnimationFrame(() => {\r\n isRestoringRef.current = false;\r\n checkScrollability();\r\n });\r\n }\r\n }\r\n } catch {\r\n // Ignore storage errors\r\n }\r\n }, [containerElement, scrollPositionKey, checkScrollability]);\r\n\r\n const preserveScrollPosition = useCallback(() => {\r\n if (containerRef.current) {\r\n try {\r\n sessionStorage.setItem(scrollPositionKey, String(containerRef.current.scrollTop));\r\n } catch {\r\n // Ignore storage errors\r\n }\r\n }\r\n }, [scrollPositionKey]);\r\n\r\n useEffect(() => {\r\n return () => {\r\n saveToStorage.cancel();\r\n };\r\n }, [saveToStorage]);\r\n\r\n useEffect(() => {\r\n if (!containerElement) return;\r\n\r\n const timeoutId = setTimeout(checkScrollability, SCROLL_CHECK_DELAY);\r\n\r\n const handleScroll = () => {\r\n checkScrollability();\r\n };\r\n\r\n containerElement.addEventListener('scroll', handleScroll, { passive: true });\r\n window.addEventListener('resize', checkScrollability);\r\n\r\n const resizeObserver = new ResizeObserver(checkScrollability);\r\n resizeObserver.observe(containerElement);\r\n\r\n return () => {\r\n clearTimeout(timeoutId);\r\n containerElement.removeEventListener('scroll', handleScroll);\r\n window.removeEventListener('resize', checkScrollability);\r\n resizeObserver.disconnect();\r\n };\r\n }, [containerElement, checkScrollability]);\r\n\r\n const scrollUp = useCallback(() => {\r\n containerRef.current?.scrollBy({ top: -SCROLL_AMOUNT, behavior: 'smooth' });\r\n }, []);\r\n\r\n const scrollDown = useCallback(() => {\r\n containerRef.current?.scrollBy({ top: SCROLL_AMOUNT, behavior: 'smooth' });\r\n }, []);\r\n\r\n return {\r\n containerRef: assignContainerRef as Ref<HTMLElement | null>,\r\n canScrollUp,\r\n canScrollDown,\r\n scrollUp,\r\n scrollDown,\r\n checkScrollability,\r\n preserveScrollPosition,\r\n };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../use-media-query.ts","../use-mobile.ts","../use-infinite-scroll.tsx","../use-pull-to-refresh.ts","../use-live-timestamp.ts","../use-scrollable-container.ts"],"names":["useState","useEffect","useRef","useCallback","useMemo","debounce","useLayoutEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useEffect, useState } from 'react';\r\n\r\nexport interface UseMediaQueryOptions {\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMatches(query: string, defaultValue: boolean): boolean {\r\n if (typeof window === 'undefined') return defaultValue;\r\n return window.matchMedia(query).matches;\r\n}\r\n\r\n/**\r\n * Subscribe to a CSS media query.\r\n */\r\nexport function useMediaQuery(\r\n query: string,\r\n { defaultValue = false }: UseMediaQueryOptions = {},\r\n): boolean {\r\n const [matches, setMatches] = useState<boolean>(() => getMatches(query, defaultValue));\r\n\r\n useEffect(() => {\r\n const mql = window.matchMedia(query);\r\n const onChange = () => setMatches(mql.matches);\r\n\r\n setMatches(mql.matches);\r\n mql.addEventListener('change', onChange);\r\n return () => mql.removeEventListener('change', onChange);\r\n }, [query]);\r\n\r\n return matches;\r\n}\r\n","import { useMediaQuery } from './use-media-query';\r\n\r\nexport const MOBILE_BREAKPOINT = 768;\r\n\r\nexport interface UseIsMobileOptions {\r\n /** Pixel breakpoint below which the viewport is considered mobile. */\r\n breakpoint?: number;\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMobileMediaQuery(breakpoint: number): string {\r\n return `(max-width: ${breakpoint - 1}px)`;\r\n}\r\n\r\nexport function useIsMobile({\r\n breakpoint = MOBILE_BREAKPOINT,\r\n defaultValue = false,\r\n}: UseIsMobileOptions = {}): boolean {\r\n return useMediaQuery(getMobileMediaQuery(breakpoint), { defaultValue });\r\n}\r\n","import { useEffect, useRef, useCallback, useState, type RefCallback } from 'react';\r\n\r\nexport interface UseIntersectionLoadMoreOptions {\r\n /** Callback when intersection occurs */\r\n onLoadMore: () => void;\r\n /** Whether more data can be loaded */\r\n hasNextPage: boolean;\r\n /** Whether currently fetching */\r\n isFetchingNextPage: boolean;\r\n /** Root margin for intersection observer */\r\n rootMargin?: string;\r\n /** Threshold for intersection */\r\n threshold?: number;\r\n /** Enable/disable the hook */\r\n enabled?: boolean;\r\n /** Custom root element for IntersectionObserver (e.g., ScrollArea viewport) */\r\n root?: HTMLElement | null;\r\n}\r\n\r\nexport interface UseIntersectionLoadMoreReturn {\r\n /** Callback ref for the sentinel element. */\r\n sentinelRef: RefCallback<HTMLElement | null>;\r\n}\r\n\r\n/**\r\n * Headless infinite-load behaviour using IntersectionObserver.\r\n */\r\nexport function useIntersectionLoadMore({\r\n onLoadMore,\r\n hasNextPage,\r\n isFetchingNextPage,\r\n rootMargin = '200px',\r\n threshold = 0,\r\n enabled = true,\r\n root,\r\n}: UseIntersectionLoadMoreOptions): UseIntersectionLoadMoreReturn {\r\n const observerRef = useRef<IntersectionObserver | null>(null);\r\n const [sentinelElement, setSentinelElement] = useState<HTMLElement | null>(null);\r\n\r\n const handleIntersection = useCallback(\r\n (entries: IntersectionObserverEntry[]) => {\r\n const entry = entries[0];\r\n if (!entry?.isIntersecting) return;\r\n if (hasNextPage && !isFetchingNextPage && enabled) {\r\n onLoadMore();\r\n }\r\n },\r\n [onLoadMore, hasNextPage, isFetchingNextPage, enabled]\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) return;\r\n\r\n if (observerRef.current) {\r\n observerRef.current.disconnect();\r\n }\r\n\r\n observerRef.current = new IntersectionObserver(handleIntersection, {\r\n root: root || null,\r\n rootMargin,\r\n threshold,\r\n });\r\n\r\n if (sentinelElement) {\r\n observerRef.current.observe(sentinelElement);\r\n }\r\n\r\n return () => {\r\n if (observerRef.current) {\r\n observerRef.current.disconnect();\r\n }\r\n };\r\n }, [sentinelElement, handleIntersection, rootMargin, threshold, enabled, root]);\r\n\r\n return {\r\n sentinelRef: setSentinelElement,\r\n };\r\n}\r\n","import { useState, useEffect, useRef, useCallback, type Ref } from 'react';\r\n\r\nexport interface UsePullToRefreshOptions {\r\n onRefresh: () => Promise<void>;\r\n threshold?: number;\r\n resistance?: number;\r\n disabled?: boolean;\r\n}\r\n\r\nexport interface UsePullToRefreshReturn {\r\n pullDistance: number;\r\n isRefreshing: boolean;\r\n isPulling: boolean;\r\n /** Callback ref — attach to the scrollable container element */\r\n containerRef: Ref<HTMLElement | null>;\r\n pullProgress: number;\r\n}\r\n\r\n/**\r\n * Hook to add pull-to-refresh gesture for mobile devices.\r\n */\r\nexport function usePullToRefresh({\r\n onRefresh,\r\n threshold = 80,\r\n resistance = 2.5,\r\n disabled = false,\r\n}: UsePullToRefreshOptions): UsePullToRefreshReturn {\r\n const [pullDistance, setPullDistance] = useState(0);\r\n const [isRefreshing, setIsRefreshing] = useState(false);\r\n const [isPulling, setIsPulling] = useState(false);\r\n const [containerElement, setContainerElement] = useState<HTMLElement | null>(null);\r\n\r\n const containerRef = useRef<HTMLElement | null>(null);\r\n const startYRef = useRef<number | null>(null);\r\n const currentYRef = useRef<number | null>(null);\r\n const pullDistanceRef = useRef(0);\r\n\r\n pullDistanceRef.current = pullDistance;\r\n\r\n const assignContainerRef = useCallback((node: HTMLElement | null) => {\r\n containerRef.current = node;\r\n setContainerElement(node);\r\n }, []);\r\n\r\n const handleTouchStart = useCallback(\r\n (e: TouchEvent) => {\r\n if (disabled || isRefreshing) return;\r\n\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n if (container.scrollTop <= 0) {\r\n startYRef.current = e.touches[0].clientY;\r\n setIsPulling(true);\r\n }\r\n },\r\n [disabled, isRefreshing]\r\n );\r\n\r\n const handleTouchMove = useCallback(\r\n (e: TouchEvent) => {\r\n if (disabled || isRefreshing || startYRef.current === null) return;\r\n\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n if (container.scrollTop > 0) {\r\n startYRef.current = null;\r\n setPullDistance(0);\r\n setIsPulling(false);\r\n return;\r\n }\r\n\r\n currentYRef.current = e.touches[0].clientY;\r\n const diff = currentYRef.current - startYRef.current;\r\n\r\n if (diff > 0) {\r\n const distance = Math.min(diff / resistance, threshold * 1.5);\r\n setPullDistance(distance);\r\n\r\n if (distance > 5) {\r\n e.preventDefault();\r\n }\r\n }\r\n },\r\n [disabled, isRefreshing, resistance, threshold]\r\n );\r\n\r\n const handleTouchEnd = useCallback(async () => {\r\n if (disabled || isRefreshing) return;\r\n\r\n startYRef.current = null;\r\n currentYRef.current = null;\r\n setIsPulling(false);\r\n\r\n const distance = pullDistanceRef.current;\r\n\r\n if (distance >= threshold) {\r\n setIsRefreshing(true);\r\n setPullDistance(threshold * 0.5);\r\n\r\n try {\r\n await onRefresh();\r\n } finally {\r\n setIsRefreshing(false);\r\n setPullDistance(0);\r\n }\r\n } else {\r\n setPullDistance(0);\r\n }\r\n }, [disabled, isRefreshing, threshold, onRefresh]);\r\n\r\n useEffect(() => {\r\n const container = containerElement;\r\n if (!container) return;\r\n\r\n container.addEventListener('touchstart', handleTouchStart, { passive: true });\r\n container.addEventListener('touchmove', handleTouchMove, { passive: false });\r\n container.addEventListener('touchend', handleTouchEnd, { passive: true });\r\n\r\n return () => {\r\n container.removeEventListener('touchstart', handleTouchStart);\r\n container.removeEventListener('touchmove', handleTouchMove);\r\n container.removeEventListener('touchend', handleTouchEnd);\r\n };\r\n }, [containerElement, handleTouchStart, handleTouchMove, handleTouchEnd]);\r\n\r\n const pullProgress = Math.min(pullDistance / threshold, 1);\r\n\r\n return {\r\n pullDistance,\r\n isRefreshing,\r\n isPulling,\r\n containerRef: assignContainerRef,\r\n pullProgress,\r\n };\r\n}\r\n","import { useState, useEffect, useRef } from 'react';\r\n\r\nlet globalNow = new Date();\r\n\r\ntype SubscriberId = symbol;\r\n\r\ntype Subscriber = {\r\n callback: () => void;\r\n date: Date;\r\n observer: IntersectionObserver | null;\r\n};\r\n\r\nconst subscribers = new Map<SubscriberId, Subscriber>();\r\nconst visibilityMap = new Map<SubscriberId, boolean>();\r\n\r\nlet rafId: number | null = null;\r\nlet intervalId: ReturnType<typeof setTimeout> | null = null;\r\nlet isTabVisible = true;\r\nlet pendingUpdate = false;\r\nlet visibilityListenerAttached = false;\r\n\r\nfunction getUpdateInterval(date: Date): number {\r\n const ageMs = Date.now() - date.getTime();\r\n const ageMinutes = ageMs / 60000;\r\n\r\n if (ageMinutes < 1) return 10000;\r\n if (ageMinutes < 60) return 30000;\r\n if (ageMinutes < 1440) return 300000;\r\n return 3600000;\r\n}\r\n\r\nfunction getMinInterval(): number {\r\n if (subscribers.size === 0) return 30000;\r\n const intervals = Array.from(subscribers.values()).map((sub) => getUpdateInterval(sub.date));\r\n return Math.max(Math.min(...intervals), 10000);\r\n}\r\n\r\nconst createObserver = (\r\n subscriberId: SubscriberId,\r\n callback: () => void\r\n): IntersectionObserver => {\r\n return new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach((entry) => {\r\n visibilityMap.set(subscriberId, entry.isIntersecting);\r\n if (entry.isIntersecting) {\r\n callback();\r\n }\r\n });\r\n },\r\n {\r\n root: null,\r\n rootMargin: '50px',\r\n threshold: 0.01,\r\n }\r\n );\r\n};\r\n\r\nfunction updateVisibleSubscribers() {\r\n if (!isTabVisible || subscribers.size === 0) {\r\n pendingUpdate = false;\r\n return;\r\n }\r\n\r\n globalNow = new Date();\r\n\r\n if (!pendingUpdate) {\r\n pendingUpdate = true;\r\n rafId = requestAnimationFrame(() => {\r\n subscribers.forEach((subscriber, subscriberId) => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n subscriber.callback();\r\n }\r\n });\r\n pendingUpdate = false;\r\n });\r\n }\r\n}\r\n\r\nfunction stopTimer() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n if (rafId) {\r\n cancelAnimationFrame(rafId);\r\n rafId = null;\r\n }\r\n pendingUpdate = false;\r\n}\r\n\r\nfunction scheduleNextTick() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n\r\n if (subscribers.size === 0 || !isTabVisible) {\r\n return;\r\n }\r\n\r\n intervalId = setTimeout(() => {\r\n updateVisibleSubscribers();\r\n scheduleNextTick();\r\n }, getMinInterval());\r\n}\r\n\r\nfunction ensureTimerRunning() {\r\n if (!intervalId && subscribers.size > 0 && isTabVisible) {\r\n scheduleNextTick();\r\n }\r\n}\r\n\r\nfunction restartTimer() {\r\n stopTimer();\r\n ensureTimerRunning();\r\n}\r\n\r\nfunction ensureVisibilityListener(): void {\r\n if (visibilityListenerAttached) return;\r\n if (typeof document === 'undefined') return;\r\n\r\n document.addEventListener('visibilitychange', () => {\r\n isTabVisible = !document.hidden;\r\n if (isTabVisible && subscribers.size > 0) {\r\n updateVisibleSubscribers();\r\n ensureTimerRunning();\r\n } else if (!isTabVisible) {\r\n stopTimer();\r\n }\r\n });\r\n visibilityListenerAttached = true;\r\n}\r\n\r\n/**\r\n * Live-updating timestamp hook with visibility-aware adaptive intervals.\r\n */\r\nexport function useLiveTimestamp(\r\n date: Date,\r\n element?: Element | null,\r\n): Date {\r\n const [, setTick] = useState(0);\r\n const subscriberIdRef = useRef<SubscriberId>(Symbol('live-timestamp-subscriber'));\r\n\r\n useEffect(() => {\r\n ensureVisibilityListener();\r\n const subscriberId = subscriberIdRef.current;\r\n const observedElement = element ?? null;\r\n\r\n const callback = () => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n setTick((t) => t + 1);\r\n }\r\n };\r\n\r\n let observer: IntersectionObserver | null = null;\r\n if (observedElement) {\r\n observer = createObserver(subscriberId, callback);\r\n observer.observe(observedElement);\r\n visibilityMap.set(subscriberId, false);\r\n } else {\r\n visibilityMap.set(subscriberId, true);\r\n }\r\n\r\n subscribers.set(subscriberId, {\r\n callback,\r\n date,\r\n observer,\r\n });\r\n\r\n ensureTimerRunning();\r\n\r\n return () => {\r\n const subscriber = subscribers.get(subscriberId);\r\n if (subscriber?.observer) {\r\n subscriber.observer.disconnect();\r\n }\r\n subscribers.delete(subscriberId);\r\n visibilityMap.delete(subscriberId);\r\n\r\n if (subscribers.size === 0) {\r\n stopTimer();\r\n } else {\r\n restartTimer();\r\n }\r\n };\r\n // Key on the timestamp value, not the Date identity, so callers passing a\r\n // freshly constructed Date each render don't trigger a re-subscribe.\r\n }, [date.getTime(), element]);\r\n\r\n return globalNow;\r\n}\r\n","import { useRef, useEffect, useLayoutEffect, useState, useCallback, useMemo, type Ref } from 'react';\r\nimport { debounce } from '@publikit/utils';\r\n\r\nexport const SCROLL_AMOUNT = 80;\r\nexport const SCROLL_CHECK_DELAY = 100;\r\nexport const DEFAULT_SCROLL_POSITION_KEY = 'scrollable-container-position';\r\nconst STORAGE_DEBOUNCE_MS = 150;\r\n\r\nexport type ScrollAxis = 'x' | 'y';\r\n\r\nexport interface ScrollStorageAdapter {\r\n getItem(key: string): string | null;\r\n setItem(key: string, value: string): void;\r\n}\r\n\r\nexport interface UseScrollableContainerOptions {\r\n /** Session/storage key used to persist scroll position. */\r\n scrollPositionKey: string;\r\n /** Axis to measure and scroll. */\r\n axis?: ScrollAxis;\r\n /** Distance used by scroll helpers. */\r\n scrollAmount?: number;\r\n /** Delay before initial scrollability check. */\r\n checkDelay?: number;\r\n /** Optional storage adapter. Pass `false` to disable persistence. */\r\n storage?: ScrollStorageAdapter | false;\r\n}\r\n\r\nexport interface UseScrollableContainerReturn {\r\n /** Callback ref — attach to the scrollable element. */\r\n containerRef: Ref<HTMLElement | null>;\r\n canScrollBackward: boolean;\r\n canScrollForward: boolean;\r\n scrollBackward: () => void;\r\n scrollForward: () => void;\r\n checkScrollability: () => void;\r\n preserveScrollPosition: () => void;\r\n}\r\n\r\nfunction resolveStorage(\r\n storage: ScrollStorageAdapter | false | undefined,\r\n): ScrollStorageAdapter | null {\r\n if (storage === false) return null;\r\n if (storage) return storage;\r\n if (typeof sessionStorage === 'undefined') return null;\r\n return sessionStorage;\r\n}\r\n\r\nfunction getScrollPosition(element: HTMLElement, axis: ScrollAxis): number {\r\n return axis === 'x' ? element.scrollLeft : element.scrollTop;\r\n}\r\n\r\nfunction setScrollPosition(element: HTMLElement, axis: ScrollAxis, position: number): void {\r\n if (axis === 'x') {\r\n element.scrollLeft = position;\r\n } else {\r\n element.scrollTop = position;\r\n }\r\n}\r\n\r\nfunction getScrollSize(element: HTMLElement, axis: ScrollAxis): number {\r\n return axis === 'x' ? element.scrollWidth : element.scrollHeight;\r\n}\r\n\r\nfunction getClientSize(element: HTMLElement, axis: ScrollAxis): number {\r\n return axis === 'x' ? element.clientWidth : element.clientHeight;\r\n}\r\n\r\n/**\r\n * Scrollable container with position persistence and scroll button state.\r\n */\r\nexport function useScrollableContainer(\r\n {\r\n scrollPositionKey,\r\n axis = 'y',\r\n scrollAmount = SCROLL_AMOUNT,\r\n checkDelay = SCROLL_CHECK_DELAY,\r\n storage,\r\n }: UseScrollableContainerOptions,\r\n): UseScrollableContainerReturn {\r\n const containerRef = useRef<HTMLElement | null>(null);\r\n const [containerElement, setContainerElement] = useState<HTMLElement | null>(null);\r\n const isRestoringRef = useRef<boolean>(false);\r\n const [canScrollBackward, setCanScrollBackward] = useState(false);\r\n const [canScrollForward, setCanScrollForward] = useState(false);\r\n const storageAdapter = useMemo(() => resolveStorage(storage), [storage]);\r\n\r\n const assignContainerRef = useCallback((node: HTMLElement | null) => {\r\n containerRef.current = node;\r\n setContainerElement(node);\r\n }, []);\r\n\r\n const saveToStorage = useMemo(\r\n () =>\r\n debounce((position: number) => {\r\n if (!storageAdapter) return;\r\n try {\r\n storageAdapter.setItem(scrollPositionKey, String(position));\r\n } catch {\r\n // Ignore storage errors\r\n }\r\n }, STORAGE_DEBOUNCE_MS),\r\n [scrollPositionKey, storageAdapter]\r\n );\r\n\r\n const checkScrollability = useCallback(() => {\r\n if (!containerRef.current || isRestoringRef.current) return;\r\n const scrollPosition = getScrollPosition(containerRef.current, axis);\r\n const scrollSize = getScrollSize(containerRef.current, axis);\r\n const clientSize = getClientSize(containerRef.current, axis);\r\n\r\n saveToStorage(scrollPosition);\r\n\r\n setCanScrollBackward(scrollPosition > 0);\r\n setCanScrollForward(scrollPosition < scrollSize - clientSize - 1);\r\n }, [axis, saveToStorage]);\r\n\r\n useLayoutEffect(() => {\r\n if (!containerElement || isRestoringRef.current) return;\r\n if (!storageAdapter) return;\r\n\r\n try {\r\n const savedPosition = storageAdapter.getItem(scrollPositionKey);\r\n if (savedPosition !== null) {\r\n const position = parseInt(savedPosition, 10);\r\n const currentPosition = getScrollPosition(containerElement, axis);\r\n\r\n if (position > 0 && !isNaN(position) && Math.abs(currentPosition - position) > 1) {\r\n isRestoringRef.current = true;\r\n setScrollPosition(containerElement, axis, position);\r\n requestAnimationFrame(() => {\r\n isRestoringRef.current = false;\r\n checkScrollability();\r\n });\r\n }\r\n }\r\n } catch {\r\n // Ignore storage errors\r\n }\r\n }, [axis, containerElement, scrollPositionKey, checkScrollability, storageAdapter]);\r\n\r\n const preserveScrollPosition = useCallback(() => {\r\n if (containerRef.current && storageAdapter) {\r\n try {\r\n storageAdapter.setItem(\r\n scrollPositionKey,\r\n String(getScrollPosition(containerRef.current, axis)),\r\n );\r\n } catch {\r\n // Ignore storage errors\r\n }\r\n }\r\n }, [axis, scrollPositionKey, storageAdapter]);\r\n\r\n useEffect(() => {\r\n return () => {\r\n saveToStorage.cancel();\r\n };\r\n }, [saveToStorage]);\r\n\r\n useEffect(() => {\r\n if (!containerElement) return;\r\n\r\n const timeoutId = setTimeout(checkScrollability, checkDelay);\r\n\r\n const handleScroll = () => {\r\n checkScrollability();\r\n };\r\n\r\n containerElement.addEventListener('scroll', handleScroll, { passive: true });\r\n window.addEventListener('resize', checkScrollability);\r\n\r\n const resizeObserver = new ResizeObserver(checkScrollability);\r\n resizeObserver.observe(containerElement);\r\n\r\n return () => {\r\n clearTimeout(timeoutId);\r\n containerElement.removeEventListener('scroll', handleScroll);\r\n window.removeEventListener('resize', checkScrollability);\r\n resizeObserver.disconnect();\r\n };\r\n }, [checkDelay, containerElement, checkScrollability]);\r\n\r\n const scrollBackward = useCallback(() => {\r\n const delta = -scrollAmount;\r\n containerRef.current?.scrollBy(\r\n axis === 'x' ? { left: delta, behavior: 'smooth' } : { top: delta, behavior: 'smooth' },\r\n );\r\n }, [axis, scrollAmount]);\r\n\r\n const scrollForward = useCallback(() => {\r\n containerRef.current?.scrollBy(\r\n axis === 'x'\r\n ? { left: scrollAmount, behavior: 'smooth' }\r\n : { top: scrollAmount, behavior: 'smooth' },\r\n );\r\n }, [axis, scrollAmount]);\r\n\r\n return {\r\n containerRef: assignContainerRef as Ref<HTMLElement | null>,\r\n canScrollBackward,\r\n canScrollForward,\r\n scrollBackward,\r\n scrollForward,\r\n checkScrollability,\r\n preserveScrollPosition,\r\n };\r\n}\r\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,74 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
onLoadMore: () => void;
|
|
9
|
-
/** Whether more data can be loaded */
|
|
10
|
-
hasNextPage: boolean;
|
|
11
|
-
/** Whether currently fetching */
|
|
12
|
-
isFetchingNextPage: boolean;
|
|
13
|
-
/** Root margin for intersection observer */
|
|
14
|
-
rootMargin?: string;
|
|
15
|
-
/** Threshold for intersection */
|
|
16
|
-
threshold?: number;
|
|
17
|
-
/** Enable/disable the hook */
|
|
18
|
-
enabled?: boolean;
|
|
19
|
-
/** Custom root element for IntersectionObserver (e.g., ScrollArea viewport) */
|
|
20
|
-
root?: HTMLElement | null;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Hook for implementing infinite scroll using IntersectionObserver.
|
|
24
|
-
*/
|
|
25
|
-
declare function useInfiniteScroll({ onLoadMore, hasNextPage, isFetchingNextPage, rootMargin, threshold, enabled, root, }: UseInfiniteScrollOptions): {
|
|
26
|
-
Sentinel: ({ className }: {
|
|
27
|
-
className?: string;
|
|
28
|
-
}) => react.JSX.Element;
|
|
29
|
-
sentinelRef: react.Dispatch<react.SetStateAction<HTMLDivElement | null>>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
interface UsePullToRefreshOptions {
|
|
33
|
-
onRefresh: () => Promise<void>;
|
|
34
|
-
threshold?: number;
|
|
35
|
-
resistance?: number;
|
|
36
|
-
disabled?: boolean;
|
|
37
|
-
}
|
|
38
|
-
interface UsePullToRefreshReturn {
|
|
39
|
-
pullDistance: number;
|
|
40
|
-
isRefreshing: boolean;
|
|
41
|
-
isPulling: boolean;
|
|
42
|
-
/** Callback ref — attach to the scrollable container element */
|
|
43
|
-
containerRef: Ref<HTMLDivElement | null>;
|
|
44
|
-
pullProgress: number;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Hook to add pull-to-refresh gesture for mobile devices.
|
|
48
|
-
*/
|
|
49
|
-
declare function usePullToRefresh({ onRefresh, threshold, resistance, disabled, }: UsePullToRefreshOptions): UsePullToRefreshReturn;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Live-updating timestamp hook with visibility-aware adaptive intervals.
|
|
53
|
-
*/
|
|
54
|
-
declare function useLiveTimestamp(date: Date, elementRef?: RefObject<Element>): Date;
|
|
55
|
-
|
|
56
|
-
declare const SCROLL_AMOUNT = 80;
|
|
57
|
-
declare const SCROLL_CHECK_DELAY = 100;
|
|
58
|
-
declare const DEFAULT_SCROLL_POSITION_KEY = "scrollable-container-position";
|
|
59
|
-
/**
|
|
60
|
-
* Scrollable container with position persistence and scroll button state.
|
|
61
|
-
*
|
|
62
|
-
* @param scrollPositionKey - sessionStorage key for scroll position
|
|
63
|
-
*/
|
|
64
|
-
declare function useScrollableContainer(scrollPositionKey?: string): {
|
|
65
|
-
containerRef: Ref<HTMLElement | null>;
|
|
66
|
-
canScrollUp: boolean;
|
|
67
|
-
canScrollDown: boolean;
|
|
68
|
-
scrollUp: () => void;
|
|
69
|
-
scrollDown: () => void;
|
|
70
|
-
checkScrollability: () => void;
|
|
71
|
-
preserveScrollPosition: () => void;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export { DEFAULT_SCROLL_POSITION_KEY, SCROLL_AMOUNT, SCROLL_CHECK_DELAY, type UseInfiniteScrollOptions, type UsePullToRefreshOptions, type UsePullToRefreshReturn, useInfiniteScroll, useIsMobile, useLiveTimestamp, usePullToRefresh, useScrollableContainer };
|
|
1
|
+
export { UseMediaQueryOptions, useMediaQuery } from './use-media-query.cjs';
|
|
2
|
+
export { MOBILE_BREAKPOINT, UseIsMobileOptions, useIsMobile } from './use-mobile.cjs';
|
|
3
|
+
export { UseIntersectionLoadMoreOptions, UseIntersectionLoadMoreReturn, useIntersectionLoadMore } from './use-infinite-scroll.cjs';
|
|
4
|
+
export { UsePullToRefreshOptions, UsePullToRefreshReturn, usePullToRefresh } from './use-pull-to-refresh.cjs';
|
|
5
|
+
export { useLiveTimestamp } from './use-live-timestamp.cjs';
|
|
6
|
+
export { DEFAULT_SCROLL_POSITION_KEY, SCROLL_AMOUNT, SCROLL_CHECK_DELAY, ScrollAxis, ScrollStorageAdapter, UseScrollableContainerOptions, UseScrollableContainerReturn, useScrollableContainer } from './use-scrollable-container.cjs';
|
|
7
|
+
import 'react';
|