js.foresight 3.3.5 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,100 +1,100 @@
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
-
15
- ForesightJS is a lightweight JavaScript library that predicts user intent to prefetch content before it's needed. **It works completely out of the box without configuration**, supporting both desktop and mobile devices with different prediction strategies.
16
-
17
- ### [Playground](https://foresightjs.com/)
18
-
19
- ![](https://github.com/user-attachments/assets/f5650c63-4489-4878-bd72-d8954c6a739b)
20
- _In the GIF above, the [ForesightJS DevTools](https://foresightjs.com/docs/debugging/devtools) are enabled. Normally, users won't see anything that ForesightJS does except the increased perceived speed from early prefetching._
21
-
22
- ## Download
23
-
24
- ```bash
25
- pnpm add js.foresight
26
- # or
27
- npm install js.foresight
28
- # or
29
- yarn add js.foresight
30
- ```
31
-
32
- ## Basic Usage Example
33
-
34
- 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/react/useForesight).
35
-
36
- ```javascript
37
- import { ForesightManager } from "foresightjs"
38
-
39
- // Initialize the manager if you want custom global settings (do this once at app startup)
40
- ForesightManager.initialize({
41
- // Optional props (see configuration)
42
- })
43
-
44
- // Register an element to be tracked
45
- const myButton = document.getElementById("my-button")
46
-
47
- ForesightManager.instance.register({
48
- element: myButton,
49
- callback: () => {
50
- // This is where your prefetching logic goes
51
- },
52
- hitSlop: 20, // Optional: "hit slop" in pixels.
53
- // other optional props (see configuration)
54
- })
55
- ```
56
-
57
- ## Integrations
58
-
59
- Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. Ready-to-use implementations are available for:
60
-
61
- - **React**: [Next.js](https://foresightjs.com/docs/react/nextjs), [React Router](https://foresightjs.com/docs/react/react-router), and [useForesight hook](https://foresightjs.com/docs/react/hook)
62
- - **Vue**: [Directive](https://foresightjs.com/docs/vue/directive) and [Composable](https://foresightjs.com/docs/vue/composable)
63
-
64
- Sharing integrations for other frameworks/packages is highly appreciated!
65
-
66
- ## Configuration
67
-
68
- ForesightJS works out of the box with no setup required, but it can be configured both [globally](https://foresightjs.com/docs/configuration/global-settings) and per [element](https://foresightjs.com/docs/configuration/element-settings) if needed.
69
-
70
- ## Development Tools
71
-
72
- ForesightJS has dedicated [Development Tools](https://github.com/spaansba/ForesightJS/tree/main/packages/js.foresight-devtools) created with [Foresight Events](https://foresightjs.com/docs/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.
73
-
74
- ```bash
75
- pnpm add js.foresight-devtools
76
- ```
77
-
78
- See the [development tools documentation](https://foresightjs.com/docs/debugging/devtools) for more details.
79
-
80
- ## Prediction Strategies
81
-
82
- ForesightJS uses different prediction strategies depending on the device type:
83
-
84
- **Desktop/Keyboard Users**: Mouse trajectory prediction, keyboard navigation tracking, and scroll-based prefetching. [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#keyboardmouse-users)
85
-
86
- **Mobile Devices**: Viewport enter detection and touch start events (configurable via [`touchDeviceStrategy`]). [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#touch-devices-v330)
87
-
88
- ## Providing Context to AI Tools
89
-
90
- 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:
91
-
92
- - Use [llms.txt](https://foresightjs.com/llms.txt) for a concise overview of the API and usage patterns.
93
- - 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.
94
- - 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.
95
-
96
- [Read more](https://foresightjs.com/docs/ai-context)
97
-
98
- # Contributing
99
-
100
- 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
+
15
+ ForesightJS is a lightweight JavaScript library that predicts user intent to prefetch content before it's needed. **It works completely out of the box without configuration**, supporting both desktop and mobile devices with different prediction strategies.
16
+
17
+ ### [Playground](https://foresightjs.com/)
18
+
19
+ ![](https://github.com/user-attachments/assets/f5650c63-4489-4878-bd72-d8954c6a739b)
20
+ _In the GIF above, the [ForesightJS DevTools](https://foresightjs.com/docs/debugging/devtools) are enabled. Normally, users won't see anything that ForesightJS does except the increased perceived speed from early prefetching._
21
+
22
+ ## Download
23
+
24
+ ```bash
25
+ pnpm add js.foresight
26
+ # or
27
+ npm install js.foresight
28
+ # or
29
+ yarn add js.foresight
30
+ ```
31
+
32
+ ## Basic Usage Example
33
+
34
+ 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/react/useForesight).
35
+
36
+ ```javascript
37
+ import { ForesightManager } from "js.foresightjs"
38
+
39
+ // Initialize the manager if you want custom global settings (do this once at app startup)
40
+ ForesightManager.initialize({
41
+ // Optional props (see configuration)
42
+ })
43
+
44
+ // Register an element to be tracked
45
+ const myButton = document.getElementById("my-button")
46
+
47
+ ForesightManager.instance.register({
48
+ element: myButton,
49
+ callback: () => {
50
+ // This is where your prefetching logic goes
51
+ },
52
+ hitSlop: 20, // Optional: "hit slop" in pixels.
53
+ // other optional props (see configuration)
54
+ })
55
+ ```
56
+
57
+ ## Integrations
58
+
59
+ Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. Ready-to-use implementations are available for:
60
+
61
+ - **React**: [Next.js](https://foresightjs.com/docs/react/nextjs), [React Router](https://foresightjs.com/docs/react/react-router), and [useForesight hook](https://foresightjs.com/docs/react/hook)
62
+ - **Vue**: [Directive](https://foresightjs.com/docs/vue/directive) and [Composable](https://foresightjs.com/docs/vue/composable)
63
+
64
+ Sharing integrations for other frameworks/packages is highly appreciated!
65
+
66
+ ## Configuration
67
+
68
+ ForesightJS works out of the box with no setup required, but it can be configured both [globally](https://foresightjs.com/docs/configuration/global-settings) and per [element](https://foresightjs.com/docs/configuration/element-settings) if needed.
69
+
70
+ ## Development Tools
71
+
72
+ ForesightJS has dedicated [Development Tools](https://github.com/spaansba/ForesightJS/tree/main/packages/js.foresight-devtools) created with [Foresight Events](https://foresightjs.com/docs/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.
73
+
74
+ ```bash
75
+ pnpm add js.foresight-devtools
76
+ ```
77
+
78
+ See the [development tools documentation](https://foresightjs.com/docs/debugging/devtools) for more details.
79
+
80
+ ## Prediction Strategies
81
+
82
+ ForesightJS uses different prediction strategies depending on the device type:
83
+
84
+ **Desktop/Keyboard Users**: Mouse trajectory prediction, keyboard navigation tracking, and scroll-based prefetching. [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#keyboardmouse-users)
85
+
86
+ **Mobile Devices**: Viewport enter detection and touch start events (configurable via [`touchDeviceStrategy`]). [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#touch-devices-v330)
87
+
88
+ ## Providing Context to AI Tools
89
+
90
+ 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:
91
+
92
+ - Use [llms.txt](https://foresightjs.com/llms.txt) for a concise overview of the API and usage patterns.
93
+ - 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.
94
+ - 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.
95
+
96
+ [Read more](https://foresightjs.com/docs/ai-context)
97
+
98
+ # Contributing
99
+
100
+ Please see the [contributing guidelines](/CONTRIBUTING.md)
@@ -0,0 +1 @@
1
+ import{c as f,d as b,e as d}from"./chunk-PAYO6NXN.js";import{a as m}from"./chunk-44N4MCQB.js";import{a as l}from"./chunk-AODZNE3S.js";function y(n,i,t,e){let r=performance.now();i.add({point:{x:n.x,y:n.y},time:r});let{x:o,y:s}=n;if(i.length<2){e.x=o,e.y=s;return}let[c,a]=i.getFirstLast();if(!c||!a){e.x=o,e.y=s;return}let p=(a.time-c.time)*.001;if(p===0){e.x=o,e.y=s;return}let v=a.point.x-c.point.x,T=a.point.y-c.point.y,x=v/p,M=T/p,P=t*.001;e.x=o+x*P,e.y=s+M*P}var h=class extends l{constructor(t){super(t.dependencies);this.moduleName="MousePredictor";this.trajectoryPositions=t.trajectoryPositions}updatePointerState(t){let e=this.trajectoryPositions.currentPoint;e.x=t.clientX,e.y=t.clientY,this.settings.enableMousePrediction?y(e,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime,this.trajectoryPositions.predictedPoint):(this.trajectoryPositions.predictedPoint.x=e.x,this.trajectoryPositions.predictedPoint.y=e.y)}processMouseMovement(t){this.updatePointerState(t);let e=this.settings.enableMousePrediction,r=this.trajectoryPositions.currentPoint;for(let o of this.elements.values()){if(!o.isIntersectingWithViewport||!o.callbackInfo.isCallbackActive||o.callbackInfo.isRunningCallback)continue;let s=o.elementBounds.expandedRect;if(e)m(r,this.trajectoryPositions.predictedPoint,s)&&this.callCallback(o,{kind:"mouse",subType:"trajectory"});else if(d(r,s)){this.callCallback(o,{kind:"mouse",subType:"hover"});return}}this.hasListeners("mouseTrajectoryUpdate")&&this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:e,trajectoryPositions:this.trajectoryPositions})}onDisconnect(){}onConnect(){}};import{PositionObserver as j}from"position-observer";var u=class{constructor(i){this.head=0;this.count=0;if(i<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=i,this.buffer=new Array(i)}add(i){this.buffer[this.head]=i,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 i=(this.head-1+this.capacity)%this.capacity;return this.buffer[i]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let e=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[e,e]}let i=this.getFirst(),t=this.getLast();return[i,t]}resize(i){if(i<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(i===this.capacity)return;let t=this.getAllItems();if(this.capacity=i,this.buffer=new Array(i),this.head=0,this.count=0,t.length>i){let e=t.slice(-i);for(let r of e)this.add(r)}else for(let e of t)this.add(e)}getAllItems(){if(this.count===0)return[];let i=new Array(this.count);if(this.count<this.capacity)for(let t=0;t<this.count;t++)i[t]=this.buffer[t];else{let t=this.head;for(let e=0;e<this.capacity;e++){let r=(t+e)%this.capacity;i[e]=this.buffer[r]}}return i}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}};var g=class extends l{constructor(t){super(t);this.moduleName="DesktopHandler";this.tabPredictor=null;this.scrollPredictor=null;this.positionObserver=null;this.trajectoryPositions={positions:new u(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.handlePositionChange=t=>{let e=this.settings.enableScrollPrediction;for(let r of t){let o=this.elements.get(r.target);o&&(e?this.scrollPredictor?.handleScrollPrefetch(o,r.boundingClientRect):this.checkForMouseHover(o),this.handlePositionChangeDataUpdates(o,r))}e&&this.scrollPredictor?.resetScrollProps()};this.checkForMouseHover=t=>{d(this.trajectoryPositions.currentPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"mouse",subType:"hover"})};this.handlePositionChangeDataUpdates=(t,e)=>{let r=[],o=e.isIntersecting;t.isIntersectingWithViewport!==o&&(r.push("visibility"),t.isIntersectingWithViewport=o),o&&!b(e.boundingClientRect,t.elementBounds.originalRect)&&(r.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:e.boundingClientRect,expandedRect:f(e.boundingClientRect,t.elementBounds.hitSlop)}),r.length&&this.hasListeners("elementDataUpdated")&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:r})};this.processMouseMovement=t=>this.mousePredictor.processMouseMovement(t);this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache();this.observeElement=t=>this.positionObserver?.observe(t);this.unobserveElement=t=>this.positionObserver?.unobserve(t);this.connectTabPredictor=async()=>{if(!this.tabPredictor){let{TabPredictor:t}=await import("./TabPredictor-HA2SV3CY.js");this.tabPredictor=new t(this.storedDependencies),this.devLog("TabPredictor lazy loaded")}this.tabPredictor.connect()};this.connectScrollPredictor=async()=>{if(!this.scrollPredictor){let{ScrollPredictor:t}=await import("./ScrollPredictor-Y7NELMBI.js");this.scrollPredictor=new t({dependencies:this.storedDependencies,trajectoryPositions:this.trajectoryPositions}),this.devLog("ScrollPredictor lazy loaded")}this.scrollPredictor.connect()};this.connectMousePredictor=()=>this.mousePredictor.connect();this.disconnectTabPredictor=()=>this.tabPredictor?.disconnect();this.disconnectScrollPredictor=()=>this.scrollPredictor?.disconnect();this.disconnectMousePredictor=()=>this.mousePredictor.disconnect();this.storedDependencies=t,this.mousePredictor=new h({dependencies:t,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new j(this.handlePositionChange);let t=["mouse"];this.settings.enableTabPrediction&&t.push("tab (loading...)"),this.settings.enableScrollPrediction&&t.push("scroll (loading...)"),this.devLog(`Connected predictors: [${t.join(", ")}] and PositionObserver`);for(let e of this.elements.keys())this.positionObserver.observe(e)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}get loadedPredictors(){return{mouse:this.mousePredictor!==null,tab:this.tabPredictor!==null,scroll:this.scrollPredictor!==null}}};export{g as DesktopHandler};
@@ -0,0 +1 @@
1
+ import{a as s}from"./chunk-44N4MCQB.js";import{a as l}from"./chunk-AODZNE3S.js";function n(r,e){let o=e.top-r.top,c=e.left-r.left;return o<-1?"down":o>1?"up":c<-1?"right":c>1?"left":"none"}function p(r,e,t){let{x:o,y:c}=r,i={x:o,y:c};switch(e){case"down":i.y+=t;break;case"up":i.y-=t;break;case"left":i.x-=t;break;case"right":i.x+=t;break;case"none":break;default:}return i}var d=class extends l{constructor(t){super(t.dependencies);this.moduleName="ScrollPredictor";this.predictedScrollPoint=null;this.scrollDirection=null;this.onDisconnect=()=>this.resetScrollProps();this.trajectoryPositions=t.trajectoryPositions}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(t,o){!t.isIntersectingWithViewport||t.callbackInfo.isRunningCallback||!t.callbackInfo.isCallbackActive||(this.scrollDirection=this.scrollDirection??n(t.elementBounds.originalRect,o),this.scrollDirection!=="none"&&(this.predictedScrollPoint=this.predictedScrollPoint??p(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),s(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"scroll",subType:this.scrollDirection}),this.hasListeners("scrollTrajectoryUpdate")&&this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})))}onConnect(){}};export{d as ScrollPredictor};
@@ -0,0 +1 @@
1
+ import{a as b}from"./chunk-AODZNE3S.js";import{tabbable as f}from"tabbable";function u(r,l,e,o){if(l!==null&&l>-1){let t=r?l-1:l+1;if(t>=0&&t<e.length&&e[t]===o)return t}return e.findIndex(t=>t===o)}var h=class extends b{constructor(e){super(e);this.moduleName="TabPredictor";this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=e=>{e.key==="Tab"&&(this.lastKeyDown=e)};this.handleFocusIn=e=>{if(!this.lastKeyDown)return;let o=e.target;if(!(o instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.devLog("Caching tabbable elements"),this.tabbableElementsCache=f(document.documentElement));let t=this.lastKeyDown.shiftKey,i=u(t,this.lastFocusedIndex,this.tabbableElementsCache,o);this.lastFocusedIndex=i,this.lastKeyDown=null;let c=[],m=this.settings.tabOffset,d=this.elements;for(let n=0;n<=m;n++){let s=t?i-n:i+n,a=this.tabbableElementsCache[s];a&&a instanceof Element&&d.has(a)&&c.push(a)}for(let n of c){let s=d.get(n);s&&!s.callbackInfo.isRunningCallback&&s.callbackInfo.isCallbackActive&&this.callCallback(s,{kind:"tab",subType:t?"reverse":"forwards"})}}}invalidateCache(){this.tabbableElementsCache.length&&this.devLog("Invalidating tabbable elements cache"),this.tabbableElementsCache=[],this.lastFocusedIndex=null}onConnect(){typeof document>"u"||(this.createAbortController(),document.addEventListener("keydown",this.handleKeyDown,{signal:this.abortController?.signal,passive:!0}),document.addEventListener("focusin",this.handleFocusIn,{signal:this.abortController?.signal,passive:!0}))}onDisconnect(){this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}};export{h as TabPredictor};
@@ -0,0 +1 @@
1
+ import{a as e}from"./chunk-AODZNE3S.js";var r=class extends e{constructor(t){super(t);this.moduleName="TouchDeviceHandler";this.viewportPredictor=null;this.touchStartPredictor=null;this.predictor=null;this.onDisconnect=()=>{this.devLog("Disconnecting touch predictor"),this.predictor?.disconnect()};this.onConnect=()=>this.setTouchPredictor();this.observeElement=t=>this.predictor?.observeElement(t);this.unobserveElement=t=>this.predictor?.unobserveElement(t);this.storedDependencies=t}async getOrCreateViewportPredictor(){if(!this.viewportPredictor){let{ViewportPredictor:t}=await import("./ViewportPredictor-H3GLDETY.js");this.viewportPredictor=new t(this.storedDependencies),this.devLog("ViewportPredictor lazy loaded")}return this.viewportPredictor}async getOrCreateTouchStartPredictor(){if(!this.touchStartPredictor){let{TouchStartPredictor:t}=await import("./TouchStartPredictor-ZH3KJG2C.js");this.touchStartPredictor=new t(this.storedDependencies),this.devLog("TouchStartPredictor lazy loaded")}return this.touchStartPredictor}async setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case"viewport":this.predictor=await this.getOrCreateViewportPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`);break;case"onTouchStart":this.predictor=await this.getOrCreateTouchStartPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`);break;case"none":this.predictor=null,this.devLog('Touch strategy set to "none" - no predictor connected');return;default:this.settings.touchDeviceStrategy}this.predictor?.connect();for(let t of this.elements.keys())this.predictor?.observeElement(t)}get loadedPredictors(){return{viewport:this.viewportPredictor!==null,touchStart:this.touchStartPredictor!==null}}};export{r as TouchDeviceHandler};
@@ -0,0 +1 @@
1
+ import{a as n}from"./chunk-AODZNE3S.js";var r=class extends n{constructor(e){super(e);this.moduleName="TouchStartPredictor";this.onConnect=()=>this.createAbortController();this.onDisconnect=()=>{};this.handleTouchStart=e=>{let t=e.currentTarget,o=this.elements.get(t);o&&(this.callCallback(o,{kind:"touch"}),this.unobserveElement(t))}}observeElement(e){e instanceof HTMLElement&&e.addEventListener("pointerdown",this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(e){e instanceof HTMLElement&&e.removeEventListener("pointerdown",this.handleTouchStart)}};export{r as TouchStartPredictor};
@@ -0,0 +1 @@
1
+ import{a as n}from"./chunk-AODZNE3S.js";var s=class extends n{constructor(e){super(e);this.moduleName="ViewportPredictor";this.intersectionObserver=null;this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter);this.observeElement=e=>this.intersectionObserver?.observe(e);this.unobserveElement=e=>this.intersectionObserver?.unobserve(e);this.handleViewportEnter=e=>{for(let t of e){if(!t.isIntersecting)continue;let r=this.elements.get(t.target);r&&(this.callCallback(r,{kind:"viewport"}),this.unobserveElement(t.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}};export{s as ViewportPredictor};
@@ -0,0 +1 @@
1
+ function y(e,s,n){let r=0,f=1,l=s.x-e.x,u=s.y-e.y,i=(o,a)=>{if(o===0){if(a<0)return!1}else{let t=a/o;if(o<0){if(t>f)return!1;t>r&&(r=t)}else{if(t<r)return!1;t<f&&(f=t)}}return!0};return!i(-l,e.x-n.left)||!i(l,n.right-e.x)||!i(-u,e.y-n.top)||!i(u,n.bottom-e.y)?!1:r<=f}export{y as a};
@@ -0,0 +1 @@
1
+ var e=class{constructor(t){this._isConnected=!1;this._cachedLogStyle=null;this.elements=t.elements,this.callCallback=t.callCallback,this.emit=t.emit,this.hasListeners=t.hasListeners,this.settings=t.settings}get isConnected(){return this._isConnected}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0}devLog(t){if(this.settings.enableManagerLogging){if(this._cachedLogStyle===null){let o=this.moduleName.includes("Predictor")?"#ea580c":"#2563eb";this._cachedLogStyle=`color: ${o}; font-weight: bold;`}console.log(`%c${this.moduleName}: ${t}`,this._cachedLogStyle)}}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};export{e as a};
@@ -0,0 +1 @@
1
+ function i(t,o,e,r){return t<o?console.warn(`ForesightJS: "${r}" value ${t} is below minimum bound ${o}, clamping to ${o}`):t>e&&console.warn(`ForesightJS: "${r}" value ${t} is above maximum bound ${e}, clamping to ${e}`),Math.min(Math.max(t,o),e)}function p(t){if(typeof t=="number"){let o=i(t,0,2e3,"hitslop");return{top:o,left:o,right:o,bottom:o}}return{top:i(t.top,0,2e3,"hitslop - top"),left:i(t.left,0,2e3,"hitslop - left"),right:i(t.right,0,2e3,"hitslop - right"),bottom:i(t.bottom,0,2e3,"hitslop - bottom")}}function a(t,o){return{left:t.left-o.left,right:t.right+o.right,top:t.top-o.top,bottom:t.bottom+o.bottom}}function g(t,o){return!t||!o?t===o:t.left===o.left&&t.right===o.right&&t.top===o.top&&t.bottom===o.bottom}function b(t,o){return t.x>=o.left&&t.x<=o.right&&t.y>=o.top&&t.y<=o.bottom}export{i as a,p as b,a as c,g as d,b as e};
package/dist/index.d.ts CHANGED
@@ -191,6 +191,17 @@ type ForesightManagerData = {
191
191
  eventListeners: ReadonlyMap<keyof ForesightEventMap, ForesightEventListener[]>;
192
192
  currentDeviceStrategy: CurrentDeviceStrategy;
193
193
  activeElementCount: number;
194
+ loadedModules: {
195
+ desktopHandler: boolean;
196
+ touchHandler: boolean;
197
+ predictors: {
198
+ mouse: boolean;
199
+ tab: boolean;
200
+ scroll: boolean;
201
+ viewport: boolean;
202
+ touchStart: boolean;
203
+ };
204
+ };
194
205
  };
195
206
  type TouchDeviceStrategy = "none" | "viewport" | "onTouchStart";
196
207
  type MinimumConnectionType = "slow-2g" | "2g" | "3g" | "4g";
@@ -455,7 +466,8 @@ declare class ForesightManager {
455
466
  private activeElementCount;
456
467
  private desktopHandler;
457
468
  private touchDeviceHandler;
458
- private handler;
469
+ private currentlyActiveHandler;
470
+ private handlerDependencies;
459
471
  private isSetup;
460
472
  private pendingPointerEvent;
461
473
  private rafId;
@@ -465,10 +477,13 @@ declare class ForesightManager {
465
477
  private _globalCallbackHits;
466
478
  private _globalSettings;
467
479
  private constructor();
480
+ private getOrCreateDesktopHandler;
481
+ private getOrCreateTouchHandler;
468
482
  static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager;
469
483
  static get isInitiated(): Readonly<boolean>;
470
484
  static get instance(): ForesightManager;
471
485
  private generateId;
486
+ private get isUsingDesktopHandler();
472
487
  addEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>, options?: {
473
488
  signal?: AbortSignal;
474
489
  }): void;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function p(n,e,t,i){return n<e?console.warn(`ForesightJS: "${i}" value ${n} is below minimum bound ${e}, clamping to ${e}`):n>t&&console.warn(`ForesightJS: "${i}" value ${n} is above maximum bound ${t}, clamping to ${t}`),Math.min(Math.max(n,e),t)}function E(n){if(typeof n=="number"){let e=p(n,0,2e3,"hitslop");return{top:e,left:e,right:e,bottom:e}}return{top:p(n.top,0,2e3,"hitslop - top"),left:p(n.left,0,2e3,"hitslop - left"),right:p(n.right,0,2e3,"hitslop - right"),bottom:p(n.bottom,0,2e3,"hitslop - bottom")}}function g(n,e){return{left:n.left-e.left,right:n.right+e.right,top:n.top-e.top,bottom:n.bottom+e.bottom}}function b(n,e){return!n||!e?n===e:n.left===e.left&&n.right===e.right&&n.top===e.top&&n.bottom===e.bottom}function T(n,e){return n.x>=e.left&&n.x<=e.right&&n.y>=e.top&&n.y<=e.bottom}function N(){let n=O(),e=Z();return{isTouchDevice:n,isLimitedConnection:e,shouldRegister:!e}}function O(){return typeof window>"u"||typeof navigator>"u"?!1:window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function Z(){let n=navigator.connection;if(!n)return!1;let e=S.instance.getManagerData.globalSettings.minimumConnectionType,t=["slow-2g","2g","3g","4g"],i=t.indexOf(n.effectiveType),o=t.indexOf(e);return i<o||n.saveData}function U(n){if(typeof window>"u"||typeof document>"u")return!1;let e=window.innerWidth||document.documentElement.clientWidth,t=window.innerHeight||document.documentElement.clientHeight;return n.top<t&&n.bottom>0&&n.left<e&&n.right>0}function j(){return{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}}function B(){return{debug:!1,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"}}function K(n,e,t){let{element:i,callback:o,hitSlop:r,name:a,meta:c,reactivateAfter:h}=n,l=i.getBoundingClientRect(),s=r?E(r):t;return{id:e,element:i,callback:o,elementBounds:{originalRect:l,expandedRect:g(l,s),hitSlop:s},name:a||i.id||"unnamed",isIntersectingWithViewport:U(l),registerCount:1,meta:c??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:h??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}}}var I=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,i){if(i?.signal?.aborted)return;let o=this.eventListeners.get(e)??[];o.push(t),this.eventListeners.set(e,o),i?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let i=this.eventListeners.get(e);if(!i)return;let o=i.indexOf(t);o>-1&&i.splice(o,1)}emit(e){let t=this.eventListeners.get(e.type);if(!(!t||t.length===0))for(let i=0;i<t.length;i++)try{let o=t[i];o&&o(e)}catch(o){console.error(`Error in ForesightManager event listener ${i} for ${e.type}:`,o)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};function w(n,e){return n!==void 0&&e!==n}var pe={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}};function C(n,e,t){if(!w(t,n[e]))return!1;let{min:i,max:o}=pe[e];return n[e]=p(t,i,o,e),!0}function P(n,e,t){return w(t,n[e])?(n[e]=t,!0):!1}function z(n,e){C(n,"trajectoryPredictionTime",e.trajectoryPredictionTime),C(n,"positionHistorySize",e.positionHistorySize),C(n,"scrollMargin",e.scrollMargin),C(n,"tabOffset",e.tabOffset),P(n,"enableMousePrediction",e.enableMousePrediction),P(n,"enableScrollPrediction",e.enableScrollPrediction),P(n,"enableTabPrediction",e.enableTabPrediction),P(n,"enableManagerLogging",e.enableManagerLogging),e.defaultHitSlop!==void 0&&(n.defaultHitSlop=E(e.defaultHitSlop)),e.touchDeviceStrategy!==void 0&&(n.touchDeviceStrategy=e.touchDeviceStrategy),e.minimumConnectionType!==void 0&&(n.minimumConnectionType=e.minimumConnectionType),e.debug!==void 0&&(n.debug=e.debug)}function V(n,e){let t=[],i=!1,o=!1,r=!1,a=!1,c=!1;if(!e)return{changedSettings:t,positionHistorySizeChanged:i,scrollPredictionChanged:o,tabPredictionChanged:r,hitSlopChanged:a,touchStrategyChanged:c};let h=["trajectoryPredictionTime","positionHistorySize","scrollMargin","tabOffset"];for(let s of h){let u=n[s];C(n,s,e[s])&&(t.push({setting:s,oldValue:u,newValue:n[s]}),s==="positionHistorySize"&&(i=!0))}let l=["enableMousePrediction","enableScrollPrediction","enableTabPrediction"];for(let s of l){let u=n[s];P(n,s,e[s])&&(t.push({setting:s,oldValue:u,newValue:n[s]}),s==="enableScrollPrediction"&&(o=!0),s==="enableTabPrediction"&&(r=!0))}if(e.defaultHitSlop!==void 0){let s=n.defaultHitSlop,u=E(e.defaultHitSlop);b(s,u)||(n.defaultHitSlop=u,t.push({setting:"defaultHitSlop",oldValue:s,newValue:u}),a=!0)}if(e.touchDeviceStrategy!==void 0){let s=n.touchDeviceStrategy;n.touchDeviceStrategy=e.touchDeviceStrategy,t.push({setting:"touchDeviceStrategy",oldValue:s,newValue:e.touchDeviceStrategy}),c=!0}if(e.minimumConnectionType!==void 0){let s=n.minimumConnectionType;n.minimumConnectionType=e.minimumConnectionType,t.push({setting:"minimumConnectionType",oldValue:s,newValue:e.minimumConnectionType})}return{changedSettings:t,positionHistorySizeChanged:i,scrollPredictionChanged:o,tabPredictionChanged:r,hitSlopChanged:a,touchStrategyChanged:c}}var d=class{constructor(e){this._isConnected=!1;this._cachedLogStyle=null;this.elements=e.elements,this.callCallback=e.callCallback,this.emit=e.emit,this.hasListeners=e.hasListeners,this.settings=e.settings}get isConnected(){return this._isConnected}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0}devLog(e){if(this.settings.enableManagerLogging){if(this._cachedLogStyle===null){let t=this.moduleName.includes("Predictor")?"#ea580c":"#2563eb";this._cachedLogStyle=`color: ${t}; font-weight: bold;`}console.log(`%c${this.moduleName}: ${e}`,this._cachedLogStyle)}}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};function M(n,e,t){let i=0,o=1,r=e.x-n.x,a=e.y-n.y,c=(h,l)=>{if(h===0){if(l<0)return!1}else{let s=l/h;if(h<0){if(s>o)return!1;s>i&&(i=s)}else{if(s<i)return!1;s<o&&(o=s)}}return!0};return!c(-r,n.x-t.left)||!c(r,t.right-n.x)||!c(-a,n.y-t.top)||!c(a,t.bottom-n.y)?!1:i<=o}function $(n,e,t,i){let o=performance.now();e.add({point:{x:n.x,y:n.y},time:o});let{x:r,y:a}=n;if(e.length<2){i.x=r,i.y=a;return}let[c,h]=e.getFirstLast();if(!c||!h){i.x=r,i.y=a;return}let l=(h.time-c.time)*.001;if(l===0){i.x=r,i.y=a;return}let s=h.point.x-c.point.x,u=h.point.y-c.point.y,W=s/l,J=u/l,H=t*.001;i.x=r+W*H,i.y=a+J*H}var k=class extends d{constructor(t){super(t.dependencies);this.moduleName="MousePredictor";this.trajectoryPositions=t.trajectoryPositions}updatePointerState(t){let i=this.trajectoryPositions.currentPoint;i.x=t.clientX,i.y=t.clientY,this.settings.enableMousePrediction?$(i,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime,this.trajectoryPositions.predictedPoint):(this.trajectoryPositions.predictedPoint.x=i.x,this.trajectoryPositions.predictedPoint.y=i.y)}processMouseMovement(t){this.updatePointerState(t);let i=this.settings.enableMousePrediction,o=this.trajectoryPositions.currentPoint;for(let r of this.elements.values()){if(!r.isIntersectingWithViewport||!r.callbackInfo.isCallbackActive||r.callbackInfo.isRunningCallback)continue;let a=r.elementBounds.expandedRect;if(i)M(o,this.trajectoryPositions.predictedPoint,a)&&this.callCallback(r,{kind:"mouse",subType:"trajectory"});else if(T(o,a)){this.callCallback(r,{kind:"mouse",subType:"hover"});return}}this.hasListeners("mouseTrajectoryUpdate")&&this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:i,trajectoryPositions:this.trajectoryPositions})}onDisconnect(){}onConnect(){}};function G(n,e){let i=e.top-n.top,o=e.left-n.left;return i<-1?"down":i>1?"up":o<-1?"right":o>1?"left":"none"}function Y(n,e,t){let{x:i,y:o}=n,r={x:i,y:o};switch(e){case"down":r.y+=t;break;case"up":r.y-=t;break;case"left":r.x-=t;break;case"right":r.x+=t;break;case"none":break;default:}return r}var R=class extends d{constructor(t){super(t.dependencies);this.moduleName="ScrollPredictor";this.predictedScrollPoint=null;this.scrollDirection=null;this.onDisconnect=()=>this.resetScrollProps();this.trajectoryPositions=t.trajectoryPositions}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(t,i){!t.isIntersectingWithViewport||t.callbackInfo.isRunningCallback||!t.callbackInfo.isCallbackActive||(this.scrollDirection=this.scrollDirection??G(t.elementBounds.originalRect,i),this.scrollDirection!=="none"&&(this.predictedScrollPoint=this.predictedScrollPoint??Y(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),M(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"scroll",subType:this.scrollDirection}),this.hasListeners("scrollTrajectoryUpdate")&&this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})))}onConnect(){}};import{tabbable as me}from"tabbable";function X(n,e,t,i){if(e!==null&&e>-1){let o=n?e-1:e+1;if(o>=0&&o<t.length&&t[o]===i)return o}return t.findIndex(o=>o===i)}var x=class extends d{constructor(t){super(t);this.moduleName="TabPredictor";this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=t=>{t.key==="Tab"&&(this.lastKeyDown=t)};this.handleFocusIn=t=>{if(!this.lastKeyDown)return;let i=t.target;if(!(i instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.devLog("Caching tabbable elements"),this.tabbableElementsCache=me(document.documentElement));let o=this.lastKeyDown.shiftKey,r=X(o,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let a=[],c=this.settings.tabOffset,h=this.elements;for(let l=0;l<=c;l++){let s=o?r-l:r+l,u=this.tabbableElementsCache[s];u&&u instanceof Element&&h.has(u)&&a.push(u)}for(let l of a){let s=h.get(l);s&&!s.callbackInfo.isRunningCallback&&s.callbackInfo.isCallbackActive&&this.callCallback(s,{kind:"tab",subType:o?"reverse":"forwards"})}}}invalidateCache(){this.tabbableElementsCache.length&&this.devLog("Invalidating tabbable elements cache"),this.tabbableElementsCache=[],this.lastFocusedIndex=null}onConnect(){typeof document>"u"||(this.createAbortController(),document.addEventListener("keydown",this.handleKeyDown,{signal:this.abortController?.signal,passive:!0}),document.addEventListener("focusin",this.handleFocusIn,{signal:this.abortController?.signal,passive:!0}))}onDisconnect(){this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}};import{PositionObserver as ge}from"position-observer";var D=class{constructor(e){this.head=0;this.count=0;if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=e,this.buffer=new Array(e)}add(e){this.buffer[this.head]=e,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 e=(this.head-1+this.capacity)%this.capacity;return this.buffer[e]}}}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 e=this.getFirst(),t=this.getLast();return[e,t]}resize(e){if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(e===this.capacity)return;let t=this.getAllItems();if(this.capacity=e,this.buffer=new Array(e),this.head=0,this.count=0,t.length>e){let i=t.slice(-e);for(let o of i)this.add(o)}else for(let i of t)this.add(i)}getAllItems(){if(this.count===0)return[];let e=new Array(this.count);if(this.count<this.capacity)for(let t=0;t<this.count;t++)e[t]=this.buffer[t];else{let t=this.head;for(let i=0;i<this.capacity;i++){let o=(t+i)%this.capacity;e[i]=this.buffer[o]}}return e}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}};var m=class extends d{constructor(t){super(t);this.moduleName="DesktopHandler";this.positionObserver=null;this.trajectoryPositions={positions:new D(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.handlePositionChange=t=>{let i=this.settings.enableScrollPrediction;for(let o of t){let r=this.elements.get(o.target);r&&(i?this.scrollPredictor?.handleScrollPrefetch(r,o.boundingClientRect):this.checkForMouseHover(r),this.handlePositionChangeDataUpdates(r,o))}i&&this.scrollPredictor?.resetScrollProps()};this.checkForMouseHover=t=>{T(this.trajectoryPositions.currentPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"mouse",subType:"hover"})};this.handlePositionChangeDataUpdates=(t,i)=>{let o=[],r=i.isIntersecting;t.isIntersectingWithViewport!==r&&(o.push("visibility"),t.isIntersectingWithViewport=r),r&&!b(i.boundingClientRect,t.elementBounds.originalRect)&&(o.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:i.boundingClientRect,expandedRect:g(i.boundingClientRect,t.elementBounds.hitSlop)}),o.length&&this.hasListeners("elementDataUpdated")&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:o})};this.processMouseMovement=t=>this.mousePredictor.processMouseMovement(t);this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache();this.observeElement=t=>this.positionObserver?.observe(t);this.unobserveElement=t=>this.positionObserver?.unobserve(t);this.connectTabPredictor=()=>this.tabPredictor.connect();this.connectScrollPredictor=()=>this.scrollPredictor.connect();this.connectMousePredictor=()=>this.mousePredictor.connect();this.disconnectTabPredictor=()=>this.tabPredictor.disconnect();this.disconnectScrollPredictor=()=>this.scrollPredictor.disconnect();this.disconnectMousePredictor=()=>this.mousePredictor.disconnect();this.tabPredictor=new x(t),this.scrollPredictor=new R({dependencies:t,trajectoryPositions:this.trajectoryPositions}),this.mousePredictor=new k({dependencies:t,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new ge(this.handlePositionChange);let t=["mouse"];this.settings.enableTabPrediction&&t.push("tab"),this.settings.enableScrollPrediction&&t.push("scroll"),this.devLog(`Connected predictors: [${t.join(", ")}] and PositionObserver`);for(let i of this.elements.keys())this.positionObserver.observe(i)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}};var L=class extends d{constructor(t){super(t);this.moduleName="ViewportPredictor";this.intersectionObserver=null;this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter);this.observeElement=t=>this.intersectionObserver?.observe(t);this.unobserveElement=t=>this.intersectionObserver?.unobserve(t);this.handleViewportEnter=t=>{for(let i of t){if(!i.isIntersecting)continue;let o=this.elements.get(i.target);o&&(this.callCallback(o,{kind:"viewport"}),this.unobserveElement(i.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}};var _=class extends d{constructor(t){super(t);this.moduleName="TouchStartPredictor";this.onConnect=()=>this.createAbortController();this.onDisconnect=()=>{};this.handleTouchStart=t=>{let i=t.currentTarget,o=this.elements.get(i);o&&(this.callCallback(o,{kind:"touch"}),this.unobserveElement(i))}}observeElement(t){t instanceof HTMLElement&&t.addEventListener("pointerdown",this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(t){t instanceof HTMLElement&&t.removeEventListener("pointerdown",this.handleTouchStart)}};var f=class extends d{constructor(t){super(t);this.moduleName="TouchDeviceHandler";this.predictor=null;this.onDisconnect=()=>{this.devLog("Disconnecting touch predictor"),this.predictor?.disconnect()};this.onConnect=()=>this.setTouchPredictor();this.observeElement=t=>this.predictor?.observeElement(t);this.unobserveElement=t=>this.predictor?.unobserveElement(t);this.viewportPredictor=new L(t),this.touchStartPredictor=new _(t),this.predictor=this.viewportPredictor}setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case"viewport":this.predictor=this.viewportPredictor,this.devLog("Connected touch strategy: viewport (ViewportPredictor)");break;case"onTouchStart":this.predictor=this.touchStartPredictor,this.devLog("Connected touch strategy: onTouchStart (TouchStartPredictor)");break;case"none":this.predictor=null,this.devLog('Touch strategy set to "none" - no predictor connected');return;default:this.settings.touchDeviceStrategy}this.predictor?.connect();for(let t of this.elements.keys())this.predictor?.observeElement(t)}};var S=class n{constructor(e){this.elements=new Map;this.checkableElements=new Set;this.idCounter=0;this.activeElementCount=0;this.isSetup=!1;this.pendingPointerEvent=null;this.rafId=null;this.domObserver=null;this.currentDeviceStrategy=O()?"touch":"mouse";this.eventEmitter=new I;this._globalCallbackHits=j();this._globalSettings=B();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.setDeviceStrategy(this.currentDeviceStrategy=e.pointerType)),!this.rafId&&(this.rafId=requestAnimationFrame(()=>{if(this.handler instanceof f){this.rafId=null;return}this.pendingPointerEvent&&this.handler.processMouseMovement(this.pendingPointerEvent),this.rafId=null}))};this.handleDomMutations=e=>{if(!e.length)return;this.desktopHandler?.invalidateTabCache();let t=!1;for(let i=0;i<e.length;i++){let o=e[i];if(o&&o.type==="childList"&&o.removedNodes.length>0){t=!0;break}}if(t)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};e!==void 0&&z(this._globalSettings,e);let t={elements:this.elements,checkableElements:this.checkableElements,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),updateCheckableStatus:this.updateCheckableStatus.bind(this),settings:this._globalSettings};this.desktopHandler=new m(t),this.touchDeviceHandler=new f(t),this.handler=this.currentDeviceStrategy==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`),this.initializeGlobalListeners()}static initialize(e){return this.isInitiated||(n.manager=new n(e)),n.manager}static get isInitiated(){return!!n.manager}static get instance(){return this.initialize()}generateId(){return`foresight-${++this.idCounter}`}addEventListener(e,t,i){this.eventEmitter.addEventListener(e,t,i)}removeEventListener(e,t){this.eventEmitter.removeEventListener(e,t)}hasListeners(e){return this.eventEmitter.hasListeners(e)}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount}}get registeredElements(){return this.elements}register(e){let{isTouchDevice:t,isLimitedConnection:i,shouldRegister:o}=N();if(!o)return{isLimitedConnection:i,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};let r=this.elements.get(e.element);if(r)return r.registerCount++,{isLimitedConnection:i,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let a=K(e,this.generateId(),this._globalSettings.defaultHitSlop);return this.elements.set(e.element,a),this.activeElementCount++,this.updateCheckableStatus(a),this.handler.observeElement(e.element),this.eventEmitter.emit({type:"elementRegistered",timestamp:Date.now(),elementData:a}),{isTouchDevice:t,isLimitedConnection:i,isRegistered:!0,unregister:()=>{this.unregister(e.element)}}}unregister(e,t){let i=this.elements.get(e);if(!i)return;this.clearReactivateTimeout(i),this.handler.unobserveElement(e),this.elements.delete(e),this.checkableElements.delete(i),i.callbackInfo.isCallbackActive&&this.activeElementCount--;let o=this.elements.size===0&&this.isSetup;o&&(this.devLog("All elements unregistered, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:t??"by user",wasLastRegisteredElement:o})}reactivate(e){let t=this.elements.get(e);t&&(this.isSetup||this.initializeGlobalListeners(),this.clearReactivateTimeout(t),t.callbackInfo.isRunningCallback||(t.callbackInfo.isCallbackActive=!0,this.activeElementCount++,this.updateCheckableStatus(t),this.handler.observeElement(e),this.eventEmitter.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()})))}clearReactivateTimeout(e){clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0}updateCheckableStatus(e){e.isIntersectingWithViewport&&e.callbackInfo.isCallbackActive&&!e.callbackInfo.isRunningCallback?this.checkableElements.add(e):this.checkableElements.delete(e)}callCallback(e,t){e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive||(this.markElementAsRunning(e),this.executeCallbackAsync(e,t))}markElementAsRunning(e){e.callbackInfo.callbackFiredCount++,e.callbackInfo.lastCallbackInvokedAt=Date.now(),e.callbackInfo.isRunningCallback=!0,this.clearReactivateTimeout(e),this.checkableElements.delete(e)}async executeCallbackAsync(e,t){this.updateHitCounters(t),this.eventEmitter.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let i=performance.now(),o,r=null;try{await e.callback(),o="success"}catch(a){r=a instanceof Error?a.message:String(a),o="error",console.error(`Error in callback for element ${e.name}:`,a)}this.finalizeCallback(e,t,i,o,r)}finalizeCallback(e,t,i,o,r){e.callbackInfo.lastCallbackCompletedAt=Date.now(),e.callbackInfo.isRunningCallback=!1,e.callbackInfo.isCallbackActive=!1,this.activeElementCount--,this.handler.unobserveElement(e.element),e.callbackInfo.reactivateAfter!==1/0&&(e.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.callbackInfo.reactivateAfter));let a=this.activeElementCount===0;a&&(this.devLog("All elements unactivated, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:e.callbackInfo.lastCallbackRuntime=performance.now()-i,status:e.callbackInfo.lastCallbackStatus=o,errorMessage:e.callbackInfo.lastCallbackErrorMessage=r,wasLastActiveElement:a})}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++}setDeviceStrategy(e){let t=this.handler instanceof m?"mouse":"touch";t!==e&&this.devLog(`Switching device strategy from ${t} to ${e}`),this.handler.disconnect(),this.handler=e==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.handler.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.handler.disconnect(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingPointerEvent=null)}alterGlobalSettings(e){let t=V(this._globalSettings,e);t.positionHistorySizeChanged&&this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize),t.scrollPredictionChanged&&this.handler instanceof m&&(this._globalSettings.enableScrollPrediction?this.handler.connectScrollPredictor():this.handler.disconnectScrollPredictor()),t.tabPredictionChanged&&this.handler instanceof m&&(this._globalSettings.enableTabPrediction?this.handler.connectTabPredictor():this.handler.disconnectTabPredictor()),t.hitSlopChanged&&this.forceUpdateAllElementBounds(),t.touchStrategyChanged&&this.handler instanceof f&&this.handler.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.elements.values())e.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),i=g(t,e.elementBounds.hitSlop);if(!b(i,e.elementBounds.expandedRect)){let o={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:i}};this.elements.set(e.element,o),this.eventEmitter.emit({type:"elementDataUpdated",elementData:o,updatedProps:["bounds"]})}}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c\u{1F6E0}\uFE0F ForesightManager: ${e}`,"color: #16a34a; font-weight: bold;")}};export{S as ForesightManager};
1
+ import{a as C,b as u,c as v,d as p}from"./chunk-PAYO6NXN.js";function k(){let i=S(),e=I();return{isTouchDevice:i,isLimitedConnection:e,shouldRegister:!e}}function S(){return typeof window>"u"||typeof navigator>"u"?!1:window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function I(){let i=navigator.connection;if(!i)return!1;let e=g.instance.getManagerData.globalSettings.minimumConnectionType,t=["slow-2g","2g","3g","4g"],n=t.indexOf(i.effectiveType),a=t.indexOf(e);return n<a||i.saveData}function T(i){if(typeof window>"u"||typeof document>"u")return!1;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}function H(){return{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}}function F(){return{debug:!1,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"}}function M(i,e,t){let{element:n,callback:a,hitSlop:s,name:o,meta:c,reactivateAfter:b}=i,d=n.getBoundingClientRect(),r=s?u(s):t;return{id:e,element:n,callback:a,elementBounds:{originalRect:d,expandedRect:v(d,r),hitSlop:r},name:o||n.id||"unnamed",isIntersectingWithViewport:T(d),registerCount:1,meta:c??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:b??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}}}var f=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,n){if(n?.signal?.aborted)return;let a=this.eventListeners.get(e)??[];a.push(t),this.eventListeners.set(e,a),n?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let n=this.eventListeners.get(e);if(!n)return;let a=n.indexOf(t);a>-1&&n.splice(a,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 a=t[n];a&&a(e)}catch(a){console.error(`Error in ForesightManager event listener ${n} for ${e.type}:`,a)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};function y(i,e){return i!==void 0&&e!==i}var L={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}};function h(i,e,t){if(!y(t,i[e]))return!1;let{min:n,max:a}=L[e];return i[e]=C(t,n,a,e),!0}function m(i,e,t){return y(t,i[e])?(i[e]=t,!0):!1}function D(i,e){h(i,"trajectoryPredictionTime",e.trajectoryPredictionTime),h(i,"positionHistorySize",e.positionHistorySize),h(i,"scrollMargin",e.scrollMargin),h(i,"tabOffset",e.tabOffset),m(i,"enableMousePrediction",e.enableMousePrediction),m(i,"enableScrollPrediction",e.enableScrollPrediction),m(i,"enableTabPrediction",e.enableTabPrediction),m(i,"enableManagerLogging",e.enableManagerLogging),e.defaultHitSlop!==void 0&&(i.defaultHitSlop=u(e.defaultHitSlop)),e.touchDeviceStrategy!==void 0&&(i.touchDeviceStrategy=e.touchDeviceStrategy),e.minimumConnectionType!==void 0&&(i.minimumConnectionType=e.minimumConnectionType),e.debug!==void 0&&(i.debug=e.debug)}function R(i,e){let t=[],n=!1,a=!1,s=!1,o=!1,c=!1;if(!e)return{changedSettings:t,positionHistorySizeChanged:n,scrollPredictionChanged:a,tabPredictionChanged:s,hitSlopChanged:o,touchStrategyChanged:c};let b=["trajectoryPredictionTime","positionHistorySize","scrollMargin","tabOffset"];for(let r of b){let l=i[r];h(i,r,e[r])&&(t.push({setting:r,oldValue:l,newValue:i[r]}),r==="positionHistorySize"&&(n=!0))}let d=["enableMousePrediction","enableScrollPrediction","enableTabPrediction"];for(let r of d){let l=i[r];m(i,r,e[r])&&(t.push({setting:r,oldValue:l,newValue:i[r]}),r==="enableScrollPrediction"&&(a=!0),r==="enableTabPrediction"&&(s=!0))}if(e.defaultHitSlop!==void 0){let r=i.defaultHitSlop,l=u(e.defaultHitSlop);p(r,l)||(i.defaultHitSlop=l,t.push({setting:"defaultHitSlop",oldValue:r,newValue:l}),o=!0)}if(e.touchDeviceStrategy!==void 0){let r=i.touchDeviceStrategy;i.touchDeviceStrategy=e.touchDeviceStrategy,t.push({setting:"touchDeviceStrategy",oldValue:r,newValue:e.touchDeviceStrategy}),c=!0}if(e.minimumConnectionType!==void 0){let r=i.minimumConnectionType;i.minimumConnectionType=e.minimumConnectionType,t.push({setting:"minimumConnectionType",oldValue:r,newValue:e.minimumConnectionType})}return{changedSettings:t,positionHistorySizeChanged:n,scrollPredictionChanged:a,tabPredictionChanged:s,hitSlopChanged:o,touchStrategyChanged:c}}var g=class i{constructor(e){this.elements=new Map;this.checkableElements=new Set;this.idCounter=0;this.activeElementCount=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 f;this._globalCallbackHits=H();this._globalSettings=F();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 a=e[n];if(a&&a.type==="childList"&&a.removedNodes.length>0){t=!0;break}}if(t)for(let n of this.elements.keys())n.isConnected||this.unregister(n,"disconnected")};e!==void 0&&D(this._globalSettings,e),this.handlerDependencies={elements:this.elements,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),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-BOXAW4XX.js");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-JWBQ2YOV.js");this.touchDeviceHandler=new e(this.handlerDependencies),this.devLog("TouchDeviceHandler lazy loaded")}return this.touchDeviceHandler}static initialize(e){return this.isInitiated||(i.manager=new i(e)),i.manager}static get isInitiated(){return!!i.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)}get getManagerData(){let e=this.desktopHandler?.loadedPredictors,t=this.touchDeviceHandler?.loadedPredictors;return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount,loadedModules:{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}}}}get registeredElements(){return this.elements}register(e){let{isTouchDevice:t,isLimitedConnection:n,shouldRegister:a}=k();if(!a)return{isLimitedConnection:n,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};let s=this.elements.get(e.element);if(s)return s.registerCount++,{isLimitedConnection:n,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let o=M(e,this.generateId(),this._globalSettings.defaultHitSlop);return this.elements.set(e.element,o),this.activeElementCount++,this.updateCheckableStatus(o),this.currentlyActiveHandler?.observeElement(e.element),this.eventEmitter.emit({type:"elementRegistered",timestamp:Date.now(),elementData:o}),{isTouchDevice:t,isLimitedConnection:n,isRegistered:!0,unregister:()=>{this.unregister(e.element)}}}unregister(e,t){let n=this.elements.get(e);if(!n)return;this.clearReactivateTimeout(n),this.currentlyActiveHandler?.unobserveElement(e),this.elements.delete(e),this.checkableElements.delete(n),n.callbackInfo.isCallbackActive&&this.activeElementCount--;let a=this.elements.size===0&&this.isSetup;a&&(this.devLog("All elements unregistered, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"elementUnregistered",elementData:n,timestamp:Date.now(),unregisterReason:t??"by user",wasLastRegisteredElement:a})}reactivate(e){let t=this.elements.get(e);t&&(this.isSetup||this.initializeGlobalListeners(),this.clearReactivateTimeout(t),t.callbackInfo.isRunningCallback||(t.callbackInfo.isCallbackActive=!0,this.activeElementCount++,this.updateCheckableStatus(t),this.currentlyActiveHandler?.observeElement(e),this.eventEmitter.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()})))}clearReactivateTimeout(e){clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0}updateCheckableStatus(e){e.isIntersectingWithViewport&&e.callbackInfo.isCallbackActive&&!e.callbackInfo.isRunningCallback?this.checkableElements.add(e):this.checkableElements.delete(e)}callCallback(e,t){e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive||(this.markElementAsRunning(e),this.executeCallbackAsync(e,t))}markElementAsRunning(e){e.callbackInfo.callbackFiredCount++,e.callbackInfo.lastCallbackInvokedAt=Date.now(),e.callbackInfo.isRunningCallback=!0,this.clearReactivateTimeout(e),this.checkableElements.delete(e)}async executeCallbackAsync(e,t){this.updateHitCounters(t),this.eventEmitter.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let n=performance.now(),a,s=null;try{await e.callback(),a="success"}catch(o){s=o instanceof Error?o.message:String(o),a="error",console.error(`Error in callback for element ${e.name}:`,o)}this.finalizeCallback(e,t,n,a,s)}finalizeCallback(e,t,n,a,s){e.callbackInfo.lastCallbackCompletedAt=Date.now(),e.callbackInfo.isRunningCallback=!1,e.callbackInfo.isCallbackActive=!1,this.activeElementCount--,this.currentlyActiveHandler?.unobserveElement(e.element),e.callbackInfo.reactivateAfter!==1/0&&(e.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.callbackInfo.reactivateAfter));let o=this.activeElementCount===0;o&&(this.devLog("All elements unactivated, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:e.callbackInfo.lastCallbackRuntime=performance.now()-n,status:e.callbackInfo.lastCallbackStatus=a,errorMessage:e.callbackInfo.lastCallbackErrorMessage=s,wasLastActiveElement:o})}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),this.rafId=null),this.pendingPointerEvent=null)}alterGlobalSettings(e){let t=R(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.elements.values())e.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),n=v(t,e.elementBounds.hitSlop);if(!p(n,e.elementBounds.expandedRect)){let a={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:n}};this.elements.set(e.element,a),this.eventEmitter.emit({type:"elementDataUpdated",elementData:a,updatedProps:["bounds"]})}}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c\u{1F6E0}\uFE0F ForesightManager: ${e}`,"color: #16a34a; font-weight: bold;")}};export{g as ForesightManager};
package/package.json CHANGED
@@ -1,73 +1,75 @@
1
- {
2
- "name": "js.foresight",
3
- "version": "3.3.5",
4
- "description": "Predicts where users will click based on mouse movement, keyboard navigation, and scroll behavior. Includes touch device support. Triggers callbacks before interactions happen to enable prefetching and faster UI responses. Works with any framework.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
- }
14
- },
15
- "homepage": "https://foresightjs.com/",
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/spaansba/ForesightJS"
19
- },
20
- "files": [
21
- "dist",
22
- "README.md",
23
- "LICENSE"
24
- ],
25
- "keywords": [
26
- "javascript-prefetch",
27
- "smart-prefetch",
28
- "fast-prefetch",
29
- "interaction-prediction",
30
- "mobile-prefetch",
31
- "mouse-trajectory",
32
- "element-hitslop",
33
- "foresight",
34
- "interaction-prediction",
35
- "cursor-prediction",
36
- "vanilla-javascript",
37
- "prefetching",
38
- "keyboard-tracking",
39
- "keyboard-prefetching",
40
- "tab-prefetching"
41
- ],
42
- "author": "Bart Spaans",
43
- "license": "MIT",
44
- "llms": "https://foresightjs.com/llms.txt",
45
- "llmsFull": "https://foresightjs.com/llms-full.txt",
46
- "devDependencies": {
47
- "@testing-library/dom": "^10.4.1",
48
- "@testing-library/jest-dom": "^6.9.1",
49
- "@types/node": "^24.10.1",
50
- "@vitest/coverage-v8": "4.0.16",
51
- "@vitest/ui": "^4.0.9",
52
- "happy-dom": "^20.0.10",
53
- "jsdom": "^27.4.0",
54
- "tslib": "^2.8.1",
55
- "tsup": "^8.5.1",
56
- "typescript": "^5.9.3",
57
- "vitest": "^4.0.9"
58
- },
59
- "dependencies": {
60
- "position-observer": "^1.0.3",
61
- "tabbable": "^6.3.0"
62
- },
63
- "scripts": {
64
- "build": "tsup --sourcemap",
65
- "build:prod": "tsup",
66
- "dev": "tsup --sourcemap --watch",
67
- "test": "vitest",
68
- "test:watch": "vitest --watch",
69
- "test:ui": "vitest --ui",
70
- "test:coverage": "vitest --coverage",
71
- "test:run": "vitest run"
72
- }
73
- }
1
+ {
2
+ "name": "js.foresight",
3
+ "version": "3.4.0",
4
+ "description": "Predicts where users will click based on mouse movement, keyboard navigation, and scroll behavior. Includes touch device support. Triggers callbacks before interactions happen to enable prefetching and faster UI responses. Works with any framework.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "scripts": {
8
+ "build": "tsup --sourcemap",
9
+ "build:prod": "tsup",
10
+ "dev": "tsup --sourcemap --watch",
11
+ "test": "vitest",
12
+ "test:watch": "vitest --watch",
13
+ "test:ui": "vitest --ui",
14
+ "test:coverage": "vitest --coverage",
15
+ "test:run": "vitest run",
16
+ "prepublishOnly": "pnpm test:run && pnpm build:prod"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "homepage": "https://foresightjs.com/",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/spaansba/ForesightJS"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "keywords": [
38
+ "javascript-prefetch",
39
+ "smart-prefetch",
40
+ "fast-prefetch",
41
+ "interaction-prediction",
42
+ "mobile-prefetch",
43
+ "mouse-trajectory",
44
+ "element-hitslop",
45
+ "foresight",
46
+ "interaction-prediction",
47
+ "cursor-prediction",
48
+ "vanilla-javascript",
49
+ "prefetching",
50
+ "keyboard-tracking",
51
+ "keyboard-prefetching",
52
+ "tab-prefetching"
53
+ ],
54
+ "author": "Bart Spaans",
55
+ "license": "MIT",
56
+ "llms": "https://foresightjs.com/llms.txt",
57
+ "llmsFull": "https://foresightjs.com/llms-full.txt",
58
+ "devDependencies": {
59
+ "@testing-library/dom": "^10.4.1",
60
+ "@testing-library/jest-dom": "^6.9.1",
61
+ "@types/node": "^24.10.1",
62
+ "@vitest/coverage-v8": "4.0.16",
63
+ "@vitest/ui": "^4.0.9",
64
+ "happy-dom": "^20.0.10",
65
+ "jsdom": "^27.4.0",
66
+ "tslib": "^2.8.1",
67
+ "tsup": "^8.5.1",
68
+ "typescript": "^5.9.3",
69
+ "vitest": "^4.0.9"
70
+ },
71
+ "dependencies": {
72
+ "position-observer": "^1.0.3",
73
+ "tabbable": "^6.3.0"
74
+ }
75
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
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
- SOFTWARE.