@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +28 -5
  3. package/dist/index.cjs +113 -67
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +7 -74
  6. package/dist/index.d.ts +7 -74
  7. package/dist/index.js +111 -67
  8. package/dist/index.js.map +1 -1
  9. package/dist/use-infinite-scroll.cjs +53 -0
  10. package/dist/use-infinite-scroll.cjs.map +1 -0
  11. package/dist/use-infinite-scroll.d.cts +28 -0
  12. package/dist/use-infinite-scroll.d.ts +28 -0
  13. package/dist/use-infinite-scroll.js +51 -0
  14. package/dist/use-infinite-scroll.js.map +1 -0
  15. package/dist/use-live-timestamp.cjs +156 -0
  16. package/dist/use-live-timestamp.cjs.map +1 -0
  17. package/dist/use-live-timestamp.d.cts +6 -0
  18. package/dist/use-live-timestamp.d.ts +6 -0
  19. package/dist/use-live-timestamp.js +154 -0
  20. package/dist/use-live-timestamp.js.map +1 -0
  21. package/dist/use-media-query.cjs +24 -0
  22. package/dist/use-media-query.cjs.map +1 -0
  23. package/dist/use-media-query.d.cts +10 -0
  24. package/dist/use-media-query.d.ts +10 -0
  25. package/dist/use-media-query.js +22 -0
  26. package/dist/use-media-query.js.map +1 -0
  27. package/dist/use-mobile.cjs +37 -0
  28. package/dist/use-mobile.cjs.map +1 -0
  29. package/dist/use-mobile.d.cts +10 -0
  30. package/dist/use-mobile.d.ts +10 -0
  31. package/dist/use-mobile.js +34 -0
  32. package/dist/use-mobile.js.map +1 -0
  33. package/dist/use-pull-to-refresh.cjs +103 -0
  34. package/dist/use-pull-to-refresh.cjs.map +1 -0
  35. package/dist/use-pull-to-refresh.d.cts +22 -0
  36. package/dist/use-pull-to-refresh.d.ts +22 -0
  37. package/dist/use-pull-to-refresh.js +101 -0
  38. package/dist/use-pull-to-refresh.js.map +1 -0
  39. package/dist/use-scrollable-container.cjs +149 -0
  40. package/dist/use-scrollable-container.cjs.map +1 -0
  41. package/dist/use-scrollable-container.d.cts +38 -0
  42. package/dist/use-scrollable-container.d.ts +38 -0
  43. package/dist/use-scrollable-container.js +144 -0
  44. package/dist/use-scrollable-container.js.map +1 -0
  45. 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 component dependencies.
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
- | `useInfiniteScroll` | IntersectionObserver-based infinite scroll |
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
- useInfiniteScroll,
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 { Sentinel } = useInfiniteScroll({
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
- <Sentinel />
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-mobile.tsx
8
- var MOBILE_BREAKPOINT = 768;
9
- var MOBILE_MEDIA_QUERY = `(max-width: ${MOBILE_BREAKPOINT - 1}px)`;
10
- function getIsMobile() {
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 useIsMobile() {
15
- const [isMobile, setIsMobile] = react.useState(getIsMobile);
11
+ function useMediaQuery(query, { defaultValue = false } = {}) {
12
+ const [matches, setMatches] = react.useState(() => getMatches(query, defaultValue));
16
13
  react.useEffect(() => {
17
- const mql = window.matchMedia(MOBILE_MEDIA_QUERY);
18
- const onChange = () => setIsMobile(mql.matches);
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 isMobile;
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 useInfiniteScroll({
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 [sentinelRef, setSentinelRef] = react.useState(null);
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 (sentinelRef) {
56
- observerRef.current.observe(sentinelRef);
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
- }, [sentinelRef, handleIntersection, rootMargin, threshold, enabled, root]);
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
- Sentinel,
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
- if (typeof window !== "undefined") {
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, elementRef) {
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 (elementRef?.current) {
290
+ if (observedElement) {
288
291
  observer = createObserver(subscriberId, callback);
289
- observer.observe(elementRef.current);
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(), elementRef]);
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 useScrollableContainer(scrollPositionKey = DEFAULT_SCROLL_POSITION_KEY) {
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 [canScrollUp, setCanScrollUp] = react.useState(false);
325
- const [canScrollDown, setCanScrollDown] = react.useState(false);
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
- sessionStorage.setItem(scrollPositionKey, String(position));
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 { scrollTop, scrollHeight, clientHeight } = containerRef.current;
342
- saveToStorage(scrollTop);
343
- setCanScrollUp(scrollTop > 0);
344
- setCanScrollDown(scrollTop < scrollHeight - clientHeight - 1);
345
- }, [saveToStorage]);
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 = sessionStorage.getItem(scrollPositionKey);
385
+ const savedPosition = storageAdapter.getItem(scrollPositionKey);
350
386
  if (savedPosition !== null) {
351
387
  const position = parseInt(savedPosition, 10);
352
- const currentPosition = containerElement.scrollTop;
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.scrollTop = position;
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
- sessionStorage.setItem(scrollPositionKey, String(containerRef.current.scrollTop));
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, SCROLL_CHECK_DELAY);
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 scrollUp = react.useCallback(() => {
396
- containerRef.current?.scrollBy({ top: -SCROLL_AMOUNT, behavior: "smooth" });
397
- }, []);
398
- const scrollDown = react.useCallback(() => {
399
- containerRef.current?.scrollBy({ top: SCROLL_AMOUNT, behavior: "smooth" });
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
- canScrollUp,
404
- canScrollDown,
405
- scrollUp,
406
- scrollDown,
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.useInfiniteScroll = useInfiniteScroll;
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
@@ -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
- import * as react from 'react';
2
- import { Ref, RefObject } from 'react';
3
-
4
- declare function useIsMobile(): boolean;
5
-
6
- interface UseInfiniteScrollOptions {
7
- /** Callback when intersection occurs */
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';