@perspective-ai/sdk-react 1.3.0 → 1.4.0-pr-25-20260306102715
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/index.cjs +224 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +225 -108
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/hooks/useAutoOpen.test.ts +110 -13
- package/src/hooks/useAutoOpen.ts +63 -4
- package/src/hooks/useFloatBubble.ts +8 -4
- package/src/hooks/usePopup.test.ts +224 -55
- package/src/hooks/usePopup.ts +93 -62
- package/src/hooks/useSlider.test.ts +274 -41
- package/src/hooks/useSlider.ts +93 -62
- package/src/hooks/useStableValue.test.ts +108 -0
- package/src/hooks/useStableValue.ts +34 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef, useLayoutEffect, useEffect, useCallback, useState } from 'react';
|
|
2
|
-
import { openPopup, openSlider, createFloatBubble, markShown, shouldShow, setupTrigger, createWidget, createFullpage } from '@perspective-ai/sdk';
|
|
2
|
+
import { openPopup, openSlider, createFloatBubble, markShown, shouldShow, preloadIframe, getHost, destroyPreloadedByType, setupTrigger, createWidget, createFullpage } from '@perspective-ai/sdk';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
@@ -14,6 +14,28 @@ function useStableCallback(callback) {
|
|
|
14
14
|
);
|
|
15
15
|
return callback ? stable : callback;
|
|
16
16
|
}
|
|
17
|
+
function sortObjectKeys(value) {
|
|
18
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
19
|
+
return Object.keys(value).sort().reduce((result, key) => {
|
|
20
|
+
result[key] = value[key];
|
|
21
|
+
return result;
|
|
22
|
+
}, {});
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function useStableValue(value) {
|
|
27
|
+
const ref = useRef(value);
|
|
28
|
+
const serialized = JSON.stringify(
|
|
29
|
+
value,
|
|
30
|
+
(_key, currentValue) => sortObjectKeys(currentValue)
|
|
31
|
+
);
|
|
32
|
+
const prevSerialized = useRef(serialized);
|
|
33
|
+
if (prevSerialized.current !== serialized) {
|
|
34
|
+
prevSerialized.current = serialized;
|
|
35
|
+
ref.current = value;
|
|
36
|
+
}
|
|
37
|
+
return ref.current;
|
|
38
|
+
}
|
|
17
39
|
|
|
18
40
|
// src/hooks/usePopup.ts
|
|
19
41
|
function usePopup(options) {
|
|
@@ -36,6 +58,9 @@ function usePopup(options) {
|
|
|
36
58
|
const handleRef = useRef(null);
|
|
37
59
|
const isControlled = controlledOpen !== void 0;
|
|
38
60
|
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
61
|
+
const initialHiddenRef = useRef(!isOpen);
|
|
62
|
+
const stableParams = useStableValue(params);
|
|
63
|
+
const stableBrand = useStableValue(brand);
|
|
39
64
|
const stableOnReady = useStableCallback(onReady);
|
|
40
65
|
const stableOnSubmit = useStableCallback(onSubmit);
|
|
41
66
|
const stableOnNavigate = useStableCallback(onNavigate);
|
|
@@ -50,65 +75,98 @@ function usePopup(options) {
|
|
|
50
75
|
},
|
|
51
76
|
[isControlled, onOpenChange]
|
|
52
77
|
);
|
|
78
|
+
const destroyPopup = useCallback(
|
|
79
|
+
(targetHandle = handleRef.current) => {
|
|
80
|
+
if (!targetHandle) return;
|
|
81
|
+
targetHandle.update({ onClose: void 0 });
|
|
82
|
+
targetHandle.destroy();
|
|
83
|
+
if (handleRef.current === targetHandle) {
|
|
84
|
+
handleRef.current = null;
|
|
85
|
+
setHandle(null);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
[]
|
|
89
|
+
);
|
|
53
90
|
const handleClose = useCallback(() => {
|
|
54
|
-
handleRef.current = null;
|
|
55
|
-
setHandle(null);
|
|
56
91
|
setOpen(false);
|
|
57
92
|
onClose?.();
|
|
58
93
|
}, [setOpen, onClose]);
|
|
59
94
|
const stableOnClose = useStableCallback(handleClose);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const newHandle = openPopup({
|
|
95
|
+
const createPopupHandle = useCallback(
|
|
96
|
+
(startHidden) => openPopup({
|
|
63
97
|
researchId,
|
|
64
|
-
params,
|
|
65
|
-
brand,
|
|
98
|
+
params: stableParams,
|
|
99
|
+
brand: stableBrand,
|
|
66
100
|
theme,
|
|
67
101
|
host,
|
|
68
102
|
onReady: stableOnReady,
|
|
69
103
|
onSubmit: stableOnSubmit,
|
|
70
104
|
onNavigate: stableOnNavigate,
|
|
71
105
|
onClose: stableOnClose,
|
|
72
|
-
onError: stableOnError
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
handleRef.current
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
onError: stableOnError,
|
|
107
|
+
_startHidden: startHidden
|
|
108
|
+
}),
|
|
109
|
+
[
|
|
110
|
+
researchId,
|
|
111
|
+
stableParams,
|
|
112
|
+
stableBrand,
|
|
113
|
+
theme,
|
|
114
|
+
host,
|
|
115
|
+
stableOnReady,
|
|
116
|
+
stableOnSubmit,
|
|
117
|
+
stableOnNavigate,
|
|
118
|
+
stableOnClose,
|
|
119
|
+
stableOnError
|
|
120
|
+
]
|
|
121
|
+
);
|
|
122
|
+
const setPopupHandle = useCallback(
|
|
123
|
+
(startHidden) => {
|
|
124
|
+
const newHandle = createPopupHandle(startHidden);
|
|
125
|
+
handleRef.current = newHandle;
|
|
126
|
+
setHandle(newHandle);
|
|
127
|
+
return newHandle;
|
|
128
|
+
},
|
|
129
|
+
[createPopupHandle]
|
|
130
|
+
);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const currentHandle = handleRef.current;
|
|
133
|
+
if (!currentHandle) return;
|
|
134
|
+
if (isOpen) {
|
|
135
|
+
currentHandle.show();
|
|
136
|
+
} else {
|
|
137
|
+
currentHandle.hide();
|
|
94
138
|
}
|
|
95
|
-
}, []);
|
|
139
|
+
}, [isOpen]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const nextHandle = setPopupHandle(
|
|
142
|
+
handleRef.current ? !handleRef.current.isOpen : initialHiddenRef.current
|
|
143
|
+
);
|
|
144
|
+
return () => {
|
|
145
|
+
if (handleRef.current === nextHandle) {
|
|
146
|
+
destroyPopup(nextHandle);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}, [destroyPopup, setPopupHandle]);
|
|
150
|
+
const ensurePopup = useCallback(() => {
|
|
151
|
+
if (handleRef.current) return handleRef.current;
|
|
152
|
+
return setPopupHandle(false);
|
|
153
|
+
}, [setPopupHandle]);
|
|
96
154
|
const openFn = useCallback(() => {
|
|
97
155
|
if (isControlled) {
|
|
98
156
|
onOpenChange?.(true);
|
|
99
157
|
} else {
|
|
100
|
-
|
|
158
|
+
ensurePopup().show();
|
|
101
159
|
setInternalOpen(true);
|
|
102
160
|
}
|
|
103
|
-
}, [isControlled, onOpenChange
|
|
161
|
+
}, [ensurePopup, isControlled, onOpenChange]);
|
|
104
162
|
const closeFn = useCallback(() => {
|
|
105
163
|
if (isControlled) {
|
|
106
164
|
onOpenChange?.(false);
|
|
107
165
|
} else {
|
|
108
|
-
|
|
166
|
+
handleRef.current?.hide();
|
|
109
167
|
setInternalOpen(false);
|
|
110
168
|
}
|
|
111
|
-
}, [isControlled, onOpenChange
|
|
169
|
+
}, [isControlled, onOpenChange]);
|
|
112
170
|
const toggleFn = useCallback(() => {
|
|
113
171
|
if (isOpen) {
|
|
114
172
|
closeFn();
|
|
@@ -116,22 +174,6 @@ function usePopup(options) {
|
|
|
116
174
|
openFn();
|
|
117
175
|
}
|
|
118
176
|
}, [isOpen, openFn, closeFn]);
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
if (!isControlled) return;
|
|
121
|
-
if (controlledOpen && !handleRef.current) {
|
|
122
|
-
createPopup();
|
|
123
|
-
} else if (!controlledOpen && handleRef.current) {
|
|
124
|
-
destroyPopup();
|
|
125
|
-
}
|
|
126
|
-
}, [controlledOpen, isControlled, createPopup, destroyPopup]);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
return () => {
|
|
129
|
-
if (handleRef.current) {
|
|
130
|
-
handleRef.current.destroy();
|
|
131
|
-
handleRef.current = null;
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
}, []);
|
|
135
177
|
return {
|
|
136
178
|
open: openFn,
|
|
137
179
|
close: closeFn,
|
|
@@ -160,6 +202,9 @@ function useSlider(options) {
|
|
|
160
202
|
const handleRef = useRef(null);
|
|
161
203
|
const isControlled = controlledOpen !== void 0;
|
|
162
204
|
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
205
|
+
const initialHiddenRef = useRef(!isOpen);
|
|
206
|
+
const stableParams = useStableValue(params);
|
|
207
|
+
const stableBrand = useStableValue(brand);
|
|
163
208
|
const stableOnReady = useStableCallback(onReady);
|
|
164
209
|
const stableOnSubmit = useStableCallback(onSubmit);
|
|
165
210
|
const stableOnNavigate = useStableCallback(onNavigate);
|
|
@@ -174,65 +219,98 @@ function useSlider(options) {
|
|
|
174
219
|
},
|
|
175
220
|
[isControlled, onOpenChange]
|
|
176
221
|
);
|
|
222
|
+
const destroySlider = useCallback(
|
|
223
|
+
(targetHandle = handleRef.current) => {
|
|
224
|
+
if (!targetHandle) return;
|
|
225
|
+
targetHandle.update({ onClose: void 0 });
|
|
226
|
+
targetHandle.destroy();
|
|
227
|
+
if (handleRef.current === targetHandle) {
|
|
228
|
+
handleRef.current = null;
|
|
229
|
+
setHandle(null);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
[]
|
|
233
|
+
);
|
|
177
234
|
const handleClose = useCallback(() => {
|
|
178
|
-
handleRef.current = null;
|
|
179
|
-
setHandle(null);
|
|
180
235
|
setOpen(false);
|
|
181
236
|
onClose?.();
|
|
182
237
|
}, [setOpen, onClose]);
|
|
183
238
|
const stableOnClose = useStableCallback(handleClose);
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const newHandle = openSlider({
|
|
239
|
+
const createSliderHandle = useCallback(
|
|
240
|
+
(startHidden) => openSlider({
|
|
187
241
|
researchId,
|
|
188
|
-
params,
|
|
189
|
-
brand,
|
|
242
|
+
params: stableParams,
|
|
243
|
+
brand: stableBrand,
|
|
190
244
|
theme,
|
|
191
245
|
host,
|
|
192
246
|
onReady: stableOnReady,
|
|
193
247
|
onSubmit: stableOnSubmit,
|
|
194
248
|
onNavigate: stableOnNavigate,
|
|
195
249
|
onClose: stableOnClose,
|
|
196
|
-
onError: stableOnError
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
handleRef.current
|
|
216
|
-
|
|
217
|
-
|
|
250
|
+
onError: stableOnError,
|
|
251
|
+
_startHidden: startHidden
|
|
252
|
+
}),
|
|
253
|
+
[
|
|
254
|
+
researchId,
|
|
255
|
+
stableParams,
|
|
256
|
+
stableBrand,
|
|
257
|
+
theme,
|
|
258
|
+
host,
|
|
259
|
+
stableOnReady,
|
|
260
|
+
stableOnSubmit,
|
|
261
|
+
stableOnNavigate,
|
|
262
|
+
stableOnClose,
|
|
263
|
+
stableOnError
|
|
264
|
+
]
|
|
265
|
+
);
|
|
266
|
+
const setSliderHandle = useCallback(
|
|
267
|
+
(startHidden) => {
|
|
268
|
+
const newHandle = createSliderHandle(startHidden);
|
|
269
|
+
handleRef.current = newHandle;
|
|
270
|
+
setHandle(newHandle);
|
|
271
|
+
return newHandle;
|
|
272
|
+
},
|
|
273
|
+
[createSliderHandle]
|
|
274
|
+
);
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
const currentHandle = handleRef.current;
|
|
277
|
+
if (!currentHandle) return;
|
|
278
|
+
if (isOpen) {
|
|
279
|
+
currentHandle.show();
|
|
280
|
+
} else {
|
|
281
|
+
currentHandle.hide();
|
|
218
282
|
}
|
|
219
|
-
}, []);
|
|
283
|
+
}, [isOpen]);
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
const nextHandle = setSliderHandle(
|
|
286
|
+
handleRef.current ? !handleRef.current.isOpen : initialHiddenRef.current
|
|
287
|
+
);
|
|
288
|
+
return () => {
|
|
289
|
+
if (handleRef.current === nextHandle) {
|
|
290
|
+
destroySlider(nextHandle);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}, [destroySlider, setSliderHandle]);
|
|
294
|
+
const ensureSlider = useCallback(() => {
|
|
295
|
+
if (handleRef.current) return handleRef.current;
|
|
296
|
+
return setSliderHandle(false);
|
|
297
|
+
}, [setSliderHandle]);
|
|
220
298
|
const openFn = useCallback(() => {
|
|
221
299
|
if (isControlled) {
|
|
222
300
|
onOpenChange?.(true);
|
|
223
301
|
} else {
|
|
224
|
-
|
|
302
|
+
ensureSlider().show();
|
|
225
303
|
setInternalOpen(true);
|
|
226
304
|
}
|
|
227
|
-
}, [isControlled, onOpenChange
|
|
305
|
+
}, [ensureSlider, isControlled, onOpenChange]);
|
|
228
306
|
const closeFn = useCallback(() => {
|
|
229
307
|
if (isControlled) {
|
|
230
308
|
onOpenChange?.(false);
|
|
231
309
|
} else {
|
|
232
|
-
|
|
310
|
+
handleRef.current?.hide();
|
|
233
311
|
setInternalOpen(false);
|
|
234
312
|
}
|
|
235
|
-
}, [isControlled, onOpenChange
|
|
313
|
+
}, [isControlled, onOpenChange]);
|
|
236
314
|
const toggleFn = useCallback(() => {
|
|
237
315
|
if (isOpen) {
|
|
238
316
|
closeFn();
|
|
@@ -240,22 +318,6 @@ function useSlider(options) {
|
|
|
240
318
|
openFn();
|
|
241
319
|
}
|
|
242
320
|
}, [isOpen, openFn, closeFn]);
|
|
243
|
-
useEffect(() => {
|
|
244
|
-
if (!isControlled) return;
|
|
245
|
-
if (controlledOpen && !handleRef.current) {
|
|
246
|
-
createSlider();
|
|
247
|
-
} else if (!controlledOpen && handleRef.current) {
|
|
248
|
-
destroySlider();
|
|
249
|
-
}
|
|
250
|
-
}, [controlledOpen, isControlled, createSlider, destroySlider]);
|
|
251
|
-
useEffect(() => {
|
|
252
|
-
return () => {
|
|
253
|
-
if (handleRef.current) {
|
|
254
|
-
handleRef.current.destroy();
|
|
255
|
-
handleRef.current = null;
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
}, []);
|
|
259
321
|
return {
|
|
260
322
|
open: openFn,
|
|
261
323
|
close: closeFn,
|
|
@@ -283,6 +345,8 @@ function useFloatBubble(options) {
|
|
|
283
345
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
284
346
|
const handleRef = useRef(null);
|
|
285
347
|
const isControlled = controlledOpen !== void 0;
|
|
348
|
+
const stableParams = useStableValue(params);
|
|
349
|
+
const stableBrand = useStableValue(brand);
|
|
286
350
|
const stableOnReady = useStableCallback(onReady);
|
|
287
351
|
const stableOnSubmit = useStableCallback(onSubmit);
|
|
288
352
|
const stableOnNavigate = useStableCallback(onNavigate);
|
|
@@ -298,8 +362,8 @@ function useFloatBubble(options) {
|
|
|
298
362
|
useEffect(() => {
|
|
299
363
|
const newHandle = createFloatBubble({
|
|
300
364
|
researchId,
|
|
301
|
-
params,
|
|
302
|
-
brand,
|
|
365
|
+
params: stableParams,
|
|
366
|
+
brand: stableBrand,
|
|
303
367
|
theme,
|
|
304
368
|
host,
|
|
305
369
|
onReady: stableOnReady,
|
|
@@ -319,8 +383,8 @@ function useFloatBubble(options) {
|
|
|
319
383
|
};
|
|
320
384
|
}, [
|
|
321
385
|
researchId,
|
|
322
|
-
|
|
323
|
-
|
|
386
|
+
stableParams,
|
|
387
|
+
stableBrand,
|
|
324
388
|
theme,
|
|
325
389
|
host,
|
|
326
390
|
stableOnReady,
|
|
@@ -378,14 +442,66 @@ function useFloatBubble(options) {
|
|
|
378
442
|
};
|
|
379
443
|
}
|
|
380
444
|
function useAutoOpen(options) {
|
|
381
|
-
const {
|
|
445
|
+
const {
|
|
446
|
+
trigger,
|
|
447
|
+
showOnce = "session",
|
|
448
|
+
researchId,
|
|
449
|
+
params,
|
|
450
|
+
brand,
|
|
451
|
+
theme,
|
|
452
|
+
host,
|
|
453
|
+
onReady,
|
|
454
|
+
onSubmit,
|
|
455
|
+
onNavigate,
|
|
456
|
+
onClose,
|
|
457
|
+
onError,
|
|
458
|
+
onAuth,
|
|
459
|
+
channel,
|
|
460
|
+
welcomeMessage
|
|
461
|
+
} = options;
|
|
382
462
|
const cleanupRef = useRef(null);
|
|
383
463
|
const [triggered, setTriggered] = useState(false);
|
|
384
464
|
const triggerDelay = trigger.type === "timeout" ? trigger.delay : void 0;
|
|
465
|
+
const stableParams = useStableValue(params);
|
|
466
|
+
const stableBrand = useStableValue(brand);
|
|
467
|
+
const stableOnReady = useStableCallback(onReady);
|
|
468
|
+
const stableOnSubmit = useStableCallback(onSubmit);
|
|
469
|
+
const stableOnNavigate = useStableCallback(onNavigate);
|
|
470
|
+
const stableOnClose = useStableCallback(onClose);
|
|
471
|
+
const stableOnError = useStableCallback(onError);
|
|
472
|
+
const stableOnAuth = useStableCallback(onAuth);
|
|
385
473
|
const stableOnTrigger = useStableCallback(() => {
|
|
386
474
|
markShown(researchId, showOnce);
|
|
387
|
-
openPopup({
|
|
475
|
+
openPopup({
|
|
476
|
+
researchId,
|
|
477
|
+
params: stableParams,
|
|
478
|
+
brand: stableBrand,
|
|
479
|
+
theme,
|
|
480
|
+
host,
|
|
481
|
+
onReady: stableOnReady,
|
|
482
|
+
onSubmit: stableOnSubmit,
|
|
483
|
+
onNavigate: stableOnNavigate,
|
|
484
|
+
onClose: stableOnClose,
|
|
485
|
+
onError: stableOnError,
|
|
486
|
+
onAuth: stableOnAuth,
|
|
487
|
+
channel,
|
|
488
|
+
welcomeMessage
|
|
489
|
+
});
|
|
388
490
|
});
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (triggered || !shouldShow(researchId, showOnce)) return;
|
|
493
|
+
preloadIframe(
|
|
494
|
+
researchId,
|
|
495
|
+
"popup",
|
|
496
|
+
getHost(host),
|
|
497
|
+
stableParams,
|
|
498
|
+
stableBrand,
|
|
499
|
+
theme
|
|
500
|
+
);
|
|
501
|
+
return () => {
|
|
502
|
+
destroyPreloadedByType(researchId, "popup");
|
|
503
|
+
};
|
|
504
|
+
}, [researchId, showOnce, host, stableParams, stableBrand, theme, triggered]);
|
|
389
505
|
useEffect(() => {
|
|
390
506
|
if (!shouldShow(researchId, showOnce)) return;
|
|
391
507
|
cleanupRef.current = setupTrigger(trigger, () => {
|
|
@@ -403,7 +519,8 @@ function useAutoOpen(options) {
|
|
|
403
519
|
const cancel = useCallback(() => {
|
|
404
520
|
cleanupRef.current?.();
|
|
405
521
|
cleanupRef.current = null;
|
|
406
|
-
|
|
522
|
+
destroyPreloadedByType(researchId, "popup");
|
|
523
|
+
}, [researchId]);
|
|
407
524
|
return { cancel, triggered };
|
|
408
525
|
}
|
|
409
526
|
function useThemeSync(theme = "system") {
|