js.foresight 3.1.0 → 3.1.2

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Bart Spaans
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bart Spaans
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/README.md CHANGED
@@ -1,130 +1,133 @@
1
- # [ForesightJS](https://foresightjs.com/)
2
-
3
- [![npm version](https://img.shields.io/npm/v/js.foresight.svg)](https://www.npmjs.com/package/js.foresight)
4
- [![npm downloads](https://img.shields.io/npm/dt/js.foresight.svg)](https://www.npmjs.com/package/js.foresight)
5
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/js.foresight)](https://bundlephobia.com/package/js.foresight)
6
- [![GitHub stars](https://img.shields.io/github/stars/spaansba/ForesightJS.svg?style=social&label=Star)](https://github.com/spaansba/ForesightJS)
7
- [![GitHub last commit](https://img.shields.io/github/last-commit/spaansba/ForesightJS)](https://github.com/spaansba/ForesightJS/commits)
8
-
9
- [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
10
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
- [![Demo](https://img.shields.io/badge/demo-live-blue)](https://foresightjs.com/#playground)
12
-
13
- ForesightJS is a lightweight JavaScript library with full TypeScript support that predicts user intent based on mouse movements, scroll and keyboard navigation. By analyzing cursor/scroll trajectory and tab sequences, it anticipates which elements a user is likely to interact with, allowing developers to trigger actions before the actual hover or click occurs (for example prefetching).
14
-
15
- ### Understanding ForesightJS's Role:
16
-
17
- When you over simplify prefetching it exists of three parts.
18
-
19
- - **What** resource or data to load
20
- - **How** the loading method and caching strategy is
21
- - **When** the optimal moment to start fetching is
22
-
23
- ForesightJS takes care of the **When** by predicting user intent with mouse trajectory and tab navigation.
24
- You supply the **What** and **How** inside your `callback` when you register an element.
25
-
26
- ### [Playground](https://foresightjs.com/)
27
-
28
- ![](https://github.com/user-attachments/assets/36c81a82-fee7-43d6-ba1e-c48214136f90)
29
- _In the GIF above, the [ForesightJS DevTools](https://foresightjs.com/docs/getting_started/development_tools) are enabled. Normally, users won't see anything that ForesightJS does except the increased perceived speed from early prefetching._
30
-
31
- ## Download
32
-
33
- ```bash
34
- pnpm add js.foresight
35
- # or
36
- npm install js.foresight
37
- # or
38
- yarn add js.foresight
39
- ```
40
-
41
- ## Which problems does ForesightJS solve?
42
-
43
- ### Problem 1: On-Hover Prefetching Still Has Latency
44
-
45
- Traditional hover-based prefetching only triggers after the user's cursor reaches an element. This approach wastes the critical 100-200ms window between when a user begins moving toward a target and when the hover event actually fires.
46
-
47
- ### Problem 2: Viewport-Based Prefetching is Wasteful
48
-
49
- Many modern frameworks (like Next.js) automatically prefetch resources for all links that enter the viewport. While well-intentioned, this creates significant overhead since users typically interact with only a small fraction of visible elements. Simply scrolling up and down the Next.js homepage can trigger **_1.59MB_** of unnecessary prefetch requests.
50
-
51
- ### Problem 3: Hover-Based Prefetching Excludes Keyboard Users
52
-
53
- Many routers rely on hover-based prefetching, but this approach completely excludes keyboard users since keyboard navigation never triggers hover events. This means keyboard users miss out on the performance benefits that mouse users get from hover-based prefetching.
54
-
55
- ### The ForesightJS Solution
56
-
57
- ForesightJS bridges the gap between wasteful viewport prefetching and basic hover prefetching. The `ForesightManager` predicts user interactions by analyzing mouse trajectory patterns, scroll direction and keyboard navigation sequences. This allows you to prefetch resources at the optimal time to improve performance, but targeted enough to avoid waste.
58
-
59
- ## Basic Usage Example
60
-
61
- This basic example is in vanilla JS, ofcourse most people will use ForesightJS with a framework. You can read about framework integrations in the [docs](https://foresightjs.com/docs/integrations).
62
-
63
- ```javascript
64
- import { ForesightManager } from "foresightjs"
65
-
66
- // Initialize the manager if you want custom global settings (do this once at app startup)
67
- ForesightManager.initialize({
68
- // Optional props (see configuration)
69
- })
70
-
71
- // Register an element to be tracked
72
- const myButton = document.getElementById("my-button")
73
-
74
- const { isTouchDevice, unregister } = ForesightManager.instance.register({
75
- element: myButton,
76
- callback: () => {
77
- // This is where your prefetching logic goes
78
- },
79
- hitSlop: 20, // Optional: "hit slop" in pixels. Overwrites defaultHitSlop
80
- // other optional props (see configuration)
81
- })
82
-
83
- // Later, when done with this element:
84
- unregister()
85
- ```
86
-
87
- ## Integrations
88
-
89
- Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. While I haven't yet built [integrations](https://foresightjs.com/docs/integrations) for every framework, ready-to-use implementations for [Next.js](https://foresightjs.com/docs/integrations/react/nextjs) and [React Router](https://foresightjs.com/docs/integrations/react/react-router) are already available. Sharing integrations for other frameworks/packages is highly appreciated!
90
-
91
- ## Configuration
92
-
93
- ForesightJS can be used bare-bones but also can be configured. For all configuration possibilities you can reference the [docs](https://foresightjs.com/docs/getting_started/config).
94
-
95
- ## Development Tools
96
-
97
- ForesightJS has dedicated [Development Tools](https://github.com/spaansba/ForesightJS-DevTools) created with [Foresight Events](https://foresightjs.com/docs/getting_started/events) that help you understand and tune how foresight is working in your application. This standalone development package provides real-time visualization of mouse trajectory predictions, element bounds, and callback execution. It's particularly helpful when setting up ForesightJS for the first time or when fine-tuning for specific UI components.
98
-
99
- ```bash
100
- npm install js.foresight-devtools
101
- ```
102
-
103
- See the [development tools documentation](https://foresightjs.com/docs/getting_started/debug) for more details.
104
-
105
- ## What About Touch Devices and Slow Connections?
106
-
107
- Since ForesightJS relies on the keyboard/mouse it will not register elements for touch devices. For limited connections (2G or data-saver mode), we respect the user's preference to minimize data usage and skip registration aswell.
108
-
109
- The `ForesightManager.instance.register()` method returns these properties:
110
-
111
- - `isTouchDevice` - true if user is on a touch device
112
- - `isLimitedConnection` - true when user is on a 2G connection or has data-saver enabled
113
- - `isRegistered` - true if element was actually registered
114
-
115
- With these properties you could create your own fallback prefetching methods if required. For example if the user is on a touch device you could prefetch based on viewport.
116
- An example of this can be found in the [Next.js](https://foresightjs.com/docs/integrations/react/nextjs) or [React Router](https://foresightjs.com/docs/integrations/react/react-router) ForesightLink components.
117
-
118
- ## How Does ForesightJS Work?
119
-
120
- For a detailed technical explanation of its prediction algorithms and internal architecture, see the **[Behind the Scenes documentation](https://foresightjs.com/docs/Behind_the_Scenes)**.
121
-
122
- ## Providing Context to AI Tools
123
-
124
- Since ForesightJS is a relatively new and unknown library, most AI assistants and large language models (LLMs) may not have comprehensive knowledge about it in their training data. To help AI assistants better understand and work with ForesightJS, you can provide them with context from our [llms.txt](https://foresightjs.com/llms.txt) page, which contains structured information about the library's API and usage patterns.
125
-
126
- Additionally, every page in the documentation is available in markdown format (try adding .md to any documentation URL). You can share these markdown files as context with AI assistants, though all this information is also consolidated in the llms.txt file for convenience.
127
-
128
- # Contributing
129
-
130
- Please see the [contributing guidelines](/CONTRIBUTING.md)
1
+ # [ForesightJS](https://foresightjs.com/)
2
+
3
+ [![npm version](https://img.shields.io/npm/v/js.foresight.svg)](https://www.npmjs.com/package/js.foresight)
4
+ [![npm downloads](https://img.shields.io/npm/dt/js.foresight.svg)](https://www.npmjs.com/package/js.foresight)
5
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/js.foresight)](https://bundlephobia.com/package/js.foresight)
6
+ [![GitHub last commit](https://img.shields.io/github/last-commit/spaansba/ForesightJS)](https://github.com/spaansba/ForesightJS/commits)
7
+
8
+ [![GitHub stars](https://img.shields.io/github/stars/spaansba/ForesightJS.svg?style=social&label=Star)](https://github.com/spaansba/ForesightJS)
9
+ [![Best of JS](https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=spaansba%2FForesightJS%26since=daily)](https://bestofjs.org/projects/foresightjs)
10
+
11
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
+ [![Demo](https://img.shields.io/badge/demo-live-blue)](https://foresightjs.com#playground)
14
+ ForesightJS is a lightweight JavaScript library with full TypeScript support that predicts user intent based on mouse movements, scroll and keyboard navigation. By analyzing cursor/scroll trajectory and tab sequences, it anticipates which elements a user is likely to interact with, allowing developers to trigger actions before the actual hover or click occurs (for example prefetching).
15
+
16
+ ### Understanding ForesightJS's Role:
17
+
18
+ When you over simplify prefetching it exists of three parts.
19
+
20
+ - **What** resource or data to load
21
+ - **How** the loading method and caching strategy is
22
+ - **When** the optimal moment to start fetching is
23
+
24
+ ForesightJS takes care of the **When** by predicting user intent with mouse trajectory and tab navigation.
25
+ You supply the **What** and **How** inside your `callback` when you register an element.
26
+
27
+ ### [Playground](https://foresightjs.com/)
28
+
29
+ ![](https://github.com/user-attachments/assets/36c81a82-fee7-43d6-ba1e-c48214136f90)
30
+ _In the GIF above, the [ForesightJS DevTools](https://foresightjs.com/docs/getting_started/development_tools) are enabled. Normally, users won't see anything that ForesightJS does except the increased perceived speed from early prefetching._
31
+
32
+ ## Download
33
+
34
+ ```bash
35
+ pnpm add js.foresight
36
+ # or
37
+ npm install js.foresight
38
+ # or
39
+ yarn add js.foresight
40
+ ```
41
+
42
+ ## Which problems does ForesightJS solve?
43
+
44
+ ### Problem 1: On-Hover Prefetching Still Has Latency
45
+
46
+ Traditional hover-based prefetching only triggers after the user's cursor reaches an element. This approach wastes the critical 100-200ms window between when a user begins moving toward a target and when the hover event actually fires.
47
+
48
+ ### Problem 2: Viewport-Based Prefetching is Wasteful
49
+
50
+ Many modern frameworks (like Next.js) automatically prefetch resources for all links that enter the viewport. While well-intentioned, this creates significant overhead since users typically interact with only a small fraction of visible elements. Simply scrolling up and down the Next.js homepage can trigger **_1.59MB_** of unnecessary prefetch requests.
51
+
52
+ ### Problem 3: Hover-Based Prefetching Excludes Keyboard Users
53
+
54
+ Many routers rely on hover-based prefetching, but this approach completely excludes keyboard users since keyboard navigation never triggers hover events. This means keyboard users miss out on the performance benefits that mouse users get from hover-based prefetching.
55
+
56
+ ### The ForesightJS Solution
57
+
58
+ ForesightJS bridges the gap between wasteful viewport prefetching and basic hover prefetching. The `ForesightManager` predicts user interactions by analyzing mouse trajectory patterns, scroll direction and keyboard navigation sequences. This allows you to prefetch resources at the optimal time to improve performance, but targeted enough to avoid waste.
59
+
60
+ ## Basic Usage Example
61
+
62
+ This basic example is in vanilla JS, ofcourse most people will use ForesightJS with a framework. You can read about framework integrations in the [docs](https://foresightjs.com/docs/integrations).
63
+
64
+ ```javascript
65
+ import { ForesightManager } from "foresightjs"
66
+
67
+ // Initialize the manager if you want custom global settings (do this once at app startup)
68
+ ForesightManager.initialize({
69
+ // Optional props (see configuration)
70
+ })
71
+
72
+ // Register an element to be tracked
73
+ const myButton = document.getElementById("my-button")
74
+
75
+ const { isTouchDevice, unregister } = ForesightManager.instance.register({
76
+ element: myButton,
77
+ callback: () => {
78
+ // This is where your prefetching logic goes
79
+ },
80
+ hitSlop: 20, // Optional: "hit slop" in pixels. Overwrites defaultHitSlop
81
+ // other optional props (see configuration)
82
+ })
83
+
84
+ // Later, when done with this element:
85
+ unregister()
86
+ ```
87
+
88
+ ## Integrations
89
+
90
+ Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. While I haven't yet built [integrations](https://foresightjs.com/docs/integrations) for every framework, ready-to-use implementations for [Next.js](https://foresightjs.com/docs/integrations/react/nextjs) and [React Router](https://foresightjs.com/docs/integrations/react/react-router) are already available. Sharing integrations for other frameworks/packages is highly appreciated!
91
+
92
+ ## Configuration
93
+
94
+ ForesightJS can be used bare-bones but also can be configured. For all configuration possibilities you can reference the [docs](https://foresightjs.com/docs/getting_started/config).
95
+
96
+ ## Development Tools
97
+
98
+ ForesightJS has dedicated [Development Tools](https://github.com/spaansba/ForesightJS-DevTools) created with [Foresight Events](https://foresightjs.com/docs/getting_started/events) that help you understand and tune how foresight is working in your application. This standalone development package provides real-time visualization of mouse trajectory predictions, element bounds, and callback execution. It's particularly helpful when setting up ForesightJS for the first time or when fine-tuning for specific UI components.
99
+
100
+ ```bash
101
+ npm install js.foresight-devtools
102
+ ```
103
+
104
+ See the [development tools documentation](https://foresightjs.com/docs/getting_started/debug) for more details.
105
+
106
+ ## What About Touch Devices and Slow Connections?
107
+
108
+ Since ForesightJS relies on the keyboard/mouse it will not register elements for touch devices. For limited connections (2G or data-saver mode), we respect the user's preference to minimize data usage and skip registration aswell.
109
+
110
+ The `ForesightManager.instance.register()` method returns these properties:
111
+
112
+ - `isTouchDevice` - true if user is on a touch device
113
+ - `isLimitedConnection` - true when user is on a 2G connection or has data-saver enabled
114
+ - `isRegistered` - true if element was actually registered
115
+
116
+ With these properties you could create your own fallback prefetching methods if required. For example if the user is on a touch device you could prefetch based on viewport.
117
+ An example of this can be found in the [Next.js](https://foresightjs.com/docs/integrations/react/nextjs) or [React Router](https://foresightjs.com/docs/integrations/react/react-router) ForesightLink components.
118
+
119
+ ## How Does ForesightJS Work?
120
+
121
+ For a detailed technical explanation of its prediction algorithms and internal architecture, see the **[Behind the Scenes documentation](https://foresightjs.com/docs/Behind_the_Scenes)**.
122
+
123
+ ## Providing Context to AI Tools
124
+
125
+ ForesightJS is a newer library, so most AI assistants and LLMs may not have much built-in knowledge about it. To improve their responses, you can provide the following context:
126
+
127
+ - Use [llms.txt](https://foresightjs.com/llms.txt) for a concise overview of the API and usage patterns.
128
+ - Use [llms-full.txt](https://foresightjs.com/llms-full.txt) for a full markdown version of the docs, ideal for AI tools that support context injection or uploads.
129
+ - All documentation pages are also available in markdown. You can view them by adding .md to the end of any URL, for example: https://foresightjs.com/docs/getting_started.md.
130
+
131
+ # Contributing
132
+
133
+ Please see the [contributing guidelines](/CONTRIBUTING.md)
package/dist/index.d.ts CHANGED
@@ -1,3 +1,22 @@
1
+ declare class CircularBuffer<T> {
2
+ private buffer;
3
+ private head;
4
+ private count;
5
+ private capacity;
6
+ constructor(capacity: number);
7
+ add(item: T): void;
8
+ getFirst(): T | undefined;
9
+ getLast(): T | undefined;
10
+ getFirstLast(): [T | undefined, T | undefined];
11
+ resize(newCapacity: number): void;
12
+ private getAllItems;
13
+ clear(): void;
14
+ get length(): number;
15
+ get size(): number;
16
+ get isFull(): boolean;
17
+ get isEmpty(): boolean;
18
+ }
19
+
1
20
  type Rect = {
2
21
  top: number;
3
22
  left: number;
@@ -29,7 +48,7 @@ type Point = {
29
48
  y: number;
30
49
  };
31
50
  type TrajectoryPositions = {
32
- positions: MousePosition[];
51
+ positions: CircularBuffer<MousePosition>;
33
52
  currentPoint: Point;
34
53
  predictedPoint: Point;
35
54
  };
@@ -130,6 +149,7 @@ type ForesightManagerData = {
130
149
  globalSettings: Readonly<ForesightManagerSettings>;
131
150
  globalCallbackHits: Readonly<CallbackHits>;
132
151
  eventListeners: ReadonlyMap<keyof ForesightEventMap, ForesightEventListener[]>;
152
+ historyPositions: TrajectoryPositions;
133
153
  };
134
154
  type BaseForesightManagerSettings = {
135
155
  /**
@@ -257,6 +277,7 @@ interface ElementUnregisteredEvent extends ForesightBaseEvent {
257
277
  type: "elementUnregistered";
258
278
  elementData: ForesightElementData;
259
279
  unregisterReason: ElementUnregisteredReason;
280
+ wasLastElement: boolean;
260
281
  }
261
282
  /**
262
283
  * The reason an element was unregistered from ForesightManager's tracking.
@@ -337,21 +358,16 @@ interface ForesightBaseEvent {
337
358
  declare class ForesightManager {
338
359
  private static manager;
339
360
  private elements;
361
+ private trajectoryPositions;
340
362
  private isSetup;
341
363
  private _globalCallbackHits;
342
364
  private _globalSettings;
343
- private trajectoryPositions;
344
- private tabbableElementsCache;
345
- private lastFocusedIndex;
346
- private predictedScrollPoint;
347
- private scrollDirection;
348
365
  private domObserver;
349
366
  private positionObserver;
350
- private lastKeyDown;
351
- private globalListenersController;
352
- private rafId;
353
- private pendingMouseEvent;
354
367
  private eventListeners;
368
+ private mousePredictor;
369
+ private tabPredictor;
370
+ private scrollPredictor;
355
371
  private constructor();
356
372
  static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager;
357
373
  addEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>, options?: {
@@ -368,10 +384,6 @@ declare class ForesightManager {
368
384
  private updateNumericSettings;
369
385
  private updateBooleanSetting;
370
386
  alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
371
- private forceUpdateAllElementBounds;
372
- private updatePointerState;
373
- private handleMouseMove;
374
- private processMouseMovement;
375
387
  /**
376
388
  * Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.
377
389
  *
@@ -379,20 +391,18 @@ declare class ForesightManager {
379
391
  *
380
392
  */
381
393
  private handleDomMutations;
382
- private handleKeyDown;
383
- private handleFocusIn;
384
394
  private updateHitCounters;
385
395
  private callCallback;
396
+ private handlePositionChange;
397
+ private handlePositionChangeDataUpdates;
398
+ private initializeGlobalListeners;
399
+ private removeGlobalListeners;
400
+ private forceUpdateAllElementBounds;
386
401
  /**
387
402
  * 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.
388
403
  * We need an observer for that
389
404
  */
390
405
  private forceUpdateElementBounds;
391
- private handlePositionChange;
392
- private handlePositionChangeDataUpdates;
393
- private handleScrollPrefetch;
394
- private initializeGlobalListeners;
395
- private removeGlobalListeners;
396
406
  }
397
407
 
398
408
  export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type ElementDataUpdatedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightElement, type ForesightElementData, type ForesightEvent, ForesightManager, type ForesightManagerSettings, type Rect as ForesightRect, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HitSlop, type ManagerSettingsChangedEvent, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var E=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var B=(i,e)=>{for(var t in e)E(i,t,{get:e[t],enumerable:!0})},K=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of k(e))!j.call(i,o)&&o!==t&&E(i,o,{get:()=>e[o],enumerable:!(n=U(e,o))||n.enumerable});return i};var G=i=>K(E({},"__esModule",{value:!0}),i);var ae={};B(ae,{ForesightManager:()=>f});module.exports=G(ae);var x=require("tabbable");function P(){let i=Y(),e=z();return{isTouchDevice:i,isLimitedConnection:e,shouldRegister:!i&&!e}}function Y(){return window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function z(){let i=navigator.connection;return i?/2g/.test(i.effectiveType)||i.saveData:!1}function d(i,e,t,n){return i<e?console.warn(`ForesightJS: "${n}" value ${i} is below minimum bound ${e}, clamping to ${e}`):i>t&&console.warn(`ForesightJS: "${n}" value ${i} is above maximum bound ${t}, clamping to ${t}`),Math.min(Math.max(i,e),t)}function S(i,e,t){let n=0,o=1,s=e.x-i.x,r=e.y-i.y,a=(l,u)=>{if(l===0){if(u<0)return!1}else{let c=u/l;if(l<0){if(c>o)return!1;c>n&&(n=c)}else{if(c<n)return!1;c<o&&(o=c)}}return!0};return!a(-s,i.x-t.left)||!a(s,t.right-i.x)||!a(-r,i.y-t.top)||!a(r,t.bottom-i.y)?!1:n<=o}function _(i,e,t,n){let o=performance.now(),s={point:i,time:o},{x:r,y:a}=i;if(e.push(s),e.length>t&&e.shift(),e.length<2)return{x:r,y:a};let l=e[0],u=e[e.length-1],c=(u.time-l.time)/1e3;if(c===0)return{x:r,y:a};let g=u.point.x-l.point.x,L=u.point.y-l.point.y,N=g/c,A=L/c,R=n/1e3,H=r+N*R,w=a+A*R;return{x:H,y:w}}function v(i){if(typeof i=="number"){let e=d(i,0,2e3,"hitslop");return{top:e,left:e,right:e,bottom:e}}return{top:d(i.top,0,2e3,"hitslop - top"),left:d(i.left,0,2e3,"hitslop - left"),right:d(i.right,0,2e3,"hitslop - right"),bottom:d(i.bottom,0,2e3,"hitslop - bottom")}}function m(i,e){return{left:i.left-e.left,right:i.right+e.right,top:i.top-e.top,bottom:i.bottom+e.bottom}}function T(i,e){return!i||!e?i===e:i.left===e.left&&i.right===e.right&&i.top===e.top&&i.bottom===e.bottom}function y(i,e){return i.x>=e.left&&i.x<=e.right&&i.y>=e.top&&i.y<=e.bottom}function I(i,e){return i!==void 0&&e!==i}function M(i,e,t,n){if(e!==null){let o=i?e-1:e+1;if(o>=0&&o<t.length&&t[o]===n)return o}return t.findIndex(o=>o===n)}function F(i,e){let n=e.top-i.top,o=e.left-i.left;return n<-1?"down":n>1?"up":o<-1?"right":o>1?"left":"none"}function C(i,e,t){let{x:n,y:o}=i,s={x:n,y:o};switch(e){case"down":s.y+=t;break;case"up":s.y-=t;break;case"left":s.x-=t;break;case"right":s.x+=t;break;case"none":break;default:}return s}var D=require("position-observer");function O(i){let e=window.innerWidth||document.documentElement.clientWidth,t=window.innerHeight||document.documentElement.clientHeight;return i.top<t&&i.bottom>0&&i.left<e&&i.right>0}var f=class i{constructor(){this.elements=new Map;this.isSetup=!1;this._globalCallbackHits={mouse:{hover:0,trajectory:0},tab:{forwards:0,reverse:0},scroll:{down:0,left:0,right:0,up:0},total:0};this._globalSettings={debug:!1,enableMousePrediction:!0,enableScrollPrediction:!0,positionHistorySize:8,trajectoryPredictionTime:120,scrollMargin:150,defaultHitSlop:{top:0,left:0,right:0,bottom:0},enableTabPrediction:!0,tabOffset:2};this.trajectoryPositions={positions:[],currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.predictedScrollPoint=null;this.scrollDirection=null;this.domObserver=null;this.positionObserver=null;this.lastKeyDown=null;this.globalListenersController=new AbortController;this.rafId=null;this.pendingMouseEvent=null;this.eventListeners=new Map;this.handleMouseMove=e=>{this.pendingMouseEvent=e,!this.rafId&&(this.rafId=requestAnimationFrame(()=>{this.pendingMouseEvent&&(this.processMouseMovement(this.pendingMouseEvent),this.pendingMouseEvent=null),this.rafId=null}))};this.handleDomMutations=e=>{e.length&&(this.tabbableElementsCache=[],this.lastFocusedIndex=null);for(let t of e)if(t.type==="childList"&&t.removedNodes.length>0)for(let n of this.elements.keys())n.isConnected||this.unregister(n,"disconnected")};this.handleKeyDown=e=>{e.key==="Tab"&&(this.lastKeyDown=e)};this.handleFocusIn=e=>{if(!this.lastKeyDown||!this._globalSettings.enableTabPrediction)return;let t=e.target;if(!(t instanceof HTMLElement))return;this.tabbableElementsCache.length||(this.tabbableElementsCache=(0,x.tabbable)(document.documentElement));let n=this.lastKeyDown.shiftKey,o=M(n,this.lastFocusedIndex,this.tabbableElementsCache,t);this.lastFocusedIndex=o,this.lastKeyDown=null;let s=[];for(let r=0;r<=this._globalSettings.tabOffset;r++)if(n){let a=this.tabbableElementsCache[o-r];this.elements.has(a)&&s.push(a)}else{let a=this.tabbableElementsCache[o+r];this.elements.has(a)&&s.push(a)}s.forEach(r=>{let a=this.elements.get(r);a&&this.callCallback(a,{kind:"tab",subType:n?"reverse":"forwards"})})};this.handlePositionChange=e=>{for(let t of e){let n=this.elements.get(t.target);n&&(this.handlePositionChangeDataUpdates(n,t),this.handleScrollPrefetch(n,t.boundingClientRect))}this.scrollDirection=null,this.predictedScrollPoint=null};this.handlePositionChangeDataUpdates=(e,t)=>{let n=[],o=t.isIntersecting,s={...e,isIntersectingWithViewport:o};e.isIntersectingWithViewport!==o&&n.push("visibility"),o&&(n.push("bounds"),this.handleScrollPrefetch(s,t.boundingClientRect),s={...s,elementBounds:{hitSlop:e.elementBounds.hitSlop,originalRect:t.boundingClientRect,expandedRect:m(t.boundingClientRect,e.elementBounds.hitSlop)}}),this.elements.set(e.element,s),n.length&&this.emit({type:"elementDataUpdated",elementData:s,updatedProps:n})}}static initialize(e){return this.isInitiated||(i.manager=new i),e!==void 0&&i.manager.alterGlobalSettings(e),i.manager}addEventListener(e,t,n){if(n?.signal?.aborted)return()=>{};let o=this.eventListeners.get(e)??[];o.push(t),this.eventListeners.set(e,o),n?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let n=this.eventListeners.get(e);if(!n)return;let o=n.indexOf(t);o>-1&&n.splice(o,1)}emit(e){let t=this.eventListeners.get(e.type);t&&t.forEach(n=>{try{n(e)}catch(o){console.error(`Error in ForesightManager event listener for ${e.type}:`,o)}})}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners}}static get isInitiated(){return!!i.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:e,callback:t,hitSlop:n,name:o}){let{shouldRegister:s,isTouchDevice:r,isLimitedConnection:a}=P();if(!s)return{isLimitedConnection:a,isTouchDevice:r,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let l=e.getBoundingClientRect(),u=n?v(n):this._globalSettings.defaultHitSlop,c={element:e,callback:t,elementBounds:{originalRect:l,expandedRect:m(l,u),hitSlop:u},isHovering:!1,trajectoryHitData:{isTrajectoryHit:!1,trajectoryHitTime:0,trajectoryHitExpirationTimeoutId:void 0},name:o||e.id||"unnamed",isIntersectingWithViewport:O(l),isRunningCallback:!1,registerCount:(this.registeredElements.get(e)?.registerCount??0)+1};return this.elements.set(e,c),this.positionObserver?.observe(e),this.emit({type:"elementRegistered",timestamp:Date.now(),elementData:c}),{isTouchDevice:r,isLimitedConnection:a,isRegistered:!0,unregister:()=>this.unregister(e,"apiCall")}}unregister(e,t){if(!this.elements.has(e))return;let n=this.elements.get(e);n?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(n.trajectoryHitData.trajectoryHitExpirationTimeoutId),this.positionObserver?.unobserve(e),this.elements.delete(e),this.elements.size===0&&this.isSetup&&this.removeGlobalListeners(),n&&this.emit({type:"elementUnregistered",elementData:n,timestamp:Date.now(),unregisterReason:t})}updateNumericSettings(e,t,n,o){return I(e,this._globalSettings[t])?(this._globalSettings[t]=d(e,n,o,t),!0):!1}updateBooleanSetting(e,t){return I(e,this._globalSettings[t])?(this._globalSettings[t]=e,!0):!1}alterGlobalSettings(e){let t=[];if([{setting:"positionHistorySize",min:2,max:30},{setting:"trajectoryPredictionTime",min:10,max:200},{setting:"scrollMargin",min:30,max:300},{setting:"tabOffset",min:0,max:20}].forEach(({setting:s,min:r,max:a})=>{let l=e?.[s];if(l===void 0)return;let u=this._globalSettings[s];if(this.updateNumericSettings(l,s,r,a)&&(t.push({setting:s,oldValue:u,newValue:this._globalSettings[s]}),s==="positionHistorySize"&&this._globalSettings.positionHistorySize<u)){let g=this._globalSettings.positionHistorySize;this.trajectoryPositions.positions.length>g&&(this.trajectoryPositions.positions=this.trajectoryPositions.positions.slice(-g))}}),["enableMousePrediction","enableScrollPrediction","enableTabPrediction"].forEach(s=>{let r=e?.[s];if(r===void 0)return;let a=this._globalSettings[s];this.updateBooleanSetting(r,s)&&t.push({setting:s,oldValue:a,newValue:this._globalSettings[s]})}),e?.defaultHitSlop!==void 0){let s=this._globalSettings.defaultHitSlop,r=v(e.defaultHitSlop);T(s,r)||(this._globalSettings.defaultHitSlop=r,t.push({setting:"defaultHitSlop",oldValue:s,newValue:r}),this.forceUpdateAllElementBounds())}t.length>0&&this.emit({type:"managerSettingsChanged",timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:t})}forceUpdateAllElementBounds(){for(let[e,t]of this.elements)t.isIntersectingWithViewport&&this.forceUpdateElementBounds(t)}updatePointerState(e){let t={x:e.clientX,y:e.clientY};this.trajectoryPositions.currentPoint=t,this._globalSettings.enableMousePrediction?this.trajectoryPositions.predictedPoint=_(t,this.trajectoryPositions.positions,this._globalSettings.positionHistorySize,this._globalSettings.trajectoryPredictionTime):this.trajectoryPositions.predictedPoint=t}processMouseMovement(e){this.updatePointerState(e);for(let t of this.elements.values()){if(!t.isIntersectingWithViewport)continue;let n=t.elementBounds.expandedRect;if(this._globalSettings.enableMousePrediction)S(this.trajectoryPositions.currentPoint,this.trajectoryPositions.predictedPoint,n)&&this.callCallback(t,{kind:"mouse",subType:"trajectory"});else if(y(this.trajectoryPositions.currentPoint,n)){this.callCallback(t,{kind:"mouse",subType:"hover"});return}}this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:this._globalSettings.enableMousePrediction,trajectoryPositions:this.trajectoryPositions})}updateHitCounters(e,t){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;default:}this._globalCallbackHits.total++}callCallback(e,t){if(e.isRunningCallback)return;e.isRunningCallback=!0,(async()=>{this.updateHitCounters(t,e),this.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let o=performance.now();try{await e.callback(),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:performance.now()-o,status:"success"})}catch(s){let r=s instanceof Error?s.message:String(s);console.error(`Error in callback for element ${e.name} (${e.element.tagName}):`,s),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:performance.now()-o,status:"error",errorMessage:r})}this.unregister(e.element,"callbackHit")})()}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),n=m(t,e.elementBounds.hitSlop);if(!T(n,e.elementBounds.expandedRect)){let o={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:n}};this.elements.set(e.element,o),this.emit({type:"elementDataUpdated",elementData:o,updatedProps:["bounds"]})}}handleScrollPrefetch(e,t){if(e.isIntersectingWithViewport)if(!this._globalSettings.enableScrollPrediction)y(this.trajectoryPositions.currentPoint,e.elementBounds.expandedRect)&&this.callCallback(e,{kind:"mouse",subType:"hover"});else{if(this.scrollDirection=this.scrollDirection??F(e.elementBounds.originalRect,t),this.scrollDirection==="none")return;this.predictedScrollPoint=this.predictedScrollPoint??C(this.trajectoryPositions.currentPoint,this.scrollDirection,this._globalSettings.scrollMargin),S(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,e.elementBounds.expandedRect)&&this.callCallback(e,{kind:"scroll",subType:this.scrollDirection}),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})}}initializeGlobalListeners(){if(this.isSetup||typeof window>"u"||typeof document>"u")return;let{signal:e}=this.globalListenersController;document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("keydown",this.handleKeyDown,{signal:e}),document.addEventListener("focusin",this.handleFocusIn,{signal:e}),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.positionObserver=new D.PositionObserver(this.handlePositionChange),this.isSetup=!0}removeGlobalListeners(){this.isSetup=!1,this.globalListenersController?.abort(),this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.domObserver?.disconnect(),this.domObserver=null,this.positionObserver?.disconnect(),this.positionObserver=null,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingMouseEvent=null}};0&&(module.exports={ForesightManager});
1
+ import{PositionObserver as rt}from"position-observer";var P=class{constructor(t){this.head=0;this.count=0;if(t<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=t,this.buffer=new Array(t)}add(t){this.buffer[this.head]=t,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let t=(this.head-1+this.capacity)%this.capacity;return this.buffer[t]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let i=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[i,i]}let t=this.getFirst(),e=this.getLast();return[t,e]}resize(t){if(t<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(t===this.capacity)return;let e=this.getAllItems();if(this.capacity=t,this.buffer=new Array(t),this.head=0,this.count=0,e.length>t){let i=e.slice(-t);for(let n of i)this.add(n)}else for(let i of e)this.add(i)}getAllItems(){if(this.count===0)return[];let t=new Array(this.count);if(this.count<this.capacity)for(let e=0;e<this.count;e++)t[e]=this.buffer[e];else{let e=this.head;for(let i=0;i<this.capacity;i++){let n=(e+i)%this.capacity;t[i]=this.buffer[n]}}return t}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}};function u(o,t,e,i){return o<t?console.warn(`ForesightJS: "${i}" value ${o} is below minimum bound ${t}, clamping to ${t}`):o>e&&console.warn(`ForesightJS: "${i}" value ${o} is above maximum bound ${e}, clamping to ${e}`),Math.min(Math.max(o,t),e)}function j(o){let t=window.innerWidth||document.documentElement.clientWidth,e=window.innerHeight||document.documentElement.clientHeight;return o.top<e&&o.bottom>0&&o.left<t&&o.right>0}function F(o){if(typeof o=="number"){let t=u(o,0,2e3,"hitslop");return{top:t,left:t,right:t,bottom:t}}return{top:u(o.top,0,2e3,"hitslop - top"),left:u(o.left,0,2e3,"hitslop - left"),right:u(o.right,0,2e3,"hitslop - right"),bottom:u(o.bottom,0,2e3,"hitslop - bottom")}}function y(o,t){return{left:o.left-t.left,right:o.right+t.right,top:o.top-t.top,bottom:o.bottom+t.bottom}}function x(o,t){return!o||!t?o===t:o.left===t.left&&o.right===t.right&&o.top===t.top&&o.bottom===t.bottom}function E(o,t){return o.x>=t.left&&o.x<=t.right&&o.y>=t.top&&o.y<=t.bottom}function H(){let o=z(),t=K();return{isTouchDevice:o,isLimitedConnection:t,shouldRegister:!o&&!t}}function z(){return window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function K(){let o=navigator.connection;return o?/2g/.test(o.effectiveType)||o.saveData:!1}function O(o,t){return o!==void 0&&t!==o}function S(o,t,e){let i=0,n=1,r=t.x-o.x,l=t.y-o.y,s=(a,c)=>{if(a===0){if(c<0)return!1}else{let d=c/a;if(a<0){if(d>n)return!1;d>i&&(i=d)}else{if(d<i)return!1;d<n&&(n=d)}}return!0};return!s(-r,o.x-e.left)||!s(r,e.right-o.x)||!s(-l,o.y-e.top)||!s(l,e.bottom-o.y)?!1:i<=n}function N(o,t,e){let i=performance.now(),n={point:o,time:i},{x:r,y:l}=o;if(t.add(n),t.length<2)return{x:r,y:l};let[s,a]=t.getFirstLast();if(!s||!a)return{x:r,y:l};let c=(a.time-s.time)*.001;if(c===0)return{x:r,y:l};let d=a.point.x-s.point.x,M=a.point.y-s.point.y,L=d/c,C=M/c,I=e*.001,R=r+L*I,w=l+C*I;return{x:R,y:w}}var h=class{constructor(t){this.elements=t.dependencies.elements,this.callCallback=t.dependencies.callCallback,this.emit=t.dependencies.emit,this.abortController=new AbortController}abort(){this.abortController.abort()}handleError(t,e){console.error(`${this.constructor.name} error in ${e}:`,t)}};var T=class extends h{constructor(e){super(e);this.pendingMouseEvent=null;this.rafId=null;this.handleMouseMove=e=>{this.pendingMouseEvent=e,!this.rafId&&(this.rafId=requestAnimationFrame(()=>{this.pendingMouseEvent&&this.processMouseMovement(this.pendingMouseEvent),this.rafId=null}))};this.enableMousePrediction=e.settings.enableMousePrediction,this.trajectoryPredictionTime=e.settings.trajectoryPredictionTime,this.positionHistorySize=e.settings.positionHistorySize,this.trajectoryPositions=e.trajectoryPositions,this.initializeListeners()}initializeListeners(){let{signal:e}=this.abortController;document.addEventListener("mousemove",this.handleMouseMove,{signal:e})}updatePointerState(e){let i={x:e.clientX,y:e.clientY};this.trajectoryPositions.currentPoint=i,this.enableMousePrediction?this.trajectoryPositions.predictedPoint=N(i,this.trajectoryPositions.positions,this.trajectoryPredictionTime):this.trajectoryPositions.predictedPoint=i}cleanup(){this.abort(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingMouseEvent=null}processMouseMovement(e){try{this.updatePointerState(e);for(let i of this.elements.values()){if(!i.isIntersectingWithViewport)continue;let n=i.elementBounds.expandedRect;if(this.enableMousePrediction)S(this.trajectoryPositions.currentPoint,this.trajectoryPositions.predictedPoint,n)&&this.callCallback(i,{kind:"mouse",subType:"trajectory"});else if(E(this.trajectoryPositions.currentPoint,n)){this.callCallback(i,{kind:"mouse",subType:"hover"});return}}this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:this.enableMousePrediction,trajectoryPositions:this.trajectoryPositions})}catch(i){this.handleError(i,"processMouseMovement")}}};function A(o,t){let i=t.top-o.top,n=t.left-o.left;return i<-1?"down":i>1?"up":n<-1?"right":n>1?"left":"none"}function k(o,t,e){let{x:i,y:n}=o,r={x:i,y:n};switch(t){case"down":r.y+=e;break;case"up":r.y-=e;break;case"left":r.x-=e;break;case"right":r.x+=e;break;case"none":break;default:}return r}var b=class extends h{constructor(e){super(e);this.predictedScrollPoint=null;this.scrollDirection=null;this.scrollMargin=e.settings.scrollMargin,this.trajectoryPositions=e.trajectoryPositions}initializeListeners(){}cleanup(){this.abort(),this.resetScrollProps()}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(e,i){if(e.isIntersectingWithViewport)try{if(this.scrollDirection=this.scrollDirection??A(e.elementBounds.originalRect,i),this.scrollDirection==="none")return;this.predictedScrollPoint=this.predictedScrollPoint??k(this.trajectoryPositions.currentPoint,this.scrollDirection,this.scrollMargin),S(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,e.elementBounds.expandedRect)&&this.callCallback(e,{kind:"scroll",subType:this.scrollDirection}),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})}catch(n){this.handleError(n,"handleScrollPrefetch")}}handleError(e,i){super.handleError(e,i),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.trajectoryPositions.currentPoint,scrollDirection:"none"})}};import{tabbable as V}from"tabbable";function U(o,t,e,i){if(t!==null&&t>-1){let n=o?t-1:t+1;if(n>=0&&n<e.length&&e[n]===i)return n}return e.findIndex(n=>n===i)}var f=class extends h{constructor(e){super(e);this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=e=>{e.key==="Tab"&&(this.lastKeyDown=e)};this.handleFocusIn=e=>{try{if(!this.lastKeyDown)return;let i=e.target;if(!(i instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.tabbableElementsCache=V(document.documentElement));let n=this.lastKeyDown.shiftKey,r=U(n,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let l=[];for(let s=0;s<=this.tabOffset;s++){let a=n?r-s:r+s,c=this.tabbableElementsCache[a];c&&c instanceof Element&&this.elements.has(c)&&l.push(c)}l.forEach(s=>{let a=this.elements.get(s);a&&this.callCallback(a,{kind:"tab",subType:n?"reverse":"forwards"})})}catch(i){this.handleError(i,"handleFocusIn")}};this.tabOffset=e.settings.tabOffset,this.initializeListeners()}initializeListeners(){let{signal:e}=this.abortController;document.addEventListener("keydown",this.handleKeyDown,{signal:e}),document.addEventListener("focusin",this.handleFocusIn,{signal:e})}invalidateCache(){this.tabbableElementsCache=[],this.lastFocusedIndex=null}cleanup(){this.abort(),this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}};var D=class o{constructor(){this.elements=new Map;this.trajectoryPositions={positions:new P(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.isSetup=!1;this._globalCallbackHits={mouse:{hover:0,trajectory:0},tab:{forwards:0,reverse:0},scroll:{down:0,left:0,right:0,up:0},total:0};this._globalSettings={debug:!1,enableMousePrediction:!0,enableScrollPrediction:!0,positionHistorySize:8,trajectoryPredictionTime:120,scrollMargin:150,defaultHitSlop:{top:0,left:0,right:0,bottom:0},enableTabPrediction:!0,tabOffset:2};this.domObserver=null;this.positionObserver=null;this.eventListeners=new Map;this.mousePredictor=null;this.tabPredictor=null;this.scrollPredictor=null;this.handleDomMutations=t=>{t.length&&this.tabPredictor?.invalidateCache();for(let e of t)if(e.type==="childList"&&e.removedNodes.length>0)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};this.handlePositionChange=t=>{for(let e of t){let i=this.elements.get(e.target);i&&(this._globalSettings.enableScrollPrediction?this.scrollPredictor?.handleScrollPrefetch(i,e.boundingClientRect):E(this.trajectoryPositions.currentPoint,i.elementBounds.expandedRect)&&this.callCallback(i,{kind:"mouse",subType:"hover"}),this.handlePositionChangeDataUpdates(i,e))}this._globalSettings.enableScrollPrediction&&this.scrollPredictor?.resetScrollProps()};this.handlePositionChangeDataUpdates=(t,e)=>{let i=[],n=e.isIntersecting;t.isIntersectingWithViewport!==n&&(i.push("visibility"),t.isIntersectingWithViewport=n),n&&(i.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:e.boundingClientRect,expandedRect:y(e.boundingClientRect,t.elementBounds.hitSlop)}),i.length&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:i})}}static initialize(t){return this.isInitiated||(o.manager=new o),t!==void 0&&o.manager.alterGlobalSettings(t),o.manager}addEventListener(t,e,i){if(i?.signal?.aborted)return()=>{};let n=this.eventListeners.get(t)??[];n.push(e),this.eventListeners.set(t,n),i?.signal?.addEventListener("abort",()=>this.removeEventListener(t,e))}removeEventListener(t,e){let i=this.eventListeners.get(t);if(!i)return;let n=i.indexOf(e);n>-1&&i.splice(n,1)}emit(t){let e=this.eventListeners.get(t.type);e&&e.forEach((i,n)=>{try{i(t)}catch(r){console.error(`Error in ForesightManager event listener ${n} for ${t.type}:`,r)}})}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners,historyPositions:this.trajectoryPositions}}static get isInitiated(){return!!o.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:t,callback:e,hitSlop:i,name:n}){let{shouldRegister:r,isTouchDevice:l,isLimitedConnection:s}=H();if(!r)return{isLimitedConnection:s,isTouchDevice:l,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let a=t.getBoundingClientRect(),c=i?F(i):this._globalSettings.defaultHitSlop,d={element:t,callback:e,elementBounds:{originalRect:a,expandedRect:y(a,c),hitSlop:c},isHovering:!1,trajectoryHitData:{isTrajectoryHit:!1,trajectoryHitTime:0,trajectoryHitExpirationTimeoutId:void 0},name:n||t.id||"unnamed",isIntersectingWithViewport:j(a),isRunningCallback:!1,registerCount:(this.registeredElements.get(t)?.registerCount??0)+1};return this.elements.set(t,d),this.positionObserver?.observe(t),this.emit({type:"elementRegistered",timestamp:Date.now(),elementData:d}),{isTouchDevice:l,isLimitedConnection:s,isRegistered:!0,unregister:()=>this.unregister(t,"apiCall")}}unregister(t,e){if(!this.elements.has(t))return;let i=this.elements.get(t);i?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(i.trajectoryHitData.trajectoryHitExpirationTimeoutId),this.positionObserver?.unobserve(t),this.elements.delete(t);let n=this.elements.size===0&&this.isSetup;n&&this.removeGlobalListeners(),i&&this.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:e,wasLastElement:n})}updateNumericSettings(t,e,i,n){return O(t,this._globalSettings[e])?(this._globalSettings[e]=u(t,i,n,e),!0):!1}updateBooleanSetting(t,e){return O(t,this._globalSettings[e])?(this._globalSettings[e]=t,!0):!1}alterGlobalSettings(t){let e=[],i=this._globalSettings.trajectoryPredictionTime;this.updateNumericSettings(t?.trajectoryPredictionTime,"trajectoryPredictionTime",10,200)&&e.push({setting:"trajectoryPredictionTime",oldValue:i,newValue:this._globalSettings.trajectoryPredictionTime});let r=this._globalSettings.positionHistorySize;this.updateNumericSettings(t?.positionHistorySize,"positionHistorySize",2,30)&&(e.push({setting:"positionHistorySize",oldValue:r,newValue:this._globalSettings.positionHistorySize}),this.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize));let s=this._globalSettings.scrollMargin;if(this.updateNumericSettings(t?.scrollMargin,"scrollMargin",30,300)){let p=this._globalSettings.scrollMargin;this.scrollPredictor&&(this.scrollPredictor.scrollMargin=p),e.push({setting:"scrollMargin",oldValue:s,newValue:p})}let c=this._globalSettings.tabOffset;this.updateNumericSettings(t?.tabOffset,"tabOffset",0,20)&&(this.tabPredictor&&(this.tabPredictor.tabOffset=this._globalSettings.tabOffset),e.push({setting:"tabOffset",oldValue:c,newValue:this._globalSettings.tabOffset}));let M=this._globalSettings.enableMousePrediction;this.updateBooleanSetting(t?.enableMousePrediction,"enableMousePrediction")&&e.push({setting:"enableMousePrediction",oldValue:M,newValue:this._globalSettings.enableMousePrediction});let C=this._globalSettings.enableScrollPrediction;this.updateBooleanSetting(t?.enableScrollPrediction,"enableScrollPrediction")&&(this._globalSettings.enableScrollPrediction?this.scrollPredictor=new b({dependencies:{callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements},settings:{scrollMargin:this._globalSettings.scrollMargin},trajectoryPositions:this.trajectoryPositions}):(this.scrollPredictor?.cleanup(),this.scrollPredictor=null),e.push({setting:"enableScrollPrediction",oldValue:C,newValue:this._globalSettings.enableScrollPrediction}));let R=this._globalSettings.enableTabPrediction;if(this.updateBooleanSetting(t?.enableTabPrediction,"enableTabPrediction")&&(e.push({setting:"enableTabPrediction",oldValue:R,newValue:this._globalSettings.enableTabPrediction}),this._globalSettings.enableTabPrediction?this.tabPredictor=new f({dependencies:{callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements},settings:{tabOffset:this._globalSettings.tabOffset}}):(this.tabPredictor?.cleanup(),this.tabPredictor=null)),t?.defaultHitSlop!==void 0){let p=this._globalSettings.defaultHitSlop,_=F(t.defaultHitSlop);x(p,_)||(this._globalSettings.defaultHitSlop=_,e.push({setting:"defaultHitSlop",oldValue:p,newValue:_}),this.forceUpdateAllElementBounds())}e.length>0&&this.emit({type:"managerSettingsChanged",timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:e})}updateHitCounters(t){switch(t.kind){case"mouse":this._globalCallbackHits.mouse[t.subType]++;break;case"tab":this._globalCallbackHits.tab[t.subType]++;break;case"scroll":this._globalCallbackHits.scroll[t.subType]++;break;default:}this._globalCallbackHits.total++}callCallback(t,e){if(t.isRunningCallback)return;let i=async()=>{this.updateHitCounters(e),this.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:t,hitType:e});let n=performance.now();try{await t.callback(),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:t,hitType:e,elapsed:performance.now()-n,status:"success"})}catch(r){let l=r instanceof Error?r.message:String(r);console.error(`Error in callback for element ${t.name} (${t.element.tagName}):`,r),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:t,hitType:e,elapsed:performance.now()-n,status:"error",errorMessage:l})}this.unregister(t.element,"callbackHit")};t.isRunningCallback=!0,i()}initializeGlobalListeners(){if(this.isSetup||typeof window>"u"||typeof document>"u")return;let t=this._globalSettings,e={callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements};t.enableTabPrediction&&(this.tabPredictor=new f({dependencies:e,settings:{tabOffset:t.tabOffset}})),t.enableScrollPrediction&&(this.scrollPredictor=new b({dependencies:e,settings:{scrollMargin:t.scrollMargin},trajectoryPositions:this.trajectoryPositions})),this.mousePredictor=new T({dependencies:e,settings:{enableMousePrediction:t.enableMousePrediction,trajectoryPredictionTime:t.trajectoryPredictionTime,positionHistorySize:t.positionHistorySize},trajectoryPositions:this.trajectoryPositions}),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.positionObserver=new rt(this.handlePositionChange),this.isSetup=!0}removeGlobalListeners(){this.isSetup=!1,this.domObserver?.disconnect(),this.domObserver=null,this.positionObserver?.disconnect(),this.positionObserver=null,this.mousePredictor?.cleanup(),this.tabPredictor?.cleanup(),this.scrollPredictor?.cleanup()}forceUpdateAllElementBounds(){for(let[,t]of this.elements)t.isIntersectingWithViewport&&this.forceUpdateElementBounds(t)}forceUpdateElementBounds(t){let e=t.element.getBoundingClientRect(),i=y(e,t.elementBounds.hitSlop);if(!x(i,t.elementBounds.expandedRect)){let n={...t,elementBounds:{...t.elementBounds,originalRect:e,expandedRect:i}};this.elements.set(t.element,n),this.emit({type:"elementDataUpdated",elementData:n,updatedProps:["bounds"]})}}};export{D as ForesightManager};
2
2
  //# sourceMappingURL=index.js.map