@searchspring/snap-preact-components 0.73.6 → 0.74.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/dist/cjs/hooks/useIntersectionAdvanced.d.ts +4 -2
- package/dist/cjs/hooks/useIntersectionAdvanced.d.ts.map +1 -1
- package/dist/cjs/hooks/useIntersectionAdvanced.js +8 -16
- package/dist/cjs/providers/withTracking.d.ts.map +1 -1
- package/dist/cjs/providers/withTracking.js +33 -26
- package/dist/cjs/utilities/createImpressionObserver.d.ts +1 -0
- package/dist/cjs/utilities/createImpressionObserver.d.ts.map +1 -1
- package/dist/cjs/utilities/createImpressionObserver.js +2 -1
- package/dist/esm/hooks/useIntersectionAdvanced.d.ts +4 -2
- package/dist/esm/hooks/useIntersectionAdvanced.d.ts.map +1 -1
- package/dist/esm/hooks/useIntersectionAdvanced.js +9 -17
- package/dist/esm/providers/withTracking.d.ts.map +1 -1
- package/dist/esm/providers/withTracking.js +33 -26
- package/dist/esm/utilities/createImpressionObserver.d.ts +1 -0
- package/dist/esm/utilities/createImpressionObserver.d.ts.map +1 -1
- package/dist/esm/utilities/createImpressionObserver.js +2 -1
- package/package.json +11 -11
|
@@ -4,7 +4,9 @@ export interface UseIntersectionOptions {
|
|
|
4
4
|
fireOnce?: boolean;
|
|
5
5
|
threshold?: number | number[];
|
|
6
6
|
minVisibleTime?: number;
|
|
7
|
-
resetKey?: string;
|
|
8
7
|
}
|
|
9
|
-
export declare const useIntersectionAdvanced: (ref: MutableRef<HTMLElement | null>, options?: UseIntersectionOptions) =>
|
|
8
|
+
export declare const useIntersectionAdvanced: (ref: MutableRef<HTMLElement | null>, options?: UseIntersectionOptions) => {
|
|
9
|
+
inViewport: boolean;
|
|
10
|
+
updateRef: (el: HTMLElement | null) => void;
|
|
11
|
+
};
|
|
10
12
|
//# sourceMappingURL=useIntersectionAdvanced.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIntersectionAdvanced.d.ts","sourceRoot":"","sources":["../../../src/hooks/useIntersectionAdvanced.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"useIntersectionAdvanced.d.ts","sourceRoot":"","sources":["../../../src/hooks/useIntersectionAdvanced.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4C,UAAU,EAAE,MAAM,cAAc,CAAC;AAEpF,MAAM,WAAW,sBAAsB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAGD,eAAO,MAAM,uBAAuB,QAC9B,WAAW,WAAW,GAAG,IAAI,CAAC,YAC1B,sBAAsB;gBACf,OAAO;oBAAkB,WAAW,GAAG,IAAI,KAAK,IAAI;CA6HpE,CAAC"}
|
|
@@ -5,26 +5,18 @@ var hooks_1 = require("preact/hooks");
|
|
|
5
5
|
var VISIBILITY_POLL_INTERVAL = 250;
|
|
6
6
|
var useIntersectionAdvanced = function (ref, options) {
|
|
7
7
|
if (options === void 0) { options = {}; }
|
|
8
|
-
var _a = options.rootMargin, rootMargin = _a === void 0 ? '0px' : _a, _b = options.fireOnce, fireOnce = _b === void 0 ? false : _b, _c = options.threshold, threshold = _c === void 0 ? 0 : _c, _d = options.minVisibleTime, minVisibleTime = _d === void 0 ? 0 : _d
|
|
8
|
+
var _a = options.rootMargin, rootMargin = _a === void 0 ? '0px' : _a, _b = options.fireOnce, fireOnce = _b === void 0 ? false : _b, _c = options.threshold, threshold = _c === void 0 ? 0 : _c, _d = options.minVisibleTime, minVisibleTime = _d === void 0 ? 0 : _d;
|
|
9
9
|
// State and setter for storing whether element is visible
|
|
10
10
|
var _e = (0, hooks_1.useState)(false), isIntersecting = _e[0], setIntersecting = _e[1];
|
|
11
11
|
// Timer reference to track visibility duration
|
|
12
12
|
var visibleTimerRef = (0, hooks_1.useRef)(null);
|
|
13
13
|
// Track when the element started being visible
|
|
14
14
|
var visibleStartRef = (0, hooks_1.useRef)(null);
|
|
15
|
-
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (visibleTimerRef.current) {
|
|
21
|
-
window.clearTimeout(visibleTimerRef.current);
|
|
22
|
-
visibleTimerRef.current = null;
|
|
23
|
-
}
|
|
24
|
-
visibleStartRef.current = null;
|
|
25
|
-
lastResetKeyRef.current = resetKey;
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
15
|
+
var _f = (0, hooks_1.useState)(0), counter = _f[0], setCounter = _f[1];
|
|
16
|
+
var updateRef = (0, hooks_1.useCallback)(function (el) {
|
|
17
|
+
ref.current = el;
|
|
18
|
+
setCounter(function (c) { return c + 1; });
|
|
19
|
+
}, []);
|
|
28
20
|
(0, hooks_1.useEffect)(function () {
|
|
29
21
|
setIntersecting(false);
|
|
30
22
|
var observer = null;
|
|
@@ -119,8 +111,8 @@ var useIntersectionAdvanced = function (ref, options) {
|
|
|
119
111
|
observer.unobserve(ref.current);
|
|
120
112
|
}
|
|
121
113
|
};
|
|
122
|
-
}, [ref,
|
|
123
|
-
return isIntersecting;
|
|
114
|
+
}, [ref, counter]);
|
|
115
|
+
return { inViewport: isIntersecting, updateRef: updateRef };
|
|
124
116
|
};
|
|
125
117
|
exports.useIntersectionAdvanced = useIntersectionAdvanced;
|
|
126
118
|
function elementIsVisible(el) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withTracking.d.ts","sourceRoot":"","sources":["../../../src/providers/withTracking.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAK,aAAa,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAA8B,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACxH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAIhF,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAC/C,UAAU,iBAAiB;IAC1B,UAAU,CAAC,EAAE,gBAAgB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,KAAK,SAAS,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"withTracking.d.ts","sourceRoot":"","sources":["../../../src/providers/withTracking.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAK,aAAa,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAA8B,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACxH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAIhF,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAC/C,UAAU,iBAAiB;IAC1B,UAAU,CAAC,EAAE,gBAAgB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,KAAK,SAAS,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,CAAC,KAAK,CAAC,4BAgGnG"}
|
|
@@ -29,7 +29,7 @@ var hooks_1 = require("preact/hooks");
|
|
|
29
29
|
exports.TRACKING_ATTRIBUTE = 'sstracking';
|
|
30
30
|
function withTracking(WrappedComponent) {
|
|
31
31
|
var WithTracking = function (props) {
|
|
32
|
-
var _a;
|
|
32
|
+
var _a, _b;
|
|
33
33
|
var controller = props.controller, result = props.result, banner = props.banner, type = props.type, content = props.content, restProps = __rest(props, ["controller", "result", "banner", "type", "content"]);
|
|
34
34
|
if (props.trackingRef) {
|
|
35
35
|
// case where withTracking may get used more than once
|
|
@@ -41,29 +41,34 @@ function withTracking(WrappedComponent) {
|
|
|
41
41
|
if (!result && !banner && (!type || !content)) {
|
|
42
42
|
console.warn('Warning: No result or banner provided to withTracking');
|
|
43
43
|
}
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
var _c = (0, utilities_1.createImpressionObserver)(), ref = _c.ref, inViewport = _c.inViewport, updateRef = _c.updateRef;
|
|
45
|
+
// Reset impression tracking when the result identity changes (e.g. new search context).
|
|
46
|
+
// Each Product/Banner gets a new responseId per search response, so this naturally
|
|
47
|
+
// resets when query/sort/filters change without needing global controller state.
|
|
48
|
+
// Calling updateRef(ref.current) re-observes the same element with fresh state.
|
|
49
|
+
var resultIdentity = (_b = (result || banner || (type && ((_a = content === null || content === void 0 ? void 0 : content[type]) === null || _a === void 0 ? void 0 : _a[0])))) === null || _b === void 0 ? void 0 : _b.responseId;
|
|
50
|
+
var prevIdentityRef = (0, hooks_1.useRef)(resultIdentity);
|
|
51
|
+
// Tracks whether we're waiting for the observer to reset after an identity change.
|
|
52
|
+
// Set synchronously during render to block impressions immediately when identity
|
|
53
|
+
// changes, preventing a stale inViewport=true from firing before the observer resets.
|
|
54
|
+
var awaitingReobservationRef = (0, hooks_1.useRef)(false);
|
|
55
|
+
if (prevIdentityRef.current !== resultIdentity) {
|
|
56
|
+
awaitingReobservationRef.current = true;
|
|
54
57
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
(0, hooks_1.useEffect)(function () {
|
|
59
|
+
if (prevIdentityRef.current !== resultIdentity) {
|
|
60
|
+
prevIdentityRef.current = resultIdentity;
|
|
61
|
+
updateRef(ref.current);
|
|
62
|
+
}
|
|
63
|
+
}, [resultIdentity, updateRef]);
|
|
64
|
+
(0, hooks_1.useEffect)(function () {
|
|
65
|
+
if (awaitingReobservationRef.current && !inViewport) {
|
|
66
|
+
awaitingReobservationRef.current = false;
|
|
67
|
+
}
|
|
68
|
+
}, [inViewport, resultIdentity]);
|
|
69
|
+
var isBannerTracking = type && content && !result && ['search', 'autocomplete'].includes((controller === null || controller === void 0 ? void 0 : controller.type) || '');
|
|
70
|
+
if (inViewport && !awaitingReobservationRef.current) {
|
|
71
|
+
if (isBannerTracking) {
|
|
67
72
|
controller === null || controller === void 0 ? void 0 : controller.track.banner.impression(content[type][0]);
|
|
68
73
|
}
|
|
69
74
|
else if (!(result === null || result === void 0 ? void 0 : result.bundleSeed)) {
|
|
@@ -71,7 +76,7 @@ function withTracking(WrappedComponent) {
|
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
var handleClick = (0, hooks_1.useCallback)(function (e) {
|
|
74
|
-
if (
|
|
79
|
+
if (isBannerTracking) {
|
|
75
80
|
controller === null || controller === void 0 ? void 0 : controller.track.banner.click(e, content[type][0]);
|
|
76
81
|
}
|
|
77
82
|
else {
|
|
@@ -87,8 +92,10 @@ function withTracking(WrappedComponent) {
|
|
|
87
92
|
currentRef.removeEventListener('click', handleClick, true);
|
|
88
93
|
};
|
|
89
94
|
}
|
|
90
|
-
}, [
|
|
91
|
-
var trackingProps = __assign(__assign({}, restProps), { controller: controller, result: result, banner: banner, type: type, content: content, trackingRef:
|
|
95
|
+
}, [handleClick]);
|
|
96
|
+
var trackingProps = __assign(__assign({}, restProps), { controller: controller, result: result, banner: banner, type: type, content: content, trackingRef: (0, hooks_1.useCallback)(function (el) {
|
|
97
|
+
updateRef(el);
|
|
98
|
+
}, [updateRef]) });
|
|
92
99
|
return (0, preact_1.h)(WrappedComponent, __assign({}, trackingProps));
|
|
93
100
|
};
|
|
94
101
|
return WithTracking;
|
|
@@ -3,5 +3,6 @@ import { UseIntersectionOptions } from '../hooks';
|
|
|
3
3
|
export declare function createImpressionObserver(options?: UseIntersectionOptions): {
|
|
4
4
|
ref: Ref<HTMLElement | null>;
|
|
5
5
|
inViewport: boolean;
|
|
6
|
+
updateRef: (el: HTMLElement | null) => void;
|
|
6
7
|
};
|
|
7
8
|
//# sourceMappingURL=createImpressionObserver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createImpressionObserver.d.ts","sourceRoot":"","sources":["../../../src/utilities/createImpressionObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAU,MAAM,cAAc,CAAC;AAChD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAI3E,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG;IAC3E,GAAG,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"createImpressionObserver.d.ts","sourceRoot":"","sources":["../../../src/utilities/createImpressionObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAU,MAAM,cAAc,CAAC;AAChD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAI3E,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG;IAC3E,GAAG,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CAC5C,CAaA"}
|
|
@@ -18,10 +18,11 @@ var IMPRESSION_VISIBILITY_THRESHOLD = 0.7;
|
|
|
18
18
|
var IMPRESSION_MIN_VISIBLE_TIME = 1000;
|
|
19
19
|
function createImpressionObserver(options) {
|
|
20
20
|
var ref = (0, hooks_1.useRef)(null);
|
|
21
|
-
var
|
|
21
|
+
var _a = (0, hooks_2.useIntersectionAdvanced)(ref, __assign(__assign({}, options), { fireOnce: true, threshold: IMPRESSION_VISIBILITY_THRESHOLD, minVisibleTime: IMPRESSION_MIN_VISIBLE_TIME })), inViewport = _a.inViewport, updateRef = _a.updateRef;
|
|
22
22
|
return {
|
|
23
23
|
ref: ref,
|
|
24
24
|
inViewport: inViewport,
|
|
25
|
+
updateRef: updateRef,
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
exports.createImpressionObserver = createImpressionObserver;
|
|
@@ -4,7 +4,9 @@ export interface UseIntersectionOptions {
|
|
|
4
4
|
fireOnce?: boolean;
|
|
5
5
|
threshold?: number | number[];
|
|
6
6
|
minVisibleTime?: number;
|
|
7
|
-
resetKey?: string;
|
|
8
7
|
}
|
|
9
|
-
export declare const useIntersectionAdvanced: (ref: MutableRef<HTMLElement | null>, options?: UseIntersectionOptions) =>
|
|
8
|
+
export declare const useIntersectionAdvanced: (ref: MutableRef<HTMLElement | null>, options?: UseIntersectionOptions) => {
|
|
9
|
+
inViewport: boolean;
|
|
10
|
+
updateRef: (el: HTMLElement | null) => void;
|
|
11
|
+
};
|
|
10
12
|
//# sourceMappingURL=useIntersectionAdvanced.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIntersectionAdvanced.d.ts","sourceRoot":"","sources":["../../../src/hooks/useIntersectionAdvanced.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"useIntersectionAdvanced.d.ts","sourceRoot":"","sources":["../../../src/hooks/useIntersectionAdvanced.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4C,UAAU,EAAE,MAAM,cAAc,CAAC;AAEpF,MAAM,WAAW,sBAAsB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAGD,eAAO,MAAM,uBAAuB,QAC9B,WAAW,WAAW,GAAG,IAAI,CAAC,YAC1B,sBAAsB;gBACf,OAAO;oBAAkB,WAAW,GAAG,IAAI,KAAK,IAAI;CA6HpE,CAAC"}
|
|
@@ -1,26 +1,18 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
|
|
2
2
|
const VISIBILITY_POLL_INTERVAL = 250;
|
|
3
3
|
export const useIntersectionAdvanced = (ref, options = {}) => {
|
|
4
|
-
const { rootMargin = '0px', fireOnce = false, threshold = 0, minVisibleTime = 0
|
|
4
|
+
const { rootMargin = '0px', fireOnce = false, threshold = 0, minVisibleTime = 0 } = options;
|
|
5
5
|
// State and setter for storing whether element is visible
|
|
6
6
|
const [isIntersecting, setIntersecting] = useState(false);
|
|
7
7
|
// Timer reference to track visibility duration
|
|
8
8
|
const visibleTimerRef = useRef(null);
|
|
9
9
|
// Track when the element started being visible
|
|
10
10
|
const visibleStartRef = useRef(null);
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (visibleTimerRef.current) {
|
|
17
|
-
window.clearTimeout(visibleTimerRef.current);
|
|
18
|
-
visibleTimerRef.current = null;
|
|
19
|
-
}
|
|
20
|
-
visibleStartRef.current = null;
|
|
21
|
-
lastResetKeyRef.current = resetKey;
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
11
|
+
const [counter, setCounter] = useState(0);
|
|
12
|
+
const updateRef = useCallback((el) => {
|
|
13
|
+
ref.current = el;
|
|
14
|
+
setCounter((c) => c + 1);
|
|
15
|
+
}, []);
|
|
24
16
|
useEffect(() => {
|
|
25
17
|
setIntersecting(false);
|
|
26
18
|
let observer = null;
|
|
@@ -114,8 +106,8 @@ export const useIntersectionAdvanced = (ref, options = {}) => {
|
|
|
114
106
|
observer.unobserve(ref.current);
|
|
115
107
|
}
|
|
116
108
|
};
|
|
117
|
-
}, [ref,
|
|
118
|
-
return isIntersecting;
|
|
109
|
+
}, [ref, counter]);
|
|
110
|
+
return { inViewport: isIntersecting, updateRef };
|
|
119
111
|
};
|
|
120
112
|
function elementIsVisible(el) {
|
|
121
113
|
if (el && 'checkVisibility' in el) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withTracking.d.ts","sourceRoot":"","sources":["../../../src/providers/withTracking.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAK,aAAa,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAA8B,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACxH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAIhF,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAC/C,UAAU,iBAAiB;IAC1B,UAAU,CAAC,EAAE,gBAAgB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,KAAK,SAAS,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"withTracking.d.ts","sourceRoot":"","sources":["../../../src/providers/withTracking.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAK,aAAa,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAA8B,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACxH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAIhF,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAC/C,UAAU,iBAAiB;IAC1B,UAAU,CAAC,EAAE,gBAAgB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,KAAK,SAAS,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,CAAC,KAAK,CAAC,4BAgGnG"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { h } from 'preact';
|
|
2
2
|
import { createImpressionObserver } from '../utilities';
|
|
3
|
-
import { useEffect, useCallback } from 'preact/hooks';
|
|
3
|
+
import { useEffect, useCallback, useRef } from 'preact/hooks';
|
|
4
4
|
export const TRACKING_ATTRIBUTE = 'sstracking';
|
|
5
5
|
export function withTracking(WrappedComponent) {
|
|
6
6
|
const WithTracking = (props) => {
|
|
@@ -15,29 +15,34 @@ export function withTracking(WrappedComponent) {
|
|
|
15
15
|
if (!result && !banner && (!type || !content)) {
|
|
16
16
|
console.warn('Warning: No result or banner provided to withTracking');
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
const { ref, inViewport, updateRef } = createImpressionObserver();
|
|
19
|
+
// Reset impression tracking when the result identity changes (e.g. new search context).
|
|
20
|
+
// Each Product/Banner gets a new responseId per search response, so this naturally
|
|
21
|
+
// resets when query/sort/filters change without needing global controller state.
|
|
22
|
+
// Calling updateRef(ref.current) re-observes the same element with fresh state.
|
|
23
|
+
const resultIdentity = (result || banner || (type && content?.[type]?.[0]))?.responseId;
|
|
24
|
+
const prevIdentityRef = useRef(resultIdentity);
|
|
25
|
+
// Tracks whether we're waiting for the observer to reset after an identity change.
|
|
26
|
+
// Set synchronously during render to block impressions immediately when identity
|
|
27
|
+
// changes, preventing a stale inViewport=true from firing before the observer resets.
|
|
28
|
+
const awaitingReobservationRef = useRef(false);
|
|
29
|
+
if (prevIdentityRef.current !== resultIdentity) {
|
|
30
|
+
awaitingReobservationRef.current = true;
|
|
28
31
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (prevIdentityRef.current !== resultIdentity) {
|
|
34
|
+
prevIdentityRef.current = resultIdentity;
|
|
35
|
+
updateRef(ref.current);
|
|
36
|
+
}
|
|
37
|
+
}, [resultIdentity, updateRef]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (awaitingReobservationRef.current && !inViewport) {
|
|
40
|
+
awaitingReobservationRef.current = false;
|
|
41
|
+
}
|
|
42
|
+
}, [inViewport, resultIdentity]);
|
|
43
|
+
const isBannerTracking = type && content && !result && ['search', 'autocomplete'].includes(controller?.type || '');
|
|
44
|
+
if (inViewport && !awaitingReobservationRef.current) {
|
|
45
|
+
if (isBannerTracking) {
|
|
41
46
|
controller?.track.banner.impression(content[type][0]);
|
|
42
47
|
}
|
|
43
48
|
else if (!result?.bundleSeed) {
|
|
@@ -45,7 +50,7 @@ export function withTracking(WrappedComponent) {
|
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
const handleClick = useCallback((e) => {
|
|
48
|
-
if (
|
|
53
|
+
if (isBannerTracking) {
|
|
49
54
|
controller?.track.banner.click(e, content[type][0]);
|
|
50
55
|
}
|
|
51
56
|
else {
|
|
@@ -61,7 +66,7 @@ export function withTracking(WrappedComponent) {
|
|
|
61
66
|
currentRef.removeEventListener('click', handleClick, true);
|
|
62
67
|
};
|
|
63
68
|
}
|
|
64
|
-
}, [
|
|
69
|
+
}, [handleClick]);
|
|
65
70
|
const trackingProps = {
|
|
66
71
|
...restProps,
|
|
67
72
|
controller,
|
|
@@ -69,7 +74,9 @@ export function withTracking(WrappedComponent) {
|
|
|
69
74
|
banner,
|
|
70
75
|
type,
|
|
71
76
|
content,
|
|
72
|
-
trackingRef:
|
|
77
|
+
trackingRef: useCallback((el) => {
|
|
78
|
+
updateRef(el);
|
|
79
|
+
}, [updateRef]),
|
|
73
80
|
};
|
|
74
81
|
return h(WrappedComponent, { ...trackingProps });
|
|
75
82
|
};
|
|
@@ -3,5 +3,6 @@ import { UseIntersectionOptions } from '../hooks';
|
|
|
3
3
|
export declare function createImpressionObserver(options?: UseIntersectionOptions): {
|
|
4
4
|
ref: Ref<HTMLElement | null>;
|
|
5
5
|
inViewport: boolean;
|
|
6
|
+
updateRef: (el: HTMLElement | null) => void;
|
|
6
7
|
};
|
|
7
8
|
//# sourceMappingURL=createImpressionObserver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createImpressionObserver.d.ts","sourceRoot":"","sources":["../../../src/utilities/createImpressionObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAU,MAAM,cAAc,CAAC;AAChD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAI3E,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG;IAC3E,GAAG,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"createImpressionObserver.d.ts","sourceRoot":"","sources":["../../../src/utilities/createImpressionObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAU,MAAM,cAAc,CAAC;AAChD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAI3E,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG;IAC3E,GAAG,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CAC5C,CAaA"}
|
|
@@ -4,7 +4,7 @@ const IMPRESSION_VISIBILITY_THRESHOLD = 0.7;
|
|
|
4
4
|
const IMPRESSION_MIN_VISIBLE_TIME = 1000;
|
|
5
5
|
export function createImpressionObserver(options) {
|
|
6
6
|
const ref = useRef(null);
|
|
7
|
-
const inViewport = useIntersectionAdvanced(ref, {
|
|
7
|
+
const { inViewport, updateRef } = useIntersectionAdvanced(ref, {
|
|
8
8
|
...options,
|
|
9
9
|
fireOnce: true,
|
|
10
10
|
threshold: IMPRESSION_VISIBILITY_THRESHOLD,
|
|
@@ -13,5 +13,6 @@ export function createImpressionObserver(options) {
|
|
|
13
13
|
return {
|
|
14
14
|
ref,
|
|
15
15
|
inViewport,
|
|
16
|
+
updateRef,
|
|
16
17
|
};
|
|
17
18
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@searchspring/snap-preact-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.0",
|
|
4
4
|
"description": "Snap Preact Component Library",
|
|
5
5
|
"author": "Searchspring",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@cypress/react": "^8.0.0",
|
|
31
31
|
"@emotion/react": "11.9.0",
|
|
32
|
-
"@searchspring/snap-toolbox": "0.
|
|
32
|
+
"@searchspring/snap-toolbox": "0.74.0",
|
|
33
33
|
"classnames": "^2.3.2",
|
|
34
34
|
"cypress": "^13.7.1",
|
|
35
35
|
"cypress-wait-until": "^1.7.2",
|
|
@@ -52,14 +52,14 @@
|
|
|
52
52
|
"@babel/preset-env": "^7.21.4",
|
|
53
53
|
"@babel/preset-react": "^7.18.6",
|
|
54
54
|
"@babel/runtime": "^7.21.0",
|
|
55
|
-
"@searchspring/snap-client": "0.
|
|
56
|
-
"@searchspring/snap-controller": "0.
|
|
57
|
-
"@searchspring/snap-event-manager": "0.
|
|
58
|
-
"@searchspring/snap-logger": "0.
|
|
59
|
-
"@searchspring/snap-profiler": "0.
|
|
60
|
-
"@searchspring/snap-store-mobx": "0.
|
|
61
|
-
"@searchspring/snap-tracker": "0.
|
|
62
|
-
"@searchspring/snap-url-manager": "0.
|
|
55
|
+
"@searchspring/snap-client": "0.74.0",
|
|
56
|
+
"@searchspring/snap-controller": "0.74.0",
|
|
57
|
+
"@searchspring/snap-event-manager": "0.74.0",
|
|
58
|
+
"@searchspring/snap-logger": "0.74.0",
|
|
59
|
+
"@searchspring/snap-profiler": "0.74.0",
|
|
60
|
+
"@searchspring/snap-store-mobx": "0.74.0",
|
|
61
|
+
"@searchspring/snap-tracker": "0.74.0",
|
|
62
|
+
"@searchspring/snap-url-manager": "0.74.0",
|
|
63
63
|
"@storybook/addon-actions": "6.4.22",
|
|
64
64
|
"@storybook/addon-controls": "6.4.22",
|
|
65
65
|
"@storybook/addon-docs": "6.4.22",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"webpack-merge": "^5.8.0"
|
|
85
85
|
},
|
|
86
86
|
"sideEffects": false,
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "fd005b3943aa6eb62ac16a92e6d932dc55499865"
|
|
88
88
|
}
|