@uncinc/uncinc-react-kitchen-sink 2.26.0 → 2.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useScrollDepth
|
|
3
|
+
*
|
|
4
|
+
* Tracks scroll depth and calls `onThreshold` once per threshold per page.
|
|
5
|
+
*
|
|
6
|
+
* Why a custom hook instead of relying on third-party scroll tracking?
|
|
7
|
+
* Most trackers capture `document.scrollHeight` once at initialisation. With
|
|
8
|
+
* client-side rendering the page height changes as content loads asynchronously,
|
|
9
|
+
* so that snapshot is too small and all percentages end up wrong.
|
|
10
|
+
*
|
|
11
|
+
* How this avoids continuous re-renders:
|
|
12
|
+
* - All mutable state lives in `useRef` — nothing in `useState`.
|
|
13
|
+
* - Scroll events are throttled to one check per animation frame via
|
|
14
|
+
* `requestAnimationFrame`; the handler is removed on cleanup.
|
|
15
|
+
* - `ResizeObserver` keeps `documentHeight` in sync as async content renders,
|
|
16
|
+
* without triggering React renders.
|
|
17
|
+
*
|
|
18
|
+
* @param onThreshold - Called once per threshold per page with the percentage value.
|
|
19
|
+
* @param resetKey - Change this value on every navigation (e.g. page title or
|
|
20
|
+
* pathname) to reset the fired thresholds for the new page.
|
|
21
|
+
* @param thresholds - Scroll depth percentages (%) to track.
|
|
22
|
+
* Defaults to [25, 50, 75, 90, 100].
|
|
23
|
+
*/
|
|
24
|
+
declare const useScrollDepth: (onThreshold: (threshold: number) => void, resetKey?: unknown, thresholds?: number[]) => void;
|
|
25
|
+
export default useScrollDepth;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useScrollDepth
|
|
5
|
+
*
|
|
6
|
+
* Tracks scroll depth and calls `onThreshold` once per threshold per page.
|
|
7
|
+
*
|
|
8
|
+
* Why a custom hook instead of relying on third-party scroll tracking?
|
|
9
|
+
* Most trackers capture `document.scrollHeight` once at initialisation. With
|
|
10
|
+
* client-side rendering the page height changes as content loads asynchronously,
|
|
11
|
+
* so that snapshot is too small and all percentages end up wrong.
|
|
12
|
+
*
|
|
13
|
+
* How this avoids continuous re-renders:
|
|
14
|
+
* - All mutable state lives in `useRef` — nothing in `useState`.
|
|
15
|
+
* - Scroll events are throttled to one check per animation frame via
|
|
16
|
+
* `requestAnimationFrame`; the handler is removed on cleanup.
|
|
17
|
+
* - `ResizeObserver` keeps `documentHeight` in sync as async content renders,
|
|
18
|
+
* without triggering React renders.
|
|
19
|
+
*
|
|
20
|
+
* @param onThreshold - Called once per threshold per page with the percentage value.
|
|
21
|
+
* @param resetKey - Change this value on every navigation (e.g. page title or
|
|
22
|
+
* pathname) to reset the fired thresholds for the new page.
|
|
23
|
+
* @param thresholds - Scroll depth percentages (%) to track.
|
|
24
|
+
* Defaults to [25, 50, 75, 90, 100].
|
|
25
|
+
*/
|
|
26
|
+
var useScrollDepth = function useScrollDepth(onThreshold, resetKey) {
|
|
27
|
+
var thresholds = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [25, 50, 75, 90, 100];
|
|
28
|
+
// Tracks which thresholds have already fired for the current page.
|
|
29
|
+
// useRef so mutations never trigger a re-render.
|
|
30
|
+
var firedThresholds = useRef(new Set());
|
|
31
|
+
var rafId = useRef(null);
|
|
32
|
+
var documentHeight = useRef(0);
|
|
33
|
+
// Keep a ref to onThreshold so the effect never needs to re-run when the
|
|
34
|
+
// callback identity changes (e.g. inline arrow in parent), while still
|
|
35
|
+
// always calling the latest version.
|
|
36
|
+
var onThresholdRef = useRef(onThreshold);
|
|
37
|
+
onThresholdRef.current = onThreshold;
|
|
38
|
+
// Keep a ref to thresholds so the effect never needs to re-run when the
|
|
39
|
+
// array identity changes, while still always reading the latest value.
|
|
40
|
+
var thresholdsRef = useRef(thresholds);
|
|
41
|
+
thresholdsRef.current = thresholds;
|
|
42
|
+
|
|
43
|
+
// Reset fired thresholds on every page navigation.
|
|
44
|
+
useEffect(function () {
|
|
45
|
+
firedThresholds.current.clear();
|
|
46
|
+
}, [resetKey]);
|
|
47
|
+
useEffect(function () {
|
|
48
|
+
if (typeof window === 'undefined') return undefined;
|
|
49
|
+
var updateDocumentHeight = function updateDocumentHeight() {
|
|
50
|
+
// Use Math.max to handle cross-browser quirks (body vs documentElement).
|
|
51
|
+
documentHeight.current = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
52
|
+
};
|
|
53
|
+
var checkScrollDepth = function checkScrollDepth() {
|
|
54
|
+
var scrollTop = window.scrollY;
|
|
55
|
+
var windowHeight = window.innerHeight;
|
|
56
|
+
var docHeight = documentHeight.current;
|
|
57
|
+
|
|
58
|
+
// Skip pages that fit entirely in the viewport — no meaningful depth to track.
|
|
59
|
+
if (docHeight <= windowHeight) return;
|
|
60
|
+
|
|
61
|
+
// scrollDepth = (scrollTop + viewportHeight) / documentHeight × 100
|
|
62
|
+
var scrollPercent = Math.round((scrollTop + windowHeight) / docHeight * 100);
|
|
63
|
+
thresholdsRef.current.forEach(function (threshold) {
|
|
64
|
+
if (scrollPercent >= threshold && !firedThresholds.current.has(threshold)) {
|
|
65
|
+
firedThresholds.current.add(threshold);
|
|
66
|
+
onThresholdRef.current(threshold);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
var onScroll = function onScroll() {
|
|
71
|
+
// Gate on an active rAF so we calculate at most once per rendered frame,
|
|
72
|
+
// regardless of how fast the browser fires scroll events.
|
|
73
|
+
if (rafId.current !== null) return;
|
|
74
|
+
rafId.current = requestAnimationFrame(function () {
|
|
75
|
+
checkScrollDepth();
|
|
76
|
+
rafId.current = null;
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ResizeObserver re-measures the document whenever content loads in,
|
|
81
|
+
// keeping the height accurate as async content renders.
|
|
82
|
+
var resizeObserver = new ResizeObserver(function () {
|
|
83
|
+
updateDocumentHeight();
|
|
84
|
+
// Re-evaluate depth after a layout change; lazily-loaded content may
|
|
85
|
+
// have already scrolled us past a threshold.
|
|
86
|
+
checkScrollDepth();
|
|
87
|
+
});
|
|
88
|
+
updateDocumentHeight();
|
|
89
|
+
resizeObserver.observe(document.body);
|
|
90
|
+
window.addEventListener('scroll', onScroll, {
|
|
91
|
+
passive: true
|
|
92
|
+
});
|
|
93
|
+
return function () {
|
|
94
|
+
window.removeEventListener('scroll', onScroll);
|
|
95
|
+
resizeObserver.disconnect();
|
|
96
|
+
if (rafId.current !== null) {
|
|
97
|
+
cancelAnimationFrame(rafId.current);
|
|
98
|
+
rafId.current = null;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}, []); // Listeners are set up once; threshold resets are handled by the resetKey effect.
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default useScrollDepth;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useScrollDepthGTM
|
|
3
|
+
*
|
|
4
|
+
* Convenience hook that combines `useScrollDepth` with the GTM data layer.
|
|
5
|
+
* Pushes a `scroll_depth` event at each threshold — once per page — and
|
|
6
|
+
* resets on navigation when `resetKey` changes.
|
|
7
|
+
*
|
|
8
|
+
* Use this hook when you want GTM scroll-depth tracking out of the box.
|
|
9
|
+
* Use the underlying `useScrollDepth` directly if you need a different
|
|
10
|
+
* analytics integration or want full control over the callback.
|
|
11
|
+
*
|
|
12
|
+
* @param resetKey - Change on every navigation (e.g. pathname or page title)
|
|
13
|
+
* to reset the fired thresholds for the new page.
|
|
14
|
+
* @param thresholds - Scroll depth percentages (%) to track. Defaults to
|
|
15
|
+
* [25, 50, 75, 90, 100], mirroring GTM's built-in Scroll
|
|
16
|
+
* Depth trigger.
|
|
17
|
+
*/
|
|
18
|
+
declare const useScrollDepthGTM: (resetKey?: unknown, thresholds?: number[]) => void;
|
|
19
|
+
export default useScrollDepthGTM;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// gtm.js uses dynamic property assignment that TypeScript cannot infer from
|
|
2
|
+
// the source. The generated dist/helpers/gtm.d.ts contains the correct types,
|
|
3
|
+
// but during tsc compilation we cast to avoid the false-positive TS2339 error.
|
|
4
|
+
import gtmHelper from '../helpers/gtm';
|
|
5
|
+
import useScrollDepth from './useScrollDepth';
|
|
6
|
+
var gtm = gtmHelper;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* useScrollDepthGTM
|
|
10
|
+
*
|
|
11
|
+
* Convenience hook that combines `useScrollDepth` with the GTM data layer.
|
|
12
|
+
* Pushes a `scroll_depth` event at each threshold — once per page — and
|
|
13
|
+
* resets on navigation when `resetKey` changes.
|
|
14
|
+
*
|
|
15
|
+
* Use this hook when you want GTM scroll-depth tracking out of the box.
|
|
16
|
+
* Use the underlying `useScrollDepth` directly if you need a different
|
|
17
|
+
* analytics integration or want full control over the callback.
|
|
18
|
+
*
|
|
19
|
+
* @param resetKey - Change on every navigation (e.g. pathname or page title)
|
|
20
|
+
* to reset the fired thresholds for the new page.
|
|
21
|
+
* @param thresholds - Scroll depth percentages (%) to track. Defaults to
|
|
22
|
+
* [25, 50, 75, 90, 100], mirroring GTM's built-in Scroll
|
|
23
|
+
* Depth trigger.
|
|
24
|
+
*/
|
|
25
|
+
var useScrollDepthGTM = function useScrollDepthGTM(resetKey, thresholds) {
|
|
26
|
+
useScrollDepth(function (threshold) {
|
|
27
|
+
gtm.push({
|
|
28
|
+
event: 'scroll_depth',
|
|
29
|
+
percent_scrolled: threshold
|
|
30
|
+
});
|
|
31
|
+
}, resetKey, thresholds);
|
|
32
|
+
};
|
|
33
|
+
export default useScrollDepthGTM;
|
package/dist/index.d.ts
CHANGED
|
@@ -77,6 +77,8 @@ export { default as useDialogHelpers } from "./hooks/useDialogHelpers";
|
|
|
77
77
|
export { default as useEscape } from "./hooks/useEscape";
|
|
78
78
|
export { default as useOnScreen } from "./hooks/useOnScreen";
|
|
79
79
|
export { default as usePrevious } from "./hooks/usePrevious";
|
|
80
|
+
export { default as useScrollDepth } from "./hooks/useScrollDepth";
|
|
81
|
+
export { default as useScrollDepthGTM } from "./hooks/useScrollDepthGTM";
|
|
80
82
|
export { default as useScrollPosition } from "./hooks/useScrollPosition";
|
|
81
83
|
export { default as useWindowDimensions } from "./hooks/useWindowDimensions";
|
|
82
84
|
export { default as useTrapFocus } from "./hooks/useTrapFocus";
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,8 @@ export { default as useDialogHelpers } from './hooks/useDialogHelpers';
|
|
|
90
90
|
export { default as useEscape } from './hooks/useEscape';
|
|
91
91
|
export { default as useOnScreen } from './hooks/useOnScreen';
|
|
92
92
|
export { default as usePrevious } from './hooks/usePrevious';
|
|
93
|
+
export { default as useScrollDepth } from './hooks/useScrollDepth';
|
|
94
|
+
export { default as useScrollDepthGTM } from './hooks/useScrollDepthGTM';
|
|
93
95
|
export { default as useScrollPosition } from './hooks/useScrollPosition';
|
|
94
96
|
export { default as useWindowDimensions } from './hooks/useWindowDimensions';
|
|
95
97
|
export { default as useTrapFocus } from './hooks/useTrapFocus';
|