@prosdevlab/experience-sdk-plugins 0.1.4 → 0.2.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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +30 -0
- package/dist/index.d.ts +608 -1
- package/dist/index.js +692 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/exit-intent/exit-intent.test.ts +423 -0
- package/src/exit-intent/exit-intent.ts +372 -0
- package/src/exit-intent/index.ts +6 -0
- package/src/exit-intent/types.ts +59 -0
- package/src/index.ts +5 -0
- package/src/integration.test.ts +362 -0
- package/src/page-visits/index.ts +6 -0
- package/src/page-visits/page-visits.test.ts +562 -0
- package/src/page-visits/page-visits.ts +314 -0
- package/src/page-visits/types.ts +119 -0
- package/src/scroll-depth/index.ts +6 -0
- package/src/scroll-depth/scroll-depth.test.ts +545 -0
- package/src/scroll-depth/scroll-depth.ts +400 -0
- package/src/scroll-depth/types.ts +122 -0
- package/src/time-delay/index.ts +6 -0
- package/src/time-delay/time-delay.test.ts +477 -0
- package/src/time-delay/time-delay.ts +297 -0
- package/src/time-delay/types.ts +89 -0
- package/src/utils/sanitize.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosdevlab/experience-sdk-plugins@0.
|
|
2
|
+
> @prosdevlab/experience-sdk-plugins@0.2.0 build /home/runner/work/experience-sdk/experience-sdk/packages/plugins
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2024
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
14
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m40.31 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m105.77 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 262ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 1098ms
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m22.78 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @prosdevlab/experience-sdk-plugins
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 02de640: feat: add display condition plugins with event-driven architecture
|
|
8
|
+
|
|
9
|
+
Add 4 new display condition plugins with comprehensive testing and documentation:
|
|
10
|
+
|
|
11
|
+
**New Plugins:**
|
|
12
|
+
|
|
13
|
+
- Exit Intent: Velocity-based mouse tracking with session awareness
|
|
14
|
+
- Scroll Depth: Multiple thresholds with advanced engagement metrics
|
|
15
|
+
- Page Visits: Session and lifetime counters with first-visit detection
|
|
16
|
+
- Time Delay: Millisecond-precision delays with visibility API integration
|
|
17
|
+
|
|
18
|
+
**Core Enhancements:**
|
|
19
|
+
|
|
20
|
+
- Event-driven trigger architecture (`trigger:*` events)
|
|
21
|
+
- Composable display conditions (AND/OR/NOT logic)
|
|
22
|
+
- TriggerState interface for type-safe context updates
|
|
23
|
+
|
|
24
|
+
**Developer Experience:**
|
|
25
|
+
|
|
26
|
+
- 101 new tests across 4 plugins (314 total)
|
|
27
|
+
- 12 integration tests for plugin composition
|
|
28
|
+
- Complete API documentation with examples
|
|
29
|
+
- Pure functions for easier testing
|
|
30
|
+
|
|
31
|
+
All plugins are backward compatible and work independently or together.
|
|
32
|
+
|
|
3
33
|
## 0.1.4
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -192,6 +192,140 @@ interface DebugPlugin {
|
|
|
192
192
|
*/
|
|
193
193
|
declare const debugPlugin: PluginFunction;
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Exit Intent Plugin Configuration
|
|
197
|
+
*/
|
|
198
|
+
interface ExitIntentPluginConfig {
|
|
199
|
+
exitIntent?: {
|
|
200
|
+
/**
|
|
201
|
+
* Maximum Y position (px) where exit intent can trigger
|
|
202
|
+
* @default 50
|
|
203
|
+
*/
|
|
204
|
+
sensitivity?: number;
|
|
205
|
+
/**
|
|
206
|
+
* Minimum time on page (ms) before exit intent is active
|
|
207
|
+
* Prevents immediate triggers on page load
|
|
208
|
+
* @default 2000
|
|
209
|
+
*/
|
|
210
|
+
minTimeOnPage?: number;
|
|
211
|
+
/**
|
|
212
|
+
* Delay (ms) between detection and trigger
|
|
213
|
+
* @default 0
|
|
214
|
+
*/
|
|
215
|
+
delay?: number;
|
|
216
|
+
/**
|
|
217
|
+
* Number of mouse positions to track for velocity calculation
|
|
218
|
+
* @default 30
|
|
219
|
+
*/
|
|
220
|
+
positionHistorySize?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Disable exit intent on mobile devices
|
|
223
|
+
* @default true
|
|
224
|
+
*/
|
|
225
|
+
disableOnMobile?: boolean;
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Exit Intent Event Payload
|
|
230
|
+
*/
|
|
231
|
+
interface ExitIntentEvent {
|
|
232
|
+
timestamp: number;
|
|
233
|
+
lastY: number;
|
|
234
|
+
previousY: number;
|
|
235
|
+
velocity: number;
|
|
236
|
+
timeOnPage: number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Exit Intent Plugin API
|
|
240
|
+
*/
|
|
241
|
+
interface ExitIntentPlugin {
|
|
242
|
+
isTriggered(): boolean;
|
|
243
|
+
reset(): void;
|
|
244
|
+
getPositions(): Array<{
|
|
245
|
+
x: number;
|
|
246
|
+
y: number;
|
|
247
|
+
}>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Exit Intent Plugin
|
|
252
|
+
*
|
|
253
|
+
* Detects when users are about to leave the page by tracking upward mouse movement
|
|
254
|
+
* near the top of the viewport. Inspired by Pathfora's showOnExitIntent.
|
|
255
|
+
*
|
|
256
|
+
* **Event-Driven Architecture:**
|
|
257
|
+
* This plugin emits `trigger:exitIntent` events when exit intent is detected.
|
|
258
|
+
* The core runtime listens for these events and automatically re-evaluates experiences.
|
|
259
|
+
*
|
|
260
|
+
* **Usage Pattern:**
|
|
261
|
+
* Use `targeting.custom` to check if exit intent has triggered:
|
|
262
|
+
*
|
|
263
|
+
* @example Basic usage
|
|
264
|
+
* ```typescript
|
|
265
|
+
* import { init, register } from '@prosdevlab/experience-sdk';
|
|
266
|
+
* import { exitIntentPlugin } from '@prosdevlab/experience-sdk-plugins';
|
|
267
|
+
*
|
|
268
|
+
* init({
|
|
269
|
+
* plugins: [exitIntentPlugin],
|
|
270
|
+
* exitIntent: {
|
|
271
|
+
* sensitivity: 20, // Trigger within 20px of top (default: 50)
|
|
272
|
+
* minTimeOnPage: 2000, // Wait 2s before enabling (default: 2000)
|
|
273
|
+
* delay: 0, // Delay after trigger (default: 0)
|
|
274
|
+
* disableOnMobile: true // Disable on mobile (default: true)
|
|
275
|
+
* }
|
|
276
|
+
* });
|
|
277
|
+
*
|
|
278
|
+
* // Show banner only when exit intent is detected
|
|
279
|
+
* register('exit-offer', {
|
|
280
|
+
* type: 'banner',
|
|
281
|
+
* content: {
|
|
282
|
+
* title: 'Wait! Don't leave yet!',
|
|
283
|
+
* message: 'Get 15% off your first order',
|
|
284
|
+
* buttons: [{ text: 'Claim Offer', variant: 'primary' }]
|
|
285
|
+
* },
|
|
286
|
+
* targeting: {
|
|
287
|
+
* custom: (context) => context.triggers?.exitIntent?.triggered === true
|
|
288
|
+
* },
|
|
289
|
+
* frequency: { max: 1, per: 'session' } // Only show once per session
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* @example Combining with other conditions
|
|
294
|
+
* ```typescript
|
|
295
|
+
* // Show exit offer only on shop pages with items in cart
|
|
296
|
+
* register('cart-recovery', {
|
|
297
|
+
* type: 'banner',
|
|
298
|
+
* content: { message: 'Complete your purchase and save!' },
|
|
299
|
+
* targeting: {
|
|
300
|
+
* url: { contains: '/shop' },
|
|
301
|
+
* custom: (context) => {
|
|
302
|
+
* return (
|
|
303
|
+
* context.triggers?.exitIntent?.triggered === true &&
|
|
304
|
+
* getCart().items.length > 0
|
|
305
|
+
* );
|
|
306
|
+
* }
|
|
307
|
+
* }
|
|
308
|
+
* });
|
|
309
|
+
* ```
|
|
310
|
+
*
|
|
311
|
+
* @example Combining multiple triggers (exit intent + scroll depth)
|
|
312
|
+
* ```typescript
|
|
313
|
+
* // Show offer on exit intent OR after 70% scroll
|
|
314
|
+
* register('engaged-exit', {
|
|
315
|
+
* type: 'banner',
|
|
316
|
+
* content: { message: 'You're almost there!' },
|
|
317
|
+
* targeting: {
|
|
318
|
+
* custom: (context) => {
|
|
319
|
+
* const exitIntent = context.triggers?.exitIntent?.triggered;
|
|
320
|
+
* const scrolled = (context.triggers?.scrollDepth?.percent || 0) >= 70;
|
|
321
|
+
* return exitIntent || scrolled;
|
|
322
|
+
* }
|
|
323
|
+
* }
|
|
324
|
+
* });
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
declare const exitIntentPlugin: PluginFunction;
|
|
328
|
+
|
|
195
329
|
/**
|
|
196
330
|
* Frequency Capping Plugin
|
|
197
331
|
*
|
|
@@ -227,4 +361,477 @@ interface FrequencyPlugin {
|
|
|
227
361
|
*/
|
|
228
362
|
declare const frequencyPlugin: PluginFunction;
|
|
229
363
|
|
|
230
|
-
|
|
364
|
+
/**
|
|
365
|
+
* Page Visits Plugin Types
|
|
366
|
+
*
|
|
367
|
+
* Generic page visit tracking for any SDK built on sdk-kit.
|
|
368
|
+
* Tracks session and lifetime visit counts with first-visit detection.
|
|
369
|
+
*/
|
|
370
|
+
/**
|
|
371
|
+
* Page visits plugin configuration
|
|
372
|
+
*/
|
|
373
|
+
interface PageVisitsPluginConfig {
|
|
374
|
+
pageVisits?: {
|
|
375
|
+
/**
|
|
376
|
+
* Enable/disable page visit tracking
|
|
377
|
+
* @default true
|
|
378
|
+
*/
|
|
379
|
+
enabled?: boolean;
|
|
380
|
+
/**
|
|
381
|
+
* Honor Do Not Track browser setting
|
|
382
|
+
* @default true
|
|
383
|
+
*/
|
|
384
|
+
respectDNT?: boolean;
|
|
385
|
+
/**
|
|
386
|
+
* Storage key for session count
|
|
387
|
+
* @default 'pageVisits:session'
|
|
388
|
+
*/
|
|
389
|
+
sessionKey?: string;
|
|
390
|
+
/**
|
|
391
|
+
* Storage key for lifetime data
|
|
392
|
+
* @default 'pageVisits:total'
|
|
393
|
+
*/
|
|
394
|
+
totalKey?: string;
|
|
395
|
+
/**
|
|
396
|
+
* TTL for lifetime data in seconds (GDPR compliance)
|
|
397
|
+
* @default undefined (no expiration)
|
|
398
|
+
*/
|
|
399
|
+
ttl?: number;
|
|
400
|
+
/**
|
|
401
|
+
* Automatically increment on plugin load
|
|
402
|
+
* @default true
|
|
403
|
+
*/
|
|
404
|
+
autoIncrement?: boolean;
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Page visits event payload
|
|
409
|
+
*/
|
|
410
|
+
interface PageVisitsEvent {
|
|
411
|
+
/** Whether this is the user's first visit ever */
|
|
412
|
+
isFirstVisit: boolean;
|
|
413
|
+
/** Total visits across all sessions (lifetime) */
|
|
414
|
+
totalVisits: number;
|
|
415
|
+
/** Visits in current session */
|
|
416
|
+
sessionVisits: number;
|
|
417
|
+
/** Timestamp of first visit (unix ms) */
|
|
418
|
+
firstVisitTime?: number;
|
|
419
|
+
/** Timestamp of last visit (unix ms) */
|
|
420
|
+
lastVisitTime?: number;
|
|
421
|
+
/** Timestamp of current visit (unix ms) */
|
|
422
|
+
timestamp: number;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Page visits plugin API
|
|
426
|
+
*/
|
|
427
|
+
interface PageVisitsPlugin {
|
|
428
|
+
/**
|
|
429
|
+
* Get total visit count (lifetime)
|
|
430
|
+
*/
|
|
431
|
+
getTotalCount(): number;
|
|
432
|
+
/**
|
|
433
|
+
* Get session visit count
|
|
434
|
+
*/
|
|
435
|
+
getSessionCount(): number;
|
|
436
|
+
/**
|
|
437
|
+
* Check if this is the first visit
|
|
438
|
+
*/
|
|
439
|
+
isFirstVisit(): boolean;
|
|
440
|
+
/**
|
|
441
|
+
* Get timestamp of first visit
|
|
442
|
+
*/
|
|
443
|
+
getFirstVisitTime(): number | undefined;
|
|
444
|
+
/**
|
|
445
|
+
* Get timestamp of last visit
|
|
446
|
+
*/
|
|
447
|
+
getLastVisitTime(): number | undefined;
|
|
448
|
+
/**
|
|
449
|
+
* Manually increment page visit
|
|
450
|
+
* (useful if autoIncrement is disabled)
|
|
451
|
+
*/
|
|
452
|
+
increment(): void;
|
|
453
|
+
/**
|
|
454
|
+
* Reset all counters and data
|
|
455
|
+
* (useful for testing or user opt-out)
|
|
456
|
+
*/
|
|
457
|
+
reset(): void;
|
|
458
|
+
/**
|
|
459
|
+
* Get full page visits state
|
|
460
|
+
*/
|
|
461
|
+
getState(): PageVisitsEvent;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Page Visits Plugin
|
|
466
|
+
*
|
|
467
|
+
* Generic page visit tracking for any SDK built on sdk-kit.
|
|
468
|
+
*
|
|
469
|
+
* Features:
|
|
470
|
+
* - Session-scoped counter (sessionStorage)
|
|
471
|
+
* - Lifetime counter with timestamps (localStorage)
|
|
472
|
+
* - First-visit detection
|
|
473
|
+
* - DNT (Do Not Track) support
|
|
474
|
+
* - GDPR-compliant expiration
|
|
475
|
+
* - Auto-loads storage plugin if missing
|
|
476
|
+
*
|
|
477
|
+
* Events emitted:
|
|
478
|
+
* - 'pageVisits:incremented' with PageVisitsEvent
|
|
479
|
+
* - 'pageVisits:reset'
|
|
480
|
+
* - 'pageVisits:disabled' with { reason: 'dnt' | 'config' }
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* import { SDK } from '@lytics/sdk-kit';
|
|
485
|
+
* import { storagePlugin, pageVisitsPlugin } from '@lytics/sdk-kit-plugins';
|
|
486
|
+
*
|
|
487
|
+
* const sdk = new SDK({
|
|
488
|
+
* pageVisits: {
|
|
489
|
+
* enabled: true,
|
|
490
|
+
* respectDNT: true,
|
|
491
|
+
* ttl: 31536000 // 1 year
|
|
492
|
+
* }
|
|
493
|
+
* });
|
|
494
|
+
*
|
|
495
|
+
* sdk.use(storagePlugin);
|
|
496
|
+
* sdk.use(pageVisitsPlugin);
|
|
497
|
+
*
|
|
498
|
+
* // Listen to visit events
|
|
499
|
+
* sdk.on('pageVisits:incremented', (event) => {
|
|
500
|
+
* console.log('Visit count:', event.totalVisits);
|
|
501
|
+
* if (event.isFirstVisit) {
|
|
502
|
+
* console.log('Welcome, first-time visitor!');
|
|
503
|
+
* }
|
|
504
|
+
* });
|
|
505
|
+
*
|
|
506
|
+
* // API methods
|
|
507
|
+
* console.log(sdk.pageVisits.getTotalCount()); // 5
|
|
508
|
+
* console.log(sdk.pageVisits.getSessionCount()); // 2
|
|
509
|
+
* console.log(sdk.pageVisits.isFirstVisit()); // false
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
declare const pageVisitsPlugin: PluginFunction;
|
|
514
|
+
|
|
515
|
+
/** @module scrollDepthPlugin */
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Scroll Depth Plugin
|
|
519
|
+
*
|
|
520
|
+
* Tracks scroll depth and emits `trigger:scrollDepth` events when thresholds are crossed.
|
|
521
|
+
*
|
|
522
|
+
* ## How It Works
|
|
523
|
+
*
|
|
524
|
+
* 1. **Detection**: Listens to `scroll` events (throttled)
|
|
525
|
+
* 2. **Calculation**: Calculates current scroll percentage
|
|
526
|
+
* 3. **Tracking**: Tracks maximum scroll depth and threshold crossings
|
|
527
|
+
* 4. **Emission**: Emits `trigger:scrollDepth` events when thresholds are crossed
|
|
528
|
+
*
|
|
529
|
+
* ## Configuration
|
|
530
|
+
*
|
|
531
|
+
* ```typescript
|
|
532
|
+
* init({
|
|
533
|
+
* scrollDepth: {
|
|
534
|
+
* thresholds: [25, 50, 75, 100], // Percentages to track
|
|
535
|
+
* throttle: 100, // Throttle interval (ms)
|
|
536
|
+
* includeViewportHeight: true, // Calculation method
|
|
537
|
+
* recalculateOnResize: true // Recalculate on resize
|
|
538
|
+
* }
|
|
539
|
+
* });
|
|
540
|
+
* ```
|
|
541
|
+
*
|
|
542
|
+
* ## Experience Targeting
|
|
543
|
+
*
|
|
544
|
+
* ```typescript
|
|
545
|
+
* register('mid-article-cta', {
|
|
546
|
+
* type: 'banner',
|
|
547
|
+
* content: { message: 'Enjoying the article?' },
|
|
548
|
+
* targeting: {
|
|
549
|
+
* custom: (ctx) => (ctx.triggers?.scrollDepth?.percent || 0) >= 50
|
|
550
|
+
* }
|
|
551
|
+
* });
|
|
552
|
+
* ```
|
|
553
|
+
*
|
|
554
|
+
* ## API Methods
|
|
555
|
+
*
|
|
556
|
+
* ```typescript
|
|
557
|
+
* // Get maximum scroll percentage reached
|
|
558
|
+
* instance.scrollDepth.getMaxPercent(); // 73
|
|
559
|
+
*
|
|
560
|
+
* // Get current scroll percentage
|
|
561
|
+
* instance.scrollDepth.getCurrentPercent(); // 50
|
|
562
|
+
*
|
|
563
|
+
* // Get all crossed thresholds
|
|
564
|
+
* instance.scrollDepth.getThresholdsCrossed(); // [25, 50]
|
|
565
|
+
*
|
|
566
|
+
* // Reset tracking (useful for testing)
|
|
567
|
+
* instance.scrollDepth.reset();
|
|
568
|
+
* ```
|
|
569
|
+
*
|
|
570
|
+
* @param plugin Plugin interface from sdk-kit
|
|
571
|
+
* @param instance SDK instance
|
|
572
|
+
* @param config SDK configuration
|
|
573
|
+
*/
|
|
574
|
+
declare const scrollDepthPlugin: PluginFunction;
|
|
575
|
+
|
|
576
|
+
/** @module scrollDepthPlugin */
|
|
577
|
+
/**
|
|
578
|
+
* Scroll Depth Plugin API
|
|
579
|
+
*/
|
|
580
|
+
interface ScrollDepthPlugin {
|
|
581
|
+
getMaxPercent(): number;
|
|
582
|
+
getCurrentPercent(): number;
|
|
583
|
+
getThresholdsCrossed(): number[];
|
|
584
|
+
getDevice(): 'mobile' | 'tablet' | 'desktop';
|
|
585
|
+
getAdvancedMetrics(): {
|
|
586
|
+
timeOnPage: number;
|
|
587
|
+
directionChanges: number;
|
|
588
|
+
timeScrollingUp: number;
|
|
589
|
+
thresholdTimes: Record<number, number>;
|
|
590
|
+
} | null;
|
|
591
|
+
reset(): void;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Scroll Depth Plugin Configuration
|
|
595
|
+
*
|
|
596
|
+
* Tracks scroll depth and emits trigger:scrollDepth events when thresholds are crossed.
|
|
597
|
+
*/
|
|
598
|
+
interface ScrollDepthPluginConfig {
|
|
599
|
+
scrollDepth?: {
|
|
600
|
+
/**
|
|
601
|
+
* Array of scroll percentage thresholds to track (0-100).
|
|
602
|
+
* When user scrolls past a threshold, a trigger:scrollDepth event is emitted.
|
|
603
|
+
* @default [25, 50, 75, 100]
|
|
604
|
+
* @example [50, 100]
|
|
605
|
+
*/
|
|
606
|
+
thresholds?: number[];
|
|
607
|
+
/**
|
|
608
|
+
* Throttle interval in milliseconds for scroll event handler.
|
|
609
|
+
* Lower values are more responsive but impact performance.
|
|
610
|
+
* @default 100
|
|
611
|
+
* @example 200
|
|
612
|
+
*/
|
|
613
|
+
throttle?: number;
|
|
614
|
+
/**
|
|
615
|
+
* Include viewport height in scroll percentage calculation.
|
|
616
|
+
*
|
|
617
|
+
* - true: (scrollTop + viewportHeight) / totalHeight
|
|
618
|
+
* More intuitive: 100% when bottom of viewport reaches end
|
|
619
|
+
* - false: scrollTop / (totalHeight - viewportHeight)
|
|
620
|
+
* Pathfora's method: 100% when top of viewport reaches end
|
|
621
|
+
*
|
|
622
|
+
* @default true
|
|
623
|
+
*/
|
|
624
|
+
includeViewportHeight?: boolean;
|
|
625
|
+
/**
|
|
626
|
+
* Recalculate scroll on window resize.
|
|
627
|
+
* Useful for responsive layouts where content height changes.
|
|
628
|
+
* @default true
|
|
629
|
+
*/
|
|
630
|
+
recalculateOnResize?: boolean;
|
|
631
|
+
/**
|
|
632
|
+
* Track advanced metrics (velocity, direction, time-to-threshold).
|
|
633
|
+
* Enables advanced engagement quality analysis.
|
|
634
|
+
* Slight performance overhead but provides rich insights.
|
|
635
|
+
* @default false
|
|
636
|
+
*/
|
|
637
|
+
trackAdvancedMetrics?: boolean;
|
|
638
|
+
/**
|
|
639
|
+
* Velocity threshold (px/ms) to consider "fast scrolling".
|
|
640
|
+
* Fast scrolling often indicates skimming rather than reading.
|
|
641
|
+
* Only used when trackAdvancedMetrics is true.
|
|
642
|
+
* @default 3
|
|
643
|
+
*/
|
|
644
|
+
fastScrollVelocityThreshold?: number;
|
|
645
|
+
/**
|
|
646
|
+
* Disable scroll tracking on mobile devices.
|
|
647
|
+
* Useful since mobile scroll behavior differs significantly from desktop.
|
|
648
|
+
* @default false
|
|
649
|
+
*/
|
|
650
|
+
disableOnMobile?: boolean;
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Scroll Depth Event Payload
|
|
655
|
+
*
|
|
656
|
+
* Emitted as `trigger:scrollDepth` when a threshold is crossed.
|
|
657
|
+
*/
|
|
658
|
+
interface ScrollDepthEvent {
|
|
659
|
+
/** Whether the trigger has fired */
|
|
660
|
+
triggered: boolean;
|
|
661
|
+
/** Timestamp when the event was emitted */
|
|
662
|
+
timestamp: number;
|
|
663
|
+
/** Current scroll percentage (0-100) */
|
|
664
|
+
percent: number;
|
|
665
|
+
/** Maximum scroll percentage reached during session */
|
|
666
|
+
maxPercent: number;
|
|
667
|
+
/** The threshold that was just crossed */
|
|
668
|
+
threshold: number;
|
|
669
|
+
/** All thresholds that have been triggered */
|
|
670
|
+
thresholdsCrossed: number[];
|
|
671
|
+
/** Device type (mobile, tablet, desktop) */
|
|
672
|
+
device: 'mobile' | 'tablet' | 'desktop';
|
|
673
|
+
/** Advanced metrics (only present when trackAdvancedMetrics is enabled) */
|
|
674
|
+
advanced?: {
|
|
675
|
+
/** Time in milliseconds to reach this threshold from page load */
|
|
676
|
+
timeToThreshold: number;
|
|
677
|
+
/** Current scroll velocity in pixels per millisecond */
|
|
678
|
+
velocity: number;
|
|
679
|
+
/** Whether user is scrolling fast (indicates skimming) */
|
|
680
|
+
isFastScrolling: boolean;
|
|
681
|
+
/** Number of direction changes (up/down) since last threshold */
|
|
682
|
+
directionChanges: number;
|
|
683
|
+
/** Total time spent scrolling up (indicates seeking behavior) */
|
|
684
|
+
timeScrollingUp: number;
|
|
685
|
+
/** Scroll quality score (0-100, higher = more engaged) */
|
|
686
|
+
engagementScore: number;
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** @module timeDelayPlugin */
|
|
691
|
+
/**
|
|
692
|
+
* Time Delay Plugin Configuration
|
|
693
|
+
*
|
|
694
|
+
* Tracks time elapsed since SDK initialization and emits trigger:timeDelay events.
|
|
695
|
+
*/
|
|
696
|
+
interface TimeDelayPluginConfig {
|
|
697
|
+
timeDelay?: {
|
|
698
|
+
/**
|
|
699
|
+
* Delay before emitting trigger event (milliseconds).
|
|
700
|
+
* Set to 0 to disable (immediate trigger on init).
|
|
701
|
+
* @default 0
|
|
702
|
+
* @example 5000 // 5 seconds
|
|
703
|
+
*/
|
|
704
|
+
delay?: number;
|
|
705
|
+
/**
|
|
706
|
+
* Pause timer when tab is hidden (Page Visibility API).
|
|
707
|
+
* When true, only counts "active viewing time".
|
|
708
|
+
* When false, timer runs even when tab is hidden.
|
|
709
|
+
* @default true
|
|
710
|
+
*/
|
|
711
|
+
pauseWhenHidden?: boolean;
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Time Delay Event Payload
|
|
716
|
+
*
|
|
717
|
+
* Emitted via 'trigger:timeDelay' when the configured delay is reached.
|
|
718
|
+
*/
|
|
719
|
+
interface TimeDelayEvent {
|
|
720
|
+
/** Timestamp when the trigger event was emitted */
|
|
721
|
+
timestamp: number;
|
|
722
|
+
/** Total elapsed time since init (milliseconds, includes paused time) */
|
|
723
|
+
elapsed: number;
|
|
724
|
+
/** Active elapsed time (milliseconds, excludes time when tab was hidden) */
|
|
725
|
+
activeElapsed: number;
|
|
726
|
+
/** Whether the timer was paused at any point */
|
|
727
|
+
wasPaused: boolean;
|
|
728
|
+
/** Number of times visibility changed (hidden/visible) */
|
|
729
|
+
visibilityChanges: number;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Time Delay Plugin API
|
|
733
|
+
*/
|
|
734
|
+
interface TimeDelayPlugin {
|
|
735
|
+
/**
|
|
736
|
+
* Get total elapsed time since init (includes paused time)
|
|
737
|
+
* @returns Time in milliseconds
|
|
738
|
+
*/
|
|
739
|
+
getElapsed(): number;
|
|
740
|
+
/**
|
|
741
|
+
* Get active elapsed time (excludes paused time)
|
|
742
|
+
* @returns Time in milliseconds
|
|
743
|
+
*/
|
|
744
|
+
getActiveElapsed(): number;
|
|
745
|
+
/**
|
|
746
|
+
* Get remaining time until trigger
|
|
747
|
+
* @returns Time in milliseconds, or 0 if already triggered
|
|
748
|
+
*/
|
|
749
|
+
getRemaining(): number;
|
|
750
|
+
/**
|
|
751
|
+
* Check if timer is currently paused (tab hidden)
|
|
752
|
+
* @returns True if paused
|
|
753
|
+
*/
|
|
754
|
+
isPaused(): boolean;
|
|
755
|
+
/**
|
|
756
|
+
* Check if trigger has fired
|
|
757
|
+
* @returns True if triggered
|
|
758
|
+
*/
|
|
759
|
+
isTriggered(): boolean;
|
|
760
|
+
/**
|
|
761
|
+
* Reset timer to initial state
|
|
762
|
+
* Clears trigger flag and restarts timing
|
|
763
|
+
*/
|
|
764
|
+
reset(): void;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** @module timeDelayPlugin */
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Time Delay Plugin
|
|
771
|
+
*
|
|
772
|
+
* Tracks time elapsed since SDK initialization and emits trigger:timeDelay events
|
|
773
|
+
* when the configured delay is reached.
|
|
774
|
+
*
|
|
775
|
+
* **Features:**
|
|
776
|
+
* - Millisecond precision timing
|
|
777
|
+
* - Pause/resume on tab visibility change (optional)
|
|
778
|
+
* - Tracks active vs total elapsed time
|
|
779
|
+
* - Full timer lifecycle management
|
|
780
|
+
*
|
|
781
|
+
* **Event-Driven Architecture:**
|
|
782
|
+
* This plugin emits `trigger:timeDelay` events when the delay threshold is reached.
|
|
783
|
+
* The core runtime listens for these events and automatically re-evaluates experiences.
|
|
784
|
+
*
|
|
785
|
+
* **Usage Pattern:**
|
|
786
|
+
* Use `targeting.custom` to check if time delay has triggered:
|
|
787
|
+
*
|
|
788
|
+
* @example Basic usage
|
|
789
|
+
* ```typescript
|
|
790
|
+
* import { init, register } from '@prosdevlab/experience-sdk';
|
|
791
|
+
*
|
|
792
|
+
* init({
|
|
793
|
+
* timeDelay: {
|
|
794
|
+
* delay: 5000, // 5 seconds
|
|
795
|
+
* pauseWhenHidden: true // Pause when tab hidden (default)
|
|
796
|
+
* }
|
|
797
|
+
* });
|
|
798
|
+
*
|
|
799
|
+
* // Show banner after 5 seconds of active viewing time
|
|
800
|
+
* register('timed-offer', {
|
|
801
|
+
* type: 'banner',
|
|
802
|
+
* content: {
|
|
803
|
+
* message: 'Limited time offer!',
|
|
804
|
+
* buttons: [{ text: 'Claim Now', variant: 'primary' }]
|
|
805
|
+
* },
|
|
806
|
+
* targeting: {
|
|
807
|
+
* custom: (context) => {
|
|
808
|
+
* const active = context.triggers?.timeDelay?.activeElapsed || 0;
|
|
809
|
+
* return active >= 5000;
|
|
810
|
+
* }
|
|
811
|
+
* }
|
|
812
|
+
* });
|
|
813
|
+
* ```
|
|
814
|
+
*
|
|
815
|
+
* @example Combining with other triggers
|
|
816
|
+
* ```typescript
|
|
817
|
+
* // Show after 10s OR on exit intent (whichever comes first)
|
|
818
|
+
* register('engaged-offer', {
|
|
819
|
+
* type: 'banner',
|
|
820
|
+
* content: { message: 'Special offer for engaged users!' },
|
|
821
|
+
* targeting: {
|
|
822
|
+
* custom: (context) => {
|
|
823
|
+
* const timeElapsed = (context.triggers?.timeDelay?.activeElapsed || 0) >= 10000;
|
|
824
|
+
* const exitIntent = context.triggers?.exitIntent?.triggered;
|
|
825
|
+
* return timeElapsed || exitIntent;
|
|
826
|
+
* }
|
|
827
|
+
* }
|
|
828
|
+
* });
|
|
829
|
+
* ```
|
|
830
|
+
*
|
|
831
|
+
* @param plugin Plugin interface from sdk-kit
|
|
832
|
+
* @param instance SDK instance
|
|
833
|
+
* @param config SDK configuration
|
|
834
|
+
*/
|
|
835
|
+
declare const timeDelayPlugin: PluginFunction;
|
|
836
|
+
|
|
837
|
+
export { type BannerContent, type BannerPlugin, type BannerPluginConfig, type DebugPlugin, type DebugPluginConfig, type Decision, type DecisionMetadata, type ExitIntentEvent, type ExitIntentPlugin, type ExitIntentPluginConfig, type Experience, type ExperienceContent, type FrequencyPlugin, type FrequencyPluginConfig, type ModalContent, type PageVisitsEvent, type PageVisitsPlugin, type PageVisitsPluginConfig, type ScrollDepthEvent, type ScrollDepthPlugin, type ScrollDepthPluginConfig, type TimeDelayEvent, type TimeDelayPlugin, type TimeDelayPluginConfig, type TooltipContent, type TraceStep, bannerPlugin, debugPlugin, exitIntentPlugin, frequencyPlugin, pageVisitsPlugin, scrollDepthPlugin, timeDelayPlugin };
|