js.foresight 3.5.0 → 4.1.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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -46
  3. package/dist/BaseForesightModule-CJzWVlF2.mjs +2 -0
  4. package/dist/BaseForesightModule-CJzWVlF2.mjs.map +1 -0
  5. package/dist/DesktopHandler-CR9nLuzu.mjs +2 -0
  6. package/dist/DesktopHandler-CR9nLuzu.mjs.map +1 -0
  7. package/dist/ElementObservingModule-QE8ZBcnz.mjs +2 -0
  8. package/dist/ElementObservingModule-QE8ZBcnz.mjs.map +1 -0
  9. package/dist/ScrollPredictor-ZHRqX0lh.mjs +2 -0
  10. package/dist/ScrollPredictor-ZHRqX0lh.mjs.map +1 -0
  11. package/dist/TabPredictor-C2zyaEQZ.mjs +2 -0
  12. package/dist/TabPredictor-C2zyaEQZ.mjs.map +1 -0
  13. package/dist/TouchDeviceHandler-BPzPdr-z.mjs +2 -0
  14. package/dist/TouchDeviceHandler-BPzPdr-z.mjs.map +1 -0
  15. package/dist/TouchStartPredictor-r7R_E74t.mjs +2 -0
  16. package/dist/TouchStartPredictor-r7R_E74t.mjs.map +1 -0
  17. package/dist/ViewportPredictor-aTE2EmU2.mjs +2 -0
  18. package/dist/ViewportPredictor-aTE2EmU2.mjs.map +1 -0
  19. package/dist/index.d.mts +594 -0
  20. package/dist/index.d.mts.map +1 -0
  21. package/dist/index.mjs +2 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/lineSegmentIntersectsRect-x9nicliA.mjs +2 -0
  24. package/dist/lineSegmentIntersectsRect-x9nicliA.mjs.map +1 -0
  25. package/dist/rectAndHitSlop-T7Z3PZlb.mjs +2 -0
  26. package/dist/rectAndHitSlop-T7Z3PZlb.mjs.map +1 -0
  27. package/package.json +27 -28
  28. package/dist/DesktopHandler-BOXAW4XX.js +0 -1
  29. package/dist/ScrollPredictor-Y7NELMBI.js +0 -1
  30. package/dist/TabPredictor-HA2SV3CY.js +0 -1
  31. package/dist/TouchDeviceHandler-JWBQ2YOV.js +0 -1
  32. package/dist/TouchStartPredictor-ZH3KJG2C.js +0 -1
  33. package/dist/ViewportPredictor-H3GLDETY.js +0 -1
  34. package/dist/chunk-44N4MCQB.js +0 -1
  35. package/dist/chunk-AODZNE3S.js +0 -1
  36. package/dist/chunk-PAYO6NXN.js +0 -1
  37. package/dist/index.d.ts +0 -531
  38. package/dist/index.js +0 -1
