js.foresight 3.3.4 → 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 +100 -95
- package/dist/DesktopHandler-BOXAW4XX.js +1 -0
- package/dist/ScrollPredictor-Y7NELMBI.js +1 -0
- package/dist/TabPredictor-HA2SV3CY.js +1 -0
- package/dist/TouchDeviceHandler-JWBQ2YOV.js +1 -0
- package/dist/TouchStartPredictor-ZH3KJG2C.js +1 -0
- package/dist/ViewportPredictor-H3GLDETY.js +1 -0
- package/dist/chunk-44N4MCQB.js +1 -0
- package/dist/chunk-AODZNE3S.js +1 -0
- package/dist/chunk-PAYO6NXN.js +1 -0
- package/dist/index.d.ts +35 -23
- package/dist/index.js +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,95 +1,100 @@
|
|
|
1
|
-
# [ForesightJS](https://foresightjs.com/)
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/js.foresight)
|
|
4
|
-
[](https://www.npmjs.com/package/js.foresight)
|
|
5
|
-
[](https://bundlephobia.com/package/js.foresight)
|
|
6
|
-
[](https://github.com/spaansba/ForesightJS/commits)
|
|
7
|
-
|
|
8
|
-
[](https://github.com/spaansba/ForesightJS)
|
|
9
|
-
[](https://bestofjs.org/projects/foresightjs)
|
|
10
|
-
|
|
11
|
-
[](http://www.typescriptlang.org/)
|
|
12
|
-
[](https://opensource.org/licenses/MIT)
|
|
13
|
-
[](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
|
-

|
|
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.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
1
|
+
# [ForesightJS](https://foresightjs.com/)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/js.foresight)
|
|
4
|
+
[](https://www.npmjs.com/package/js.foresight)
|
|
5
|
+
[](https://bundlephobia.com/package/js.foresight)
|
|
6
|
+
[](https://github.com/spaansba/ForesightJS/commits)
|
|
7
|
+
|
|
8
|
+
[](https://github.com/spaansba/ForesightJS)
|
|
9
|
+
[](https://bestofjs.org/projects/foresightjs)
|
|
10
|
+
|
|
11
|
+
[](http://www.typescriptlang.org/)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
[](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
|
+

|
|
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";
|
|
@@ -450,60 +461,61 @@ interface ForesightBaseEvent {
|
|
|
450
461
|
declare class ForesightManager {
|
|
451
462
|
private static manager;
|
|
452
463
|
private elements;
|
|
464
|
+
private checkableElements;
|
|
453
465
|
private idCounter;
|
|
454
466
|
private activeElementCount;
|
|
455
467
|
private desktopHandler;
|
|
456
468
|
private touchDeviceHandler;
|
|
457
|
-
private
|
|
469
|
+
private currentlyActiveHandler;
|
|
470
|
+
private handlerDependencies;
|
|
458
471
|
private isSetup;
|
|
459
|
-
private _globalCallbackHits;
|
|
460
|
-
private _globalSettings;
|
|
461
472
|
private pendingPointerEvent;
|
|
462
473
|
private rafId;
|
|
463
474
|
private domObserver;
|
|
464
|
-
private eventListeners;
|
|
465
475
|
private currentDeviceStrategy;
|
|
476
|
+
private eventEmitter;
|
|
477
|
+
private _globalCallbackHits;
|
|
478
|
+
private _globalSettings;
|
|
466
479
|
private constructor();
|
|
467
|
-
private
|
|
480
|
+
private getOrCreateDesktopHandler;
|
|
481
|
+
private getOrCreateTouchHandler;
|
|
468
482
|
static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager;
|
|
483
|
+
static get isInitiated(): Readonly<boolean>;
|
|
484
|
+
static get instance(): ForesightManager;
|
|
485
|
+
private generateId;
|
|
486
|
+
private get isUsingDesktopHandler();
|
|
469
487
|
addEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>, options?: {
|
|
470
488
|
signal?: AbortSignal;
|
|
471
|
-
}):
|
|
489
|
+
}): void;
|
|
472
490
|
removeEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>): void;
|
|
473
|
-
|
|
491
|
+
hasListeners<K extends ForesightEvent>(eventType: K): boolean;
|
|
474
492
|
get getManagerData(): Readonly<ForesightManagerData>;
|
|
475
|
-
static get isInitiated(): Readonly<boolean>;
|
|
476
|
-
static get instance(): ForesightManager;
|
|
477
493
|
get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData>;
|
|
478
|
-
register(
|
|
494
|
+
register(options: ForesightRegisterOptions): ForesightRegisterResult;
|
|
479
495
|
unregister(element: ForesightElement, unregisterReason?: ElementUnregisteredReason): void;
|
|
480
|
-
private updateHitCounters;
|
|
481
496
|
reactivate(element: ForesightElement): void;
|
|
482
497
|
private clearReactivateTimeout;
|
|
483
|
-
|
|
498
|
+
updateCheckableStatus(elementData: ForesightElementData): void;
|
|
484
499
|
private callCallback;
|
|
500
|
+
private markElementAsRunning;
|
|
501
|
+
private executeCallbackAsync;
|
|
502
|
+
private finalizeCallback;
|
|
503
|
+
private updateHitCounters;
|
|
485
504
|
private setDeviceStrategy;
|
|
486
505
|
private handlePointerMove;
|
|
487
506
|
private initializeGlobalListeners;
|
|
488
507
|
private removeGlobalListeners;
|
|
489
|
-
/**
|
|
490
|
-
* Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.
|
|
491
|
-
*
|
|
492
|
-
* @param mutationsList - Array of MutationRecord objects describing the DOM changes
|
|
493
|
-
*
|
|
494
|
-
*/
|
|
495
508
|
private handleDomMutations;
|
|
509
|
+
alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
|
|
496
510
|
private forceUpdateAllElementBounds;
|
|
497
511
|
/**
|
|
498
512
|
* 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.
|
|
499
513
|
* We need an observer for that
|
|
500
514
|
*/
|
|
501
515
|
private forceUpdateElementBounds;
|
|
502
|
-
private initializeManagerSettings;
|
|
503
|
-
private updateNumericSettings;
|
|
504
|
-
private updateBooleanSetting;
|
|
505
|
-
alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
|
|
506
516
|
private devLog;
|
|
507
517
|
}
|
|
508
518
|
|
|
509
|
-
|
|
519
|
+
type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean;
|
|
520
|
+
|
|
521
|
+
export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type DeviceStrategyChangedEvent, type ElementCallbackInfo, type ElementDataUpdatedEvent, type ElementReactivatedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightElement, type ForesightElementData, type ForesightEvent, ForesightManager, type ForesightManagerSettings, type Rect as ForesightRect, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HasListenersFunction, type HitSlop, type ManagerSettingsChangedEvent, type MinimumConnectionType, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function g(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 z(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 C(n){if(typeof n=="number"){let e=g(n,0,2e3,"hitslop");return{top:e,left:e,right:e,bottom:e}}return{top:g(n.top,0,2e3,"hitslop - top"),left:g(n.left,0,2e3,"hitslop - left"),right:g(n.right,0,2e3,"hitslop - right"),bottom:g(n.bottom,0,2e3,"hitslop - bottom")}}function P(n,e){return{left:n.left-e.left,right:n.right+e.right,top:n.top-e.top,bottom:n.bottom+e.bottom}}function A(n,e){return!n||!e?n===e:n.left===e.left&&n.right===e.right&&n.top===e.top&&n.bottom===e.bottom}function M(n,e){return n.x>=e.left&&n.x<=e.right&&n.y>=e.top&&n.y<=e.bottom}function K(){let n=N(),e=te();return{isTouchDevice:n,isLimitedConnection:e,shouldRegister:!e}}function N(){return typeof window>"u"||typeof navigator>"u"?!1:window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function te(){let n=navigator.connection;if(!n)return!1;let e=T.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 H(n,e){return n!==void 0&&e!==n}var u=class{constructor(e){this._isConnected=!1;this.elements=e.elements,this.callCallback=e.callCallback,this.emit=e.emit,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){let t=this.moduleName.includes("Predictor")?"#ea580c":"#2563eb";console.log(`%c\u{1F6E0}\uFE0F ${this.moduleName}: ${e}`,`color: ${t}; font-weight: bold;`)}}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};function I(n,e,t){let i=0,o=1,r=e.x-n.x,l=e.y-n.y,a=(s,d)=>{if(s===0){if(d<0)return!1}else{let c=d/s;if(s<0){if(c>o)return!1;c>i&&(i=c)}else{if(c<i)return!1;c<o&&(o=c)}}return!0};return!a(-r,n.x-t.left)||!a(r,t.right-n.x)||!a(-l,n.y-t.top)||!a(l,t.bottom-n.y)?!1:i<=o}function $(n,e,t){let i=performance.now(),o={point:n,time:i},{x:r,y:l}=n;if(e.add(o),e.length<2)return{x:r,y:l};let[a,s]=e.getFirstLast();if(!a||!s)return{x:r,y:l};let d=(s.time-a.time)*.001;if(d===0)return{x:r,y:l};let c=s.point.x-a.point.x,h=s.point.y-a.point.y,y=c/d,b=h/d,L=t*.001,w=r+y*L,V=l+b*L;return{x:w,y:V}}var F=class extends u{constructor(t){super(t.dependencies);this.moduleName="MousePredictor";this.trajectoryPositions=t.trajectoryPositions}updatePointerState(t){let i={x:t.clientX,y:t.clientY};this.trajectoryPositions.currentPoint=i,this.settings.enableMousePrediction?this.trajectoryPositions.predictedPoint=$(i,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime):this.trajectoryPositions.predictedPoint=i}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 l=r.elementBounds.expandedRect;if(i)I(o,this.trajectoryPositions.predictedPoint,l)&&this.callCallback(r,{kind:"mouse",subType:"trajectory"});else if(M(o,l)){this.callCallback(r,{kind:"mouse",subType:"hover"});return}}this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:i,trajectoryPositions:this.trajectoryPositions})}onDisconnect(){}onConnect(){}};function Y(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 G(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 D=class extends u{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??Y(t.elementBounds.originalRect,i),this.scrollDirection!=="none"&&(this.predictedScrollPoint=this.predictedScrollPoint??G(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),I(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"scroll",subType:this.scrollDirection}),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})))}onConnect(){}};import{tabbable as ie}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 _=class extends u{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=ie(document.documentElement));let o=this.lastKeyDown.shiftKey,r=X(o,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let l=[],a=this.settings.tabOffset,s=this.elements;for(let d=0;d<=a;d++){let c=o?r-d:r+d,h=this.tabbableElementsCache[c];h&&h instanceof Element&&s.has(h)&&l.push(h)}for(let d of l){let c=s.get(d);c&&!c.callbackInfo.isRunningCallback&&c.callbackInfo.isCallbackActive&&this.callCallback(c,{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 ne}from"position-observer";var R=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 u{constructor(t){super(t);this.moduleName="DesktopHandler";this.positionObserver=null;this.trajectoryPositions={positions:new R(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=>{M(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&&(o.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:i.boundingClientRect,expandedRect:P(i.boundingClientRect,t.elementBounds.hitSlop)}),o.length&&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 _(t),this.scrollPredictor=new D({dependencies:t,trajectoryPositions:this.trajectoryPositions}),this.mousePredictor=new F({dependencies:t,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new ne(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 k=class extends u{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 O=class extends u{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 v=class extends u{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 k(t),this.touchStartPredictor=new O(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 T=class n{constructor(e){this.elements=new Map;this.idCounter=0;this.activeElementCount=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},touch:0,viewport:0,total:0};this._globalSettings={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"};this.pendingPointerEvent=null;this.rafId=null;this.domObserver=null;this.eventListeners=new Map;this.currentDeviceStrategy=N()?"touch":"mouse";this.handlePointerMove=e=>{this.pendingPointerEvent=e,e.pointerType!=this.currentDeviceStrategy&&(this.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 v){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&&this.initializeManagerSettings(e);let t={elements:this.elements,callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),settings:this._globalSettings};this.desktopHandler=new m(t),this.touchDeviceHandler=new v(t),this.handler=this.currentDeviceStrategy==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`),this.initializeGlobalListeners()}generateId(){return`foresight-${++this.idCounter}`}static initialize(e){return this.isInitiated||(n.manager=new n(e)),n.manager}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)?.slice();if(t)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)}}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners,currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount}}static get isInitiated(){return!!n.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:e,callback:t,hitSlop:i,name:o,meta:r,reactivateAfter:l}){let{isTouchDevice:a,isLimitedConnection:s,shouldRegister:d}=K();if(!d)return{isLimitedConnection:s,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};let c=this.elements.get(e);if(c)return c.registerCount++,{isLimitedConnection:s,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let h=e.getBoundingClientRect(),y=i?C(i):this._globalSettings.defaultHitSlop,b={id:this.generateId(),element:e,callback:t,elementBounds:{originalRect:h,expandedRect:P(h,y),hitSlop:y},name:o||e.id||"unnamed",isIntersectingWithViewport:z(h),registerCount:1,meta:r??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:l??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}};return this.elements.set(e,b),this.activeElementCount++,this.handler.observeElement(e),this.emit({type:"elementRegistered",timestamp:Date.now(),elementData:b}),{isTouchDevice:a,isLimitedConnection:s,isRegistered:!0,unregister:()=>{this.unregister(e)}}}unregister(e,t){let i=this.elements.get(e);if(!i)return;this.clearReactivateTimeout(i),this.handler.unobserveElement(e),this.elements.delete(e),i.callbackInfo.isCallbackActive&&this.activeElementCount--;let o=this.elements.size===0&&this.isSetup;o&&(this.devLog("All elements unregistered, removing global listeners"),this.removeGlobalListeners()),i&&this.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:t??"by user",wasLastRegisteredElement: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++}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.handler.observeElement(e),this.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()})))}clearReactivateTimeout(e){clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0}makeElementUnactive(e){e.callbackInfo.callbackFiredCount++,e.callbackInfo.lastCallbackInvokedAt=Date.now(),e.callbackInfo.isRunningCallback=!0,this.clearReactivateTimeout(e)}callCallback(e,t){if(e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive)return;this.makeElementUnactive(e),(async()=>{this.updateHitCounters(t),this.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let o=performance.now(),r,l=null;try{await e.callback(),r="success"}catch(s){l=s instanceof Error?s.message:String(s),r="error",console.error(`Error in callback for element ${e.name}:`,s)}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.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:e.callbackInfo.lastCallbackRuntime=performance.now()-o,status:e.callbackInfo.lastCallbackStatus=r,errorMessage:e.callbackInfo.lastCallbackErrorMessage=l,wasLastActiveElement:a})})()}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)}forceUpdateAllElementBounds(){for(let[,e]of this.elements)e.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),i=P(t,e.elementBounds.hitSlop);if(!A(i,e.elementBounds.expandedRect)){let o={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:i}};this.elements.set(e.element,o),this.emit({type:"elementDataUpdated",elementData:o,updatedProps:["bounds"]})}}initializeManagerSettings(e){this.updateNumericSettings(e.trajectoryPredictionTime,"trajectoryPredictionTime",10,200),this.updateNumericSettings(e.positionHistorySize,"positionHistorySize",2,30),this.updateNumericSettings(e.scrollMargin,"scrollMargin",30,300),this.updateNumericSettings(e.tabOffset,"tabOffset",0,20),this.updateBooleanSetting(e.enableMousePrediction,"enableMousePrediction"),this.updateBooleanSetting(e.enableScrollPrediction,"enableScrollPrediction"),this.updateBooleanSetting(e.enableTabPrediction,"enableTabPrediction"),this.updateBooleanSetting(e.enableManagerLogging,"enableManagerLogging"),e.defaultHitSlop!==void 0&&(this._globalSettings.defaultHitSlop=C(e.defaultHitSlop)),e.touchDeviceStrategy!==void 0&&(this._globalSettings.touchDeviceStrategy=e.touchDeviceStrategy),e.minimumConnectionType!==void 0&&(this._globalSettings.minimumConnectionType=e.minimumConnectionType),e.debug!==void 0&&(this._globalSettings.debug=e.debug)}updateNumericSettings(e,t,i,o){return H(e,this._globalSettings[t])?(this._globalSettings[t]=g(e,i,o,t),!0):!1}updateBooleanSetting(e,t){return H(e,this._globalSettings[t])?(this._globalSettings[t]=e,!0):!1}alterGlobalSettings(e){let t=[],i=this._globalSettings.trajectoryPredictionTime;this.updateNumericSettings(e?.trajectoryPredictionTime,"trajectoryPredictionTime",10,200)&&t.push({setting:"trajectoryPredictionTime",oldValue:i,newValue:this._globalSettings.trajectoryPredictionTime});let r=this._globalSettings.positionHistorySize;this.updateNumericSettings(e?.positionHistorySize,"positionHistorySize",2,30)&&(t.push({setting:"positionHistorySize",oldValue:r,newValue:this._globalSettings.positionHistorySize}),this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize));let a=this._globalSettings.scrollMargin;if(this.updateNumericSettings(e?.scrollMargin,"scrollMargin",30,300)){let p=this._globalSettings.scrollMargin;t.push({setting:"scrollMargin",oldValue:a,newValue:p})}let d=this._globalSettings.tabOffset;this.updateNumericSettings(e?.tabOffset,"tabOffset",0,20)&&(this._globalSettings.tabOffset=g(e.tabOffset,0,20,"tabOffset"),t.push({setting:"tabOffset",oldValue:d,newValue:this._globalSettings.tabOffset}));let h=this._globalSettings.enableMousePrediction;this.updateBooleanSetting(e?.enableMousePrediction,"enableMousePrediction")&&t.push({setting:"enableMousePrediction",oldValue:h,newValue:this._globalSettings.enableMousePrediction});let b=this._globalSettings.enableScrollPrediction;this.updateBooleanSetting(e?.enableScrollPrediction,"enableScrollPrediction")&&(this.handler instanceof m&&(this._globalSettings.enableScrollPrediction?this.handler.connectScrollPredictor():this.handler.disconnectScrollPredictor()),t.push({setting:"enableScrollPrediction",oldValue:b,newValue:this._globalSettings.enableScrollPrediction}));let w=this._globalSettings.enableTabPrediction;if(this.updateBooleanSetting(e?.enableTabPrediction,"enableTabPrediction")&&(this.handler instanceof m&&(this._globalSettings.enableTabPrediction?this.handler.connectTabPredictor():this.handler.disconnectTabPredictor()),t.push({setting:"enableTabPrediction",oldValue:w,newValue:this._globalSettings.enableTabPrediction})),e?.defaultHitSlop!==void 0){let p=this._globalSettings.defaultHitSlop,f=C(e.defaultHitSlop);A(p,f)||(this._globalSettings.defaultHitSlop=f,t.push({setting:"defaultHitSlop",oldValue:p,newValue:f}),this.forceUpdateAllElementBounds())}if(e?.touchDeviceStrategy!==void 0){let p=this._globalSettings.touchDeviceStrategy,f=e.touchDeviceStrategy;this._globalSettings.touchDeviceStrategy=f,t.push({setting:"touchDeviceStrategy",oldValue:p,newValue:f}),this.handler instanceof v&&this.handler.setTouchPredictor()}if(e?.minimumConnectionType!==void 0){let p=this._globalSettings.minimumConnectionType;this._globalSettings.minimumConnectionType=e.minimumConnectionType,t.push({setting:"minimumConnectionType",oldValue:p,newValue:this._globalSettings.minimumConnectionType})}t.length>0&&this.emit({type:"managerSettingsChanged",timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:t})}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c\u{1F6E0}\uFE0F ForesightManager: ${e}`,"color: #16a34a; font-weight: bold;")}};export{T 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,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js.foresight",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
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
5
|
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "tsup --sourcemap",
|
|
8
9
|
"build:prod": "tsup",
|
|
@@ -58,10 +59,10 @@
|
|
|
58
59
|
"@testing-library/dom": "^10.4.1",
|
|
59
60
|
"@testing-library/jest-dom": "^6.9.1",
|
|
60
61
|
"@types/node": "^24.10.1",
|
|
61
|
-
"@vitest/coverage-v8": "4.0.
|
|
62
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
62
63
|
"@vitest/ui": "^4.0.9",
|
|
63
64
|
"happy-dom": "^20.0.10",
|
|
64
|
-
"jsdom": "^27.
|
|
65
|
+
"jsdom": "^27.4.0",
|
|
65
66
|
"tslib": "^2.8.1",
|
|
66
67
|
"tsup": "^8.5.1",
|
|
67
68
|
"typescript": "^5.9.3",
|