@@ -0,0 +1,594 @@
1
+ //#region src/helpers/CircularBuffer.d.ts
2
+ declare class CircularBuffer<T> {
3
+ private buffer;
4
+ private head;
5
+ private count;
6
+ private capacity;
7
+ constructor(capacity: number);
8
+ add(item: T): void;
9
+ getFirst(): T | undefined;
10
+ getLast(): T | undefined;
11
+ getFirstLast(): [T | undefined, T | undefined];
12
+ resize(newCapacity: number): void;
13
+ private getAllItems;
14
+ clear(): void;
15
+ get length(): number;
16
+ get size(): number;
17
+ get isFull(): boolean;
18
+ get isEmpty(): boolean;
19
+ }
20
+ //#endregion
21
+ //#region src/types/types.d.ts
22
+ type Rect = {
23
+ top: number;
24
+ left: number;
25
+ right: number;
26
+ bottom: number;
27
+ };
28
+ /**
29
+ * A callback function that is executed when a foresight interaction
30
+ * (e.g., hover, trajectory hit) occurs on a registered element.
31
+ */
32
+ type ForesightCallback = (state: ForesightElementState) => void;
33
+ /**
34
+ * Represents the HTML element that is being tracked by the ForesightManager.
35
+ * This is typically a standard DOM `Element`.
36
+ */
37
+ type ForesightElement = Element;
38
+ /**
39
+ * Represents a mouse position captured at a specific point in time.
40
+ * Used for tracking mouse movement history.
41
+ */
42
+ type MousePosition = {
43
+ /** The (x, y) coordinates of the mouse. */point: Point; /** The timestamp (e.g., from `performance.now()`) when this position was recorded. */
44
+ time: number;
45
+ };
46
+ type Point = {
47
+ x: number;
48
+ y: number;
49
+ };
50
+ type TrajectoryPositions = {
51
+ positions: CircularBuffer<MousePosition>;
52
+ currentPoint: Point;
53
+ predictedPoint: Point;
54
+ };
55
+ /**
56
+ * Immutable geometry snapshot for a registered element. Replaced (never mutated)
57
+ * whenever the element's position or size changes, which happens on every
58
+ * scroll/resize tick for visible elements.
59
+ */
60
+ type ElementBounds = {
61
+ /** The expanded rectangle, including hitSlop, used for interaction detection. */expandedRect: Readonly<Rect>; /** The original bounding rectangle of the element, as returned by `getBoundingClientRect()`. */
62
+ originalRect: DOMRectReadOnly;
63
+ };
64
+ /**
65
+ * Immutable, flat state snapshot for a registered element.
66
+ * The reference is replaced (never mutated) on every change so it can be used
67
+ * with `useSyncExternalStore` and Vue's `shallowRef`.
68
+ */
69
+ type ForesightElementState = {
70
+ /** Unique identifier assigned during registration. */id: string; /** Human-readable name for debugging. */
71
+ name: string; /** Arbitrary user-supplied metadata. */
72
+ meta: Record<string, unknown>;
73
+ /** The normalized hit slop applied to this element. The element's rects live in
74
+ * {@link ElementBounds} (see `getBounds`/`subscribeToBounds`), not in this snapshot. */
75
+ hitSlop: Exclude<HitSlop, number>; /** Whether the user has connection limitations (network slower than minimum connection type (default: 3g) or data saver enabled) that prevent prefetching. */
76
+ isLimitedConnection: boolean; /** Whether the element is currently intersecting the viewport. */
77
+ isIntersectingWithViewport: boolean;
78
+ /** Whether the element is currently tracked by the manager. Stays `true` from
79
+ * registration until it is explicitly unregistered (via the returned `unregister`).
80
+ * Detaching the element from the DOM does NOT unregister it - it is parked inactive
81
+ * and resumes when it reconnects. On a limited connection it is also registered but
82
+ * inactive - check `isLimitedConnection` / `isActive`. */
83
+ isRegistered: boolean;
84
+ /** Whether the element is currently eligible to fire its callback. False when the
85
+ * element is disabled (`isEnabled: false`), on a limited connection, or temporarily
86
+ * detached from the DOM (see `isParked`). */
87
+ isActive: boolean;
88
+ /** Whether the element is detached from the DOM and parked: kept registered but
89
+ * inactive until it reconnects, at which point it resumes automatically. */
90
+ isParked: boolean;
91
+ /** Whether prediction is enabled for this element. When `false` the element
92
+ * stays registered but inactive. Note: an enabled element is still inactive on a
93
+ * limited connection (see `isLimitedConnection`). */
94
+ isEnabled: boolean; /** True once the element's callback has been triggered by a prediction hit. Stays true until the element is reactivated or unregistered. */
95
+ isPredicted: boolean; /** True while the callback is executing (between invocation and completion). The callback is awaited, so this stays true for async callbacks until they resolve or reject. */
96
+ isCallbackRunning: boolean; /** Number of times the callback has fired for this element. */
97
+ hitCount: number; /** Number of times this element has been (re)registered. */
98
+ registerCount: number; /** Duration in ms of the most recent callback run. */
99
+ durationMs: number | undefined; /** Status of the most recently completed callback. */
100
+ status: callbackStatus; /** Error message from the most recently completed callback. */
101
+ error: string | null; /** Time in ms after which the callback can be fired again (Infinity = never). */
102
+ reactivateAfter: number;
103
+ };
104
+ type ForesightRegisterResult = ForesightElementState & {
105
+ /** Function to unregister the element
106
+ */
107
+ unregister: () => void;
108
+ /**
109
+ * Subscribe to logical state changes for this element. Never fires for
110
+ * geometry-only changes (scroll/resize), use `subscribeToBounds` for those.
111
+ * Returns an unsubscribe function.
112
+ */
113
+ subscribe: (listener: () => void) => () => void;
114
+ /**
115
+ * Returns the current immutable state snapshot for this element.
116
+ * The reference only changes when logical state changes - never on scroll.
117
+ */
118
+ getSnapshot: () => ForesightElementState;
119
+ /**
120
+ * Subscribe to geometry changes for this element (position/size, fired on
121
+ * every scroll/resize tick while visible). Returns an unsubscribe function.
122
+ */
123
+ subscribeToBounds: (listener: () => void) => () => void;
124
+ /**
125
+ * Returns the current immutable geometry snapshot for this element.
126
+ */
127
+ getBounds: () => ElementBounds;
128
+ };
129
+ type callbackStatus = "error" | "success" | undefined;
130
+ type MouseCallbackCounts = {
131
+ hover: number;
132
+ trajectory: number;
133
+ };
134
+ type TabCallbackCounts = {
135
+ reverse: number;
136
+ forwards: number;
137
+ };
138
+ type ScrollDirection = "down" | "up" | "left" | "right" | "none";
139
+ type ScrollCallbackCounts = Record<`${Exclude<ScrollDirection, "none">}`, number>;
140
+ type CallbackHits = {
141
+ total: number;
142
+ mouse: MouseCallbackCounts;
143
+ tab: TabCallbackCounts;
144
+ scroll: ScrollCallbackCounts;
145
+ touch: number;
146
+ viewport: number;
147
+ };
148
+ type CallbackHitType = {
149
+ kind: "mouse";
150
+ subType: keyof MouseCallbackCounts;
151
+ } | {
152
+ kind: "tab";
153
+ subType: keyof TabCallbackCounts;
154
+ } | {
155
+ kind: "scroll";
156
+ subType: keyof ScrollCallbackCounts;
157
+ } | {
158
+ kind: "touch";
159
+ subType?: string;
160
+ } | {
161
+ kind: "viewport";
162
+ subType?: string;
163
+ };
164
+ /**
165
+ * Snapshot of the current ForesightManager state
166
+ */
167
+ type ForesightManagerData = {
168
+ registeredElements: ReadonlyMap<ForesightElement, ForesightElementState>;
169
+ globalSettings: Readonly<ForesightManagerSettings>;
170
+ globalCallbackHits: Readonly<CallbackHits>;
171
+ eventListeners: ReadonlyMap<keyof ForesightEventMap, ForesightEventListener[]>;
172
+ currentDeviceStrategy: CurrentDeviceStrategy;
173
+ activeElementCount: number;
174
+ parkedElementCount: number;
175
+ loadedModules: ForesightModules;
176
+ };
177
+ type ForesightModules = {
178
+ desktopHandler: boolean;
179
+ touchHandler: boolean;
180
+ predictors: {
181
+ mouse: boolean;
182
+ tab: boolean;
183
+ scroll: boolean;
184
+ viewport: boolean;
185
+ touchStart: boolean;
186
+ };
187
+ };
188
+ type TouchDeviceStrategy = "none" | "viewport" | "onTouchStart";
189
+ type MinimumConnectionType = "slow-2g" | "2g" | "3g" | "4g";
190
+ type BaseForesightManagerSettings = {
191
+ /**
192
+ * Number of mouse positions to keep in history for trajectory calculation.
193
+ * A higher number might lead to smoother but slightly delayed predictions.
194
+ *
195
+ *
196
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
197
+ *
198
+ *
199
+ * **This value is clamped between 2 and 30.**
200
+ * @default 8
201
+ */
202
+ positionHistorySize: number;
203
+ /**
204
+ *
205
+ * Logs basic information about the ForesightManager and its handlers that doesn't have a dedicated event.
206
+ *
207
+ * Mostly used by the maintainers of ForesightJS to debug the manager. But might be useful for implementers aswell.
208
+ *
209
+ * This is not the same as logging events, this can be done with the actual developer tools.
210
+ * @link https://foresightjs.com/docs/debugging/devtools
211
+ */
212
+ enableManagerLogging: boolean;
213
+ /**
214
+ * How far ahead (in milliseconds) to predict the mouse trajectory.
215
+ * A larger value means the prediction extends further into the future. (meaning it will trigger callbacks sooner)
216
+ *
217
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
218
+ *
219
+ * **This value is clamped between 10 and 200.**
220
+ * @default 120
221
+ */
222
+ trajectoryPredictionTime: number;
223
+ /**
224
+ * Whether to enable mouse trajectory prediction.
225
+ * If false, only direct hover/interaction is considered.
226
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
227
+ * @default true
228
+ */
229
+ enableMousePrediction: boolean;
230
+ /**
231
+ * Toggles whether keyboard prediction is on
232
+ *
233
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
234
+ * @default true
235
+ */
236
+ enableTabPrediction: boolean;
237
+ /**
238
+ * Sets the pixel distance to check from the mouse position in the scroll direction.
239
+ *
240
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
241
+ *
242
+ * **This value is clamped between 30 and 300.**
243
+ * @default 150
244
+ */
245
+ scrollMargin: number;
246
+ /**
247
+ * Toggles whether scroll prediction is on
248
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
249
+ * @default true
250
+ */
251
+ enableScrollPrediction: boolean;
252
+ /**
253
+ * Tab stops away from an element to trigger callback. Only works when @argument enableTabPrediction is true
254
+ *
255
+ * **This value is clamped between 0 and 20.**
256
+ * @default 2
257
+ */
258
+ tabOffset: number;
259
+ /**
260
+ * The prefetch strategy used for touch devices.
261
+ * - `viewport`: Prefetching is done based on the viewport, meaning elements in the viewport are preloaded.
262
+ * - `onTouchStart`: Prefetching is done when the user touches the element
263
+ * @default onTouchStart
264
+ */
265
+ touchDeviceStrategy: TouchDeviceStrategy;
266
+ /**
267
+ * Network effective connection types that should be considered as limited connections.
268
+ * When the user's network matches any of these types, ForesightJS will disable prefetching
269
+ * to avoid consuming data on slow or expensive connections.
270
+ *
271
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType
272
+ * @default 3g
273
+ */
274
+ minimumConnectionType: MinimumConnectionType;
275
+ };
276
+ type CurrentDeviceStrategy = "mouse" | "touch" | "pen";
277
+ /**
278
+ * Configuration options for the ForesightManager
279
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
280
+ */
281
+ type ForesightManagerSettings = BaseForesightManagerSettings & {
282
+ defaultHitSlop: Exclude<HitSlop, number>;
283
+ };
284
+ /**
285
+ * Update options for the ForesightManager
286
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
287
+ */
288
+ type UpdateForsightManagerSettings = BaseForesightManagerSettings & {
289
+ defaultHitSlop: HitSlop;
290
+ };
291
+ /**
292
+ * Type used to register elements to the foresight manager
293
+ */
294
+ type ForesightRegisterOptions = ForesightRegisterOptionsWithoutElement & {
295
+ element: ForesightElement;
296
+ };
297
+ type ForesightRegisterNodeListOptions = ForesightRegisterOptionsWithoutElement & {
298
+ element: NodeListOf<ForesightElement>;
299
+ };
300
+ /**
301
+ * Use full for if you want to create a custom button component in a modern framework (for example React).
302
+ * And you want to have the ForesightRegisterOptions used in ForesightManager.instance.register({})
303
+ * without the element as the element will be the ref of the component.
304
+ *
305
+ * @link https://foresightjs.com/docs/getting_started/typescript#foresightregisteroptionswithoutelement
306
+ */
307
+ type ForesightRegisterOptionsWithoutElement = {
308
+ callback: ForesightCallback;
309
+ hitSlop?: HitSlop;
310
+ name?: string;
311
+ /**
312
+ * If set by user, stores additional information about the registered element
313
+ */
314
+ meta?: Record<string, unknown>;
315
+ /**
316
+ * Time in milliseconds after which the callback can be fired again and we reactivate the element.
317
+ * Set to Infinity to prevent callback from firing again after first execution.
318
+ * @default Infinity
319
+ */
320
+ reactivateAfter?: number;
321
+ /**
322
+ * When `false` the element stays registered but inactive: excluded from
323
+ * prediction and never fires its callback.
324
+ * @default true
325
+ */
326
+ enabled?: boolean;
327
+ };
328
+ /**
329
+ * Fully invisible "slop" around the element.
330
+ * Basically increases the hover hitbox
331
+ */
332
+ type HitSlop = Rect | number;
333
+ interface ForesightEventMap {
334
+ elementRegistered: ElementRegisteredEvent;
335
+ elementUnregistered: ElementUnregisteredEvent;
336
+ callbackInvoked: CallbackInvokedEvent;
337
+ callbackCompleted: CallbackCompletedEvent;
338
+ mouseTrajectoryUpdate: MouseTrajectoryUpdateEvent;
339
+ scrollTrajectoryUpdate: ScrollTrajectoryUpdateEvent;
340
+ managerSettingsChanged: ManagerSettingsChangedEvent;
341
+ deviceStrategyChanged: DeviceStrategyChangedEvent;
342
+ }
343
+ type ForesightEvent = "elementRegistered" | "elementUnregistered" | "callbackInvoked" | "callbackCompleted" | "mouseTrajectoryUpdate" | "scrollTrajectoryUpdate" | "managerSettingsChanged" | "deviceStrategyChanged";
344
+ interface DeviceStrategyChangedEvent extends ForesightBaseEvent {
345
+ type: "deviceStrategyChanged";
346
+ newStrategy: CurrentDeviceStrategy;
347
+ oldStrategy: CurrentDeviceStrategy;
348
+ }
349
+ interface ElementRegisteredEvent extends ForesightBaseEvent {
350
+ type: "elementRegistered";
351
+ element: ForesightElement;
352
+ state: ForesightElementState;
353
+ }
354
+ interface ElementUnregisteredEvent extends ForesightBaseEvent {
355
+ type: "elementUnregistered";
356
+ element: ForesightElement;
357
+ state: ForesightElementState;
358
+ unregisterReason: ElementUnregisteredReason;
359
+ wasLastRegisteredElement: boolean;
360
+ }
361
+ /**
362
+ * The reason an element was unregistered from ForesightManager's tracking.
363
+ * - `callbackHit`: The element was automatically unregistered after its callback fired.
364
+ * - `disconnected`: No longer emitted. Elements detached from the DOM are now parked
365
+ * (kept registered but inactive) and resumed on reconnect, rather than unregistered.
366
+ * - `apiCall`: The developer manually called the `unregister()` function for the element.
367
+ * - `devtools`: When clicking the trash icon in the devtools element tab
368
+ * - any other string
369
+ */
370
+ type ElementUnregisteredReason = "disconnected" | "apiCall" | "devtools" | (string & {});
371
+ interface CallbackInvokedEvent extends ForesightBaseEvent {
372
+ type: "callbackInvoked";
373
+ element: ForesightElement;
374
+ state: ForesightElementState;
375
+ hitType: CallbackHitType;
376
+ }
377
+ interface CallbackCompletedEventBase extends ForesightBaseEvent {
378
+ type: "callbackCompleted";
379
+ element: ForesightElement;
380
+ state: ForesightElementState;
381
+ hitType: CallbackHitType;
382
+ elapsed: number;
383
+ wasLastActiveElement: boolean;
384
+ }
385
+ type CallbackCompletedEvent = CallbackCompletedEventBase & {
386
+ status: callbackStatus;
387
+ errorMessage: string | null;
388
+ };
389
+ interface MouseTrajectoryUpdateEvent extends Omit<ForesightBaseEvent, "timestamp"> {
390
+ type: "mouseTrajectoryUpdate";
391
+ trajectoryPositions: TrajectoryPositions;
392
+ predictionEnabled: boolean;
393
+ }
394
+ interface ScrollTrajectoryUpdateEvent extends Omit<ForesightBaseEvent, "timestamp"> {
395
+ type: "scrollTrajectoryUpdate";
396
+ currentPoint: Point;
397
+ predictedPoint: Point;
398
+ scrollDirection: ScrollDirection;
399
+ }
400
+ interface ManagerSettingsChangedEvent extends ForesightBaseEvent {
401
+ type: "managerSettingsChanged";
402
+ managerData: ForesightManagerData;
403
+ updatedSettings: UpdatedManagerSetting[];
404
+ }
405
+ type UpdatedManagerSetting = { [K in keyof ForesightManagerSettings]: {
406
+ setting: K;
407
+ newValue: ForesightManagerSettings[K];
408
+ oldValue: ForesightManagerSettings[K];
409
+ } }[keyof ForesightManagerSettings];
410
+ type ForesightEventListener<K extends ForesightEvent = ForesightEvent> = (event: ForesightEventMap[K]) => void;
411
+ interface ForesightBaseEvent {
412
+ type: ForesightEvent;
413
+ timestamp: number;
414
+ }
415
+ //#endregion
416
+ //#region src/managers/ForesightManager.d.ts
417
+ /**
418
+ * Manages the prediction of user intent based on mouse trajectory and element interactions.
419
+ *
420
+ * ForesightManager is a singleton class responsible for:
421
+ * - Registering HTML elements to monitor.
422
+ * - Tracking mouse movements and predicting future cursor positions.
423
+ * - Detecting when a predicted trajectory intersects with a registered element's bounds.
424
+ * - Invoking callbacks associated with elements upon predicted or actual interaction.
425
+ * - Deactivating elements after their callback completes, with optional reactivation via `reactivateAfter`.
426
+ * - Handling global settings for prediction behavior (e.g., history size, prediction time).
427
+ * - Delegating element bounds observation to device handlers ({@link DesktopHandler}, {@link TouchDeviceHandler}).
428
+ * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.
429
+ * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.
430
+ *
431
+ * It should be initialized once using {@link ForesightManager.initialize} and then
432
+ * accessed via the static getter {@link ForesightManager.instance}.
433
+ */
434
+ declare class ForesightManager {
435
+ private static manager;
436
+ /** Internal entries containing full element data, callbacks, and subscribers. */
437
+ private elementEntries;
438
+ /** Public read-only view exposing only external state, derived from {@link elementEntries}. */
439
+ readonly registeredElements: ReadonlyMap<ForesightElement, ForesightElementState>;
440
+ private idCounter;
441
+ private activeElementCount;
442
+ private parkedElementCount;
443
+ private desktopHandler;
444
+ private touchDeviceHandler;
445
+ private currentlyActiveHandler;
446
+ private handlerDependencies;
447
+ private isSetup;
448
+ private pendingPointerEvent;
449
+ private rafId;
450
+ private domObserver;
451
+ private currentDeviceStrategy;
452
+ private eventEmitter;
453
+ private _globalCallbackHits;
454
+ private _globalSettings;
455
+ private constructor();
456
+ private getOrCreateDesktopHandler;
457
+ private getOrCreateTouchHandler;
458
+ static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager;
459
+ static get isInitiated(): Readonly<boolean>;
460
+ static get instance(): ForesightManager;
461
+ private generateId;
462
+ private get isUsingDesktopHandler();
463
+ addEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>, options?: {
464
+ signal?: AbortSignal;
465
+ }): void;
466
+ removeEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>): void;
467
+ hasListeners<K extends ForesightEvent>(eventType: K): boolean;
468
+ /**
469
+ * Subscribe to logical state changes for a specific element.
470
+ * The listener is called (with no arguments) whenever the element's
471
+ * immutable state snapshot is replaced. Never fires for geometry-only
472
+ * changes (scroll/resize) - see {@link subscribeToElementBounds}.
473
+ * Use {@link registeredElements} to read the latest state inside the listener.
474
+ *
475
+ * @returns An unsubscribe function, or `undefined` if the element is not registered.
476
+ */
477
+ subscribeToElement(element: ForesightElement, listener: () => void): (() => void) | undefined;
478
+ /**
479
+ * Subscribe to geometry changes for a specific element (position/size, fired
480
+ * on every scroll/resize tick while visible). Use {@link getElementBounds}
481
+ * to read the latest geometry inside the listener.
482
+ *
483
+ * @returns An unsubscribe function, or `undefined` if the element is not registered.
484
+ */
485
+ subscribeToElementBounds(element: ForesightElement, listener: () => void): (() => void) | undefined;
486
+ /**
487
+ * Returns the current immutable geometry snapshot for a registered element,
488
+ * or `undefined` if the element is not registered.
489
+ */
490
+ getElementBounds(element: ForesightElement): ElementBounds | undefined;
491
+ get getManagerData(): Readonly<ForesightManagerData>;
492
+ private getLoadedModulesSnapshot;
493
+ register(options: ForesightRegisterNodeListOptions): ForesightRegisterResult[];
494
+ register(options: ForesightRegisterOptions): ForesightRegisterResult;
495
+ private registerElement;
496
+ /**
497
+ * Updates the options of an already-registered element.
498
+ * Only the provided fields are updated; omitted fields keep their current values.
499
+ * If a reactivation timeout is pending and reactivateAfter changed, the timeout is rescheduled.
500
+ *
501
+ * @throws Error if the element is not registered.
502
+ */
503
+ updateElementOptions(element: ForesightElement, options: Partial<ForesightRegisterOptionsWithoutElement>): ForesightElementState;
504
+ /**
505
+ * Create a subscribe function for a listener set (state or bounds subscribers).
506
+ * Returns an unsubscribe callback when called.
507
+ */
508
+ private makeSubscribe;
509
+ /**
510
+ * Replace the immutable state ref for an element and notify subscribers.
511
+ * No-op when every patch value already matches current state - preserves the
512
+ * stable-reference contract relied on by useSyncExternalStore and shallowRef.
513
+ */
514
+ private updateElementState;
515
+ /**
516
+ * Replace the immutable geometry ref for an element and notify bounds
517
+ * subscribers. No-op when both rects are content-equal. Preserves the
518
+ * stable-reference contract, mirroring {@link updateElementState}.
519
+ *
520
+ * When a single trigger changes both geometry and logical state (position
521
+ * change, hitSlop update), bounds must be updated BEFORE the state patch so
522
+ * state subscribers always read fresh geometry.
523
+ */
524
+ private updateElementBounds;
525
+ unregister(element: ForesightElement | NodeListOf<ForesightElement>, unregisterReason?: ElementUnregisteredReason): void;
526
+ private unregisterElement;
527
+ reactivate(element: ForesightElement | NodeListOf<ForesightElement>): void;
528
+ private reactivateElement;
529
+ /**
530
+ * Toggle prediction for a registered element without unregistering it.
531
+ * Disabling deactivates and stops observing; enabling reactivates it.
532
+ */
533
+ private setElementEnabled;
534
+ private clearReactivateTimeout;
535
+ private callCallback;
536
+ private markElementAsRunning;
537
+ private executeCallbackAsync;
538
+ private finalizeCallback;
539
+ private updateHitCounters;
540
+ private setDeviceStrategy;
541
+ private handlePointerMove;
542
+ private initializeGlobalListeners;
543
+ private removeGlobalListeners;
544
+ private handleDomMutations;
545
+ /**
546
+ * Deactivate an element that was detached from the DOM. It is kept in
547
+ * {@link elementEntries} (still registered) and flagged `isParked` so it can be
548
+ * resumed when it reconnects.
549
+ */
550
+ private parkDisconnected;
551
+ /**
552
+ * Re-activate a previously parked element once it is back in the DOM. Mirrors
553
+ * the activation rules used everywhere else: disabled / limited connections stay
554
+ * inactive, and an element that already fired its callback stays inactive too
555
+ * (it resumes the same state it had before it detached).
556
+ */
557
+ private resumeReconnected;
558
+ /**
559
+ * Tear down global listeners only when nothing needs them: no active elements
560
+ * to predict on, and no parked elements waiting to reconnect (which need the
561
+ * MutationObserver to detect their return).
562
+ */
563
+ private removeGlobalListenersIfIdle;
564
+ alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
565
+ private forceUpdateAllElementBounds;
566
+ /**
567
+ * ONLY use this function when you want to change the rect bounds via code, if the rects are changing because of updates in the DOM do not use this function.
568
+ * We need an observer for that
569
+ */
570
+ private forceUpdateElementBounds;
571
+ private devLog;
572
+ }
573
+ //#endregion
574
+ //#region src/helpers/createInitialState.d.ts
575
+ /**
576
+ * Snapshot of the flat state shape for an element that is not (yet) tracked by the
577
+ * manager. Reuses the same fields as a registered element so consumers don't need
578
+ * to special-case `null`.
579
+ *
580
+ * Used in two situations:
581
+ * 1. The manager refuses to register the element (touch device, limited connection,
582
+ * etc.) - pass `isLimitedConnection` to reflect that.
583
+ * 2. Framework wrappers (React, Vue) need an initial snapshot before `register()`
584
+ * has run. `register()` requires a real DOM element, which only exists after
585
+ * the consumer's first render commits, so the wrapper returns this snapshot
586
+ * during that brief window. Pass `isLimitedConnection: false` for this case.
587
+ */
588
+ declare const createUnregisteredSnapshot: (isLimitedConnection: boolean) => ForesightElementState;
589
+ //#endregion
590
+ //#region src/core/BaseForesightModule.d.ts
591
+ type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean;
592
+ //#endregion
593
+ export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type DeviceStrategyChangedEvent, type ElementBounds, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightCallback, type ForesightElement, type ForesightElementState, type ForesightEvent, type ForesightEventMap, ForesightManager, type ForesightManagerData, type ForesightManagerSettings, type Point as ForesightPoint, type Rect as ForesightRect, type ForesightRegisterNodeListOptions, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HasListenersFunction, type HitSlop, type ManagerSettingsChangedEvent, type MinimumConnectionType, type MouseTrajectoryUpdateEvent, type ScrollDirection, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting, createUnregisteredSnapshot };
594
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/helpers/CircularBuffer.ts","../src/types/types.ts","../src/managers/ForesightManager.ts","../src/helpers/createInitialState.ts","../src/core/BaseForesightModule.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{a as e,i as t,n,o as r,t as i}from"./rectAndHitSlop-T7Z3PZlb.mjs";var a=class{constructor(e,t){this.source=e,this.derive=t}get size(){return this.source.size}get(e){let t=this.source.get(e);return t===void 0?void 0:this.derive(t)}has(e){return this.source.has(e)}forEach(e){this.source.forEach((t,n)=>e(this.derive(t),n,this))}*entries(){for(let[e,t]of this.source)yield[e,this.derive(t)]}*values(){for(let e of this.source.values())yield this.derive(e)}keys(){return this.source.keys()}[Symbol.iterator](){return this.entries()}get[Symbol.toStringTag](){return`DerivedMapView`}};const o=e=>{let t=s(),n=c(e);return{isTouchDevice:t,isLimitedConnection:n,shouldRegister:!n}},s=()=>typeof window>`u`||typeof navigator>`u`?!1:window.matchMedia(`(pointer: coarse)`).matches&&navigator.maxTouchPoints>0,c=e=>{let t=navigator.connection;if(!t)return!1;let n=[`slow-2g`,`2g`,`3g`,`4g`];return n.indexOf(t.effectiveType)<n.indexOf(e)||t.saveData},l=e=>{if(typeof window>`u`||typeof document>`u`)return!1;let t=window.innerWidth||document.documentElement.clientWidth,n=window.innerHeight||document.documentElement.clientHeight;return e.top<n&&e.bottom>0&&e.left<t&&e.right>0},u=()=>({mouse:{hover:0,trajectory:0},tab:{forwards:0,reverse:0},scroll:{down:0,left:0,right:0,up:0},touch:0,viewport:0,total:0}),d=()=>({enableManagerLogging:!1,enableMousePrediction:!0,enableScrollPrediction:!0,positionHistorySize:8,trajectoryPredictionTime:120,scrollMargin:150,defaultHitSlop:{top:0,left:0,right:0,bottom:0},enableTabPrediction:!0,tabOffset:2,touchDeviceStrategy:`onTouchStart`,minimumConnectionType:`3g`}),f=(e,r,i,a)=>{let{element:o,callback:s,hitSlop:c,name:u,meta:d,reactivateAfter:f,enabled:p}=e,m=o.getBoundingClientRect(),h=c===void 0?i:t(c),g=p!==!1;return{state:{id:r,name:u||o.id||`unnamed`,meta:d??{},hitSlop:h,isLimitedConnection:a,isIntersectingWithViewport:l(m),isRegistered:!0,isActive:g&&!a,isParked:!1,isEnabled:g,isPredicted:!1,isCallbackRunning:!1,hitCount:0,registerCount:1,durationMs:void 0,status:void 0,error:null,reactivateAfter:f??1/0},bounds:{originalRect:m,expandedRect:n(m,h)},invokedAt:void 0,completedAt:void 0,element:o,callback:s,reactivateTimeoutId:void 0,subscribers:new Set,boundsSubscribers:new Set}},p=e=>({id:``,name:``,meta:{},hitSlop:{top:0,left:0,right:0,bottom:0},isLimitedConnection:e,isIntersectingWithViewport:!1,isRegistered:!1,isActive:!1,isParked:!1,isEnabled:!1,isPredicted:!1,isCallbackRunning:!1,hitCount:0,registerCount:0,durationMs:void 0,status:void 0,error:null,reactivateAfter:r});var m=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,n){if(n?.signal?.aborted)return;let r=this.eventListeners.get(e)??[];r.push(t),this.eventListeners.set(e,r),n?.signal?.addEventListener(`abort`,()=>this.removeEventListener(e,t))}removeEventListener(e,t){let n=this.eventListeners.get(e);if(!n)return;let r=n.indexOf(t);r>-1&&n.splice(r,1)}emit(e){let t=this.eventListeners.get(e.type);if(!(!t||t.length===0))for(let n=0;n<t.length;n++)try{let r=t[n];r&&r(e)}catch(t){console.error(`Error in ForesightManager event listener ${n} for ${e.type}:`,t)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};const h=(e,t)=>e!==void 0&&t!==e,g={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}},_=(t,n,r)=>{if(!h(r,t[n]))return!1;let{min:i,max:a}=g[n];return t[n]=e(r,i,a,n),!0},v=(e,t,n)=>h(n,e[t])?(e[t]=n,!0):!1,y=(e,n)=>{_(e,`trajectoryPredictionTime`,n.trajectoryPredictionTime),_(e,`positionHistorySize`,n.positionHistorySize),_(e,`scrollMargin`,n.scrollMargin),_(e,`tabOffset`,n.tabOffset),v(e,`enableMousePrediction`,n.enableMousePrediction),v(e,`enableScrollPrediction`,n.enableScrollPrediction),v(e,`enableTabPrediction`,n.enableTabPrediction),v(e,`enableManagerLogging`,n.enableManagerLogging),n.defaultHitSlop!==void 0&&(e.defaultHitSlop=t(n.defaultHitSlop)),n.touchDeviceStrategy!==void 0&&(e.touchDeviceStrategy=n.touchDeviceStrategy),n.minimumConnectionType!==void 0&&(e.minimumConnectionType=n.minimumConnectionType)},b=(e,n)=>{let r=[],a=!1,o=!1,s=!1,c=!1,l=!1;if(!n)return{changedSettings:r,positionHistorySizeChanged:a,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l};for(let t of[`trajectoryPredictionTime`,`positionHistorySize`,`scrollMargin`,`tabOffset`]){let i=e[t];_(e,t,n[t])&&(r.push({setting:t,oldValue:i,newValue:e[t]}),t===`positionHistorySize`&&(a=!0))}for(let t of[`enableMousePrediction`,`enableScrollPrediction`,`enableTabPrediction`]){let i=e[t];v(e,t,n[t])&&(r.push({setting:t,oldValue:i,newValue:e[t]}),t===`enableScrollPrediction`&&(o=!0),t===`enableTabPrediction`&&(s=!0))}if(n.defaultHitSlop!==void 0){let a=e.defaultHitSlop,o=t(n.defaultHitSlop);i(a,o)||(e.defaultHitSlop=o,r.push({setting:`defaultHitSlop`,oldValue:a,newValue:o}),c=!0)}if(n.touchDeviceStrategy!==void 0){let t=e.touchDeviceStrategy;e.touchDeviceStrategy=n.touchDeviceStrategy,r.push({setting:`touchDeviceStrategy`,oldValue:t,newValue:n.touchDeviceStrategy}),l=!0}if(n.minimumConnectionType!==void 0){let t=e.minimumConnectionType;e.minimumConnectionType=n.minimumConnectionType,r.push({setting:`minimumConnectionType`,oldValue:t,newValue:n.minimumConnectionType})}return{changedSettings:r,positionHistorySizeChanged:a,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l}};var x=class e{constructor(e){this.elementEntries=new Map,this.registeredElements=new a(this.elementEntries,e=>e.state),this.idCounter=0,this.activeElementCount=0,this.parkedElementCount=0,this.desktopHandler=null,this.touchDeviceHandler=null,this.currentlyActiveHandler=null,this.isSetup=!1,this.pendingPointerEvent=null,this.rafId=null,this.domObserver=null,this.currentDeviceStrategy=s()?`touch`:`mouse`,this.eventEmitter=new m,this._globalCallbackHits=u(),this._globalSettings=d(),this.handlePointerMove=e=>{this.pendingPointerEvent=e,e.pointerType!==this.currentDeviceStrategy&&(this.eventEmitter.emit({type:`deviceStrategyChanged`,timestamp:Date.now(),newStrategy:e.pointerType,oldStrategy:this.currentDeviceStrategy}),this.currentDeviceStrategy=e.pointerType,this.setDeviceStrategy(this.currentDeviceStrategy)),!this.rafId&&(this.rafId=requestAnimationFrame(()=>{if(!this.isUsingDesktopHandler){this.rafId=null;return}this.pendingPointerEvent&&this.desktopHandler?.processMouseMovement(this.pendingPointerEvent),this.rafId=null}))},this.handleDomMutations=e=>{if(!e.length)return;this.desktopHandler?.invalidateTabCache();let t=!1;for(let n=0;n<e.length;n++){let r=e[n];if(r&&r.type===`childList`&&(r.removedNodes.length>0||r.addedNodes.length>0)){t=!0;break}}if(t)for(let e of this.elementEntries.values())e.state.isParked?e.element.isConnected&&this.resumeReconnected(e):e.element.isConnected||this.parkDisconnected(e)},e!==void 0&&y(this._globalSettings,e),this.handlerDependencies={elements:this.elementEntries,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),updateElementState:this.updateElementState.bind(this),updateElementBounds:this.updateElementBounds.bind(this),settings:this._globalSettings},this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`),this.initializeGlobalListeners()}async getOrCreateDesktopHandler(){if(!this.desktopHandler){let{DesktopHandler:e}=await import(`./DesktopHandler-CR9nLuzu.mjs`);this.desktopHandler=new e(this.handlerDependencies),this.devLog(`DesktopHandler lazy loaded`)}return this.desktopHandler}async getOrCreateTouchHandler(){if(!this.touchDeviceHandler){let{TouchDeviceHandler:e}=await import(`./TouchDeviceHandler-BPzPdr-z.mjs`);this.touchDeviceHandler=new e(this.handlerDependencies),this.devLog(`TouchDeviceHandler lazy loaded`)}return this.touchDeviceHandler}static initialize(t){return this.isInitiated||(e.manager=new e(t)),e.manager}static get isInitiated(){return!!e.manager}static get instance(){return this.initialize()}generateId(){return`foresight-${++this.idCounter}`}get isUsingDesktopHandler(){return this.currentDeviceStrategy===`mouse`||this.currentDeviceStrategy===`pen`}addEventListener(e,t,n){this.eventEmitter.addEventListener(e,t,n)}removeEventListener(e,t){this.eventEmitter.removeEventListener(e,t)}hasListeners(e){return this.eventEmitter.hasListeners(e)}subscribeToElement(e,t){let n=this.elementEntries.get(e);if(n)return this.makeSubscribe(n.subscribers)(t)}subscribeToElementBounds(e,t){let n=this.elementEntries.get(e);if(n)return this.makeSubscribe(n.boundsSubscribers)(t)}getElementBounds(e){return this.elementEntries.get(e)?.bounds}get getManagerData(){return{registeredElements:this.registeredElements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount,parkedElementCount:this.parkedElementCount,loadedModules:this.getLoadedModulesSnapshot()}}getLoadedModulesSnapshot(){let e=this.desktopHandler?.loadedPredictors,t=this.touchDeviceHandler?.loadedPredictors;return{desktopHandler:this.desktopHandler!==null,touchHandler:this.touchDeviceHandler!==null,predictors:{mouse:e?.mouse??!1,tab:e?.tab??!1,scroll:e?.scroll??!1,viewport:t?.viewport??!1,touchStart:t?.touchStart??!1}}}register(e){let{element:t,...n}=e;return t instanceof NodeList?Array.from(t,e=>this.registerElement({...n,element:e})):this.registerElement({...n,element:t})}registerElement(e){let{isLimitedConnection:t}=o(this._globalSettings.minimumConnectionType),n=this.elementEntries.get(e.element);if(n)return this.updateElementOptions(e.element,e),this.updateElementState(n,{registerCount:n.state.registerCount+1}),{...n.state,unregister:()=>{this.unregister(e.element)},subscribe:this.makeSubscribe(n.subscribers),getSnapshot:()=>n.state,subscribeToBounds:this.makeSubscribe(n.boundsSubscribers),getBounds:()=>n.bounds};this.isSetup||this.initializeGlobalListeners();let r=f(e,this.generateId(),this._globalSettings.defaultHitSlop,t);return this.elementEntries.set(e.element,r),r.state.isActive&&(this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e.element)),this.eventEmitter.emit({type:`elementRegistered`,timestamp:Date.now(),element:e.element,state:r.state}),{...r.state,unregister:()=>{this.unregister(e.element)},subscribe:this.makeSubscribe(r.subscribers),getSnapshot:()=>r.state,subscribeToBounds:this.makeSubscribe(r.boundsSubscribers),getBounds:()=>r.bounds}}updateElementOptions(e,r){let a=this.elementEntries.get(e);if(!a)throw Error(`Cannot update options: element is not registered.`);r.callback&&(a.callback=r.callback),r.enabled!==void 0&&this.setElementEnabled(a,e,r.enabled!==!1);let o=a.state.hitSlop;if(r.hitSlop!==void 0){let s=t(r.hitSlop);if(!i(s,o)){o=s;let t=e.getBoundingClientRect();this.updateElementBounds(a,{originalRect:t,expandedRect:n(t,o)})}}let s=a.state.reactivateAfter,c=r.reactivateAfter??s,l=this.updateElementState(a,{name:r.name||a.state.name,meta:r.meta??a.state.meta,reactivateAfter:c,hitSlop:o});return c!==s&&(a.reactivateTimeoutId!==void 0&&this.clearReactivateTimeout(a),c!==1/0&&l.isPredicted&&(a.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e)},c))),l}makeSubscribe(e){return t=>(e.add(t),()=>{e.delete(t)})}updateElementState(e,t){let n=e.state,r=!1;for(let e in t)if(t[e]!==n[e]){r=!0;break}if(!r)return n;let i={...n,...t};e.state=i;for(let t of e.subscribers)try{t()}catch(e){console.error(`Error in element subscriber for ${i.name}:`,e)}return i}updateElementBounds(e,t){let n=e.bounds;if(i(t.originalRect,n.originalRect)&&i(t.expandedRect,n.expandedRect))return n;e.bounds=t;for(let t of e.boundsSubscribers)try{t()}catch(t){console.error(`Error in element bounds subscriber for ${e.state.name}:`,t)}return t}unregister(e,t){e instanceof NodeList?e.forEach(e=>this.unregisterElement(e,t)):this.unregisterElement(e,t)}unregisterElement(e,t){let n=this.elementEntries.get(e);if(!n)return;this.clearReactivateTimeout(n),this.currentlyActiveHandler?.unobserveElement(e),n.state.isActive&&this.activeElementCount--,n.state.isParked&&this.parkedElementCount--;let r=this.updateElementState(n,{isRegistered:!1,isActive:!1,isParked:!1,isPredicted:!1,isCallbackRunning:!1});this.elementEntries.delete(e),n.subscribers.clear(),n.boundsSubscribers.clear();let i=this.elementEntries.size===0&&this.isSetup;i&&(this.devLog(`All elements unregistered, removing global listeners`),this.removeGlobalListeners()),this.eventEmitter.emit({type:`elementUnregistered`,element:e,state:r,timestamp:Date.now(),unregisterReason:t??`by user`,wasLastRegisteredElement:i})}reactivate(e){e instanceof NodeList?e.forEach(e=>this.reactivateElement(e)):this.reactivateElement(e)}reactivateElement(e){let t=this.elementEntries.get(e);!t||!t.state.isEnabled||(this.isSetup||this.initializeGlobalListeners(),this.clearReactivateTimeout(t),!(t.state.isCallbackRunning||t.state.isActive)&&(this.updateElementState(t,{isActive:!0,isPredicted:!1}),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e)))}setElementEnabled(e,t,n){if(e.state.isEnabled===n)return;let r=n&&!e.state.isLimitedConnection&&!e.state.isParked;r?(this.isSetup||this.initializeGlobalListeners(),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(t)):(this.clearReactivateTimeout(e),this.currentlyActiveHandler?.unobserveElement(t),e.state.isActive&&this.activeElementCount--),this.updateElementState(e,{isEnabled:n,isActive:r,isPredicted:!1,isCallbackRunning:!1}),this.removeGlobalListenersIfIdle()}clearReactivateTimeout(e){clearTimeout(e.reactivateTimeoutId),e.reactivateTimeoutId=void 0}callCallback(e,t){e.state.isPredicted||!e.state.isActive||(this.markElementAsRunning(e),this.executeCallbackAsync(e,t))}markElementAsRunning(e){this.clearReactivateTimeout(e),e.invokedAt=Date.now(),this.updateElementState(e,{isPredicted:!0,isCallbackRunning:!0,hitCount:e.state.hitCount+1})}async executeCallbackAsync(e,t){this.updateHitCounters(t),this.eventEmitter.emit({type:`callbackInvoked`,timestamp:Date.now(),element:e.element,state:e.state,hitType:t});let n=performance.now(),r=null;try{await e.callback(e.state)}catch(t){r=t instanceof Error?t.message:String(t),console.error(`Error in callback for element ${e.state.name}:`,t)}let i=r===null?`success`:`error`;this.finalizeCallback(e,t,n,i,r)}finalizeCallback(e,t,n,r,i){let a=performance.now()-n;e.state.isActive&&this.activeElementCount--,this.currentlyActiveHandler?.unobserveElement(e.element),e.completedAt=Date.now();let o=this.updateElementState(e,{isCallbackRunning:!1,isActive:!1,durationMs:a,status:r,error:i});o.reactivateAfter!==1/0&&(e.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},o.reactivateAfter));let s=this.activeElementCount===0;this.removeGlobalListenersIfIdle(),this.eventEmitter.emit({type:`callbackCompleted`,timestamp:Date.now(),element:e.element,state:o,hitType:t,elapsed:a,status:r,errorMessage:i,wasLastActiveElement:s})}updateHitCounters(e){switch(e.kind){case`mouse`:this._globalCallbackHits.mouse[e.subType]++;break;case`tab`:this._globalCallbackHits.tab[e.subType]++;break;case`scroll`:this._globalCallbackHits.scroll[e.subType]++;break;case`touch`:this._globalCallbackHits.touch++;break;case`viewport`:this._globalCallbackHits.viewport++;break;default:}this._globalCallbackHits.total++}async setDeviceStrategy(e){let t=this.currentDeviceStrategy;t!==e&&this.devLog(`Switching device strategy from ${t} to ${e}`),this.currentlyActiveHandler?.disconnect(),this.currentlyActiveHandler=e===`mouse`||e===`pen`?await this.getOrCreateDesktopHandler():await this.getOrCreateTouchHandler(),this.currentlyActiveHandler.connect()}initializeGlobalListeners(){this.isSetup||typeof document>`u`||(this.devLog(`Initializing global listeners (pointermove, MutationObserver)`),this.setDeviceStrategy(this.currentDeviceStrategy),document.addEventListener(`pointermove`,this.handlePointerMove),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.isSetup=!0)}removeGlobalListeners(){typeof document>`u`||(this.isSetup=!1,this.domObserver?.disconnect(),this.domObserver=null,document.removeEventListener(`pointermove`,this.handlePointerMove),this.currentlyActiveHandler?.disconnect(),this.rafId&&=(cancelAnimationFrame(this.rafId),null),this.pendingPointerEvent=null)}parkDisconnected(e){this.clearReactivateTimeout(e),this.currentlyActiveHandler?.unobserveElement(e.element),e.state.isActive&&this.activeElementCount--,this.parkedElementCount++,this.updateElementState(e,{isActive:!1,isCallbackRunning:!1,isParked:!0})}resumeReconnected(e){this.parkedElementCount--;let t=e.state.isEnabled&&!e.state.isLimitedConnection,n=t&&!e.state.isPredicted;n&&(this.isSetup||this.initializeGlobalListeners(),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e.element)),this.updateElementState(e,{isActive:n,isParked:!1}),t&&e.state.isPredicted&&e.state.reactivateAfter!==1/0&&(e.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.state.reactivateAfter))}removeGlobalListenersIfIdle(){this.activeElementCount>0||this.parkedElementCount>0||this.removeGlobalListeners()}alterGlobalSettings(e){let t=b(this._globalSettings,e);t.positionHistorySizeChanged&&this.desktopHandler&&this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize),t.scrollPredictionChanged&&this.isUsingDesktopHandler&&this.desktopHandler&&(this._globalSettings.enableScrollPrediction?this.desktopHandler.connectScrollPredictor():this.desktopHandler.disconnectScrollPredictor()),t.tabPredictionChanged&&this.isUsingDesktopHandler&&this.desktopHandler&&(this._globalSettings.enableTabPrediction?this.desktopHandler.connectTabPredictor():this.desktopHandler.disconnectTabPredictor()),t.hitSlopChanged&&this.forceUpdateAllElementBounds(),t.touchStrategyChanged&&!this.isUsingDesktopHandler&&this.touchDeviceHandler&&this.touchDeviceHandler.setTouchPredictor(),t.changedSettings.length>0&&this.eventEmitter.emit({type:`managerSettingsChanged`,timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:t.changedSettings})}forceUpdateAllElementBounds(){for(let e of this.elementEntries.values())e.state.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect();this.updateElementBounds(e,{originalRect:t,expandedRect:n(t,e.state.hitSlop)})}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c🛠️ ForesightManager: ${e}`,`color: #16a34a; font-weight: bold;`)}};export{x as ForesightManager,p as createUnregisteredSnapshot};
2
+ //# sourceMappingURL=index.mjs.map