js.foresight 3.1.2 → 3.2.0-beta.1
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 +1 -12
- package/dist/index.d.ts +56 -9
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,20 +13,9 @@
|
|
|
13
13
|
[](https://foresightjs.com#playground)
|
|
14
14
|
ForesightJS is a lightweight JavaScript library with full TypeScript support that predicts user intent based on mouse movements, scroll and keyboard navigation. By analyzing cursor/scroll trajectory and tab sequences, it anticipates which elements a user is likely to interact with, allowing developers to trigger actions before the actual hover or click occurs (for example prefetching).
|
|
15
15
|
|
|
16
|
-
### Understanding ForesightJS's Role:
|
|
17
|
-
|
|
18
|
-
When you over simplify prefetching it exists of three parts.
|
|
19
|
-
|
|
20
|
-
- **What** resource or data to load
|
|
21
|
-
- **How** the loading method and caching strategy is
|
|
22
|
-
- **When** the optimal moment to start fetching is
|
|
23
|
-
|
|
24
|
-
ForesightJS takes care of the **When** by predicting user intent with mouse trajectory and tab navigation.
|
|
25
|
-
You supply the **What** and **How** inside your `callback` when you register an element.
|
|
26
|
-
|
|
27
16
|
### [Playground](https://foresightjs.com/)
|
|
28
17
|
|
|
29
|
-

|
|
30
19
|
_In the GIF above, the [ForesightJS DevTools](https://foresightjs.com/docs/getting_started/development_tools) are enabled. Normally, users won't see anything that ForesightJS does except the increased perceived speed from early prefetching._
|
|
31
20
|
|
|
32
21
|
## Download
|
package/dist/index.d.ts
CHANGED
|
@@ -106,14 +106,45 @@ type ForesightElementData = Required<Pick<ForesightRegisterOptions, "callback" |
|
|
|
106
106
|
* The element you registered
|
|
107
107
|
*/
|
|
108
108
|
element: ForesightElement;
|
|
109
|
-
/**
|
|
110
|
-
* If the element is currently running its callback
|
|
111
|
-
*/
|
|
112
|
-
isRunningCallback: boolean;
|
|
113
109
|
/**
|
|
114
110
|
* For debugging, check if you are registering the same element multiple times.
|
|
115
111
|
*/
|
|
116
112
|
registerCount: number;
|
|
113
|
+
/**
|
|
114
|
+
* If set by user, stores additional information about the registered element
|
|
115
|
+
*/
|
|
116
|
+
meta: Record<string, unknown>;
|
|
117
|
+
callbackInfo: ElementCallbackInfo;
|
|
118
|
+
};
|
|
119
|
+
type ElementCallbackInfo = {
|
|
120
|
+
/**
|
|
121
|
+
* Number of times the callback has been fired for this element
|
|
122
|
+
*/
|
|
123
|
+
callbackFiredCount: number;
|
|
124
|
+
/**
|
|
125
|
+
* Timestamp when the callback was last fired
|
|
126
|
+
*/
|
|
127
|
+
lastCallbackFiredAt: number | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* Time in milliseconds it took for the last callback to go from invoked to complete.
|
|
130
|
+
*/
|
|
131
|
+
lastCallbackRuntime: number | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Status of the last ran callback
|
|
134
|
+
*/
|
|
135
|
+
lastCallbackStatus: "error" | "success" | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* Time in milliseconds after which the callback can be fired again
|
|
138
|
+
*/
|
|
139
|
+
reactivateAfter: number;
|
|
140
|
+
/**
|
|
141
|
+
* Whether the callback is currently active (within stale time period)
|
|
142
|
+
*/
|
|
143
|
+
isCallbackActive: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* If the element is currently running its callback
|
|
146
|
+
*/
|
|
147
|
+
isRunningCallback: boolean;
|
|
117
148
|
};
|
|
118
149
|
type MouseCallbackCounts = {
|
|
119
150
|
hover: number;
|
|
@@ -149,7 +180,6 @@ type ForesightManagerData = {
|
|
|
149
180
|
globalSettings: Readonly<ForesightManagerSettings>;
|
|
150
181
|
globalCallbackHits: Readonly<CallbackHits>;
|
|
151
182
|
eventListeners: ReadonlyMap<keyof ForesightEventMap, ForesightEventListener[]>;
|
|
152
|
-
historyPositions: TrajectoryPositions;
|
|
153
183
|
};
|
|
154
184
|
type BaseForesightManagerSettings = {
|
|
155
185
|
/**
|
|
@@ -244,6 +274,16 @@ type ForesightRegisterOptions = {
|
|
|
244
274
|
*/
|
|
245
275
|
unregisterOnCallback?: boolean;
|
|
246
276
|
name?: string;
|
|
277
|
+
/**
|
|
278
|
+
* If set by user, stores additional information about the registered element
|
|
279
|
+
*/
|
|
280
|
+
meta?: Record<string, unknown>;
|
|
281
|
+
/**
|
|
282
|
+
* Time in milliseconds after which the callback can be fired again and we reactivate the element.
|
|
283
|
+
* Set to Infinity to prevent callback from firing again after first execution.
|
|
284
|
+
* @default Infinity
|
|
285
|
+
*/
|
|
286
|
+
reactivateAfter?: number;
|
|
247
287
|
};
|
|
248
288
|
/**
|
|
249
289
|
* Usefull for if you want to create a custom button component in a modern framework (for example React).
|
|
@@ -260,6 +300,7 @@ type ForesightRegisterOptionsWithoutElement = Omit<ForesightRegisterOptions, "el
|
|
|
260
300
|
type HitSlop = Rect | number;
|
|
261
301
|
interface ForesightEventMap {
|
|
262
302
|
elementRegistered: ElementRegisteredEvent;
|
|
303
|
+
elementReactivated: ElementReactivatedEvent;
|
|
263
304
|
elementUnregistered: ElementUnregisteredEvent;
|
|
264
305
|
elementDataUpdated: ElementDataUpdatedEvent;
|
|
265
306
|
callbackInvoked: CallbackInvokedEvent;
|
|
@@ -268,11 +309,15 @@ interface ForesightEventMap {
|
|
|
268
309
|
scrollTrajectoryUpdate: ScrollTrajectoryUpdateEvent;
|
|
269
310
|
managerSettingsChanged: ManagerSettingsChangedEvent;
|
|
270
311
|
}
|
|
271
|
-
type ForesightEvent = "elementRegistered" | "elementUnregistered" | "elementDataUpdated" | "callbackInvoked" | "callbackCompleted" | "mouseTrajectoryUpdate" | "scrollTrajectoryUpdate" | "managerSettingsChanged";
|
|
312
|
+
type ForesightEvent = "elementRegistered" | "elementReactivated" | "elementUnregistered" | "elementDataUpdated" | "callbackInvoked" | "callbackCompleted" | "mouseTrajectoryUpdate" | "scrollTrajectoryUpdate" | "managerSettingsChanged";
|
|
272
313
|
interface ElementRegisteredEvent extends ForesightBaseEvent {
|
|
273
314
|
type: "elementRegistered";
|
|
274
315
|
elementData: ForesightElementData;
|
|
275
316
|
}
|
|
317
|
+
interface ElementReactivatedEvent extends ForesightBaseEvent {
|
|
318
|
+
type: "elementReactivated";
|
|
319
|
+
elementData: ForesightElementData;
|
|
320
|
+
}
|
|
276
321
|
interface ElementUnregisteredEvent extends ForesightBaseEvent {
|
|
277
322
|
type: "elementUnregistered";
|
|
278
323
|
elementData: ForesightElementData;
|
|
@@ -379,8 +424,8 @@ declare class ForesightManager {
|
|
|
379
424
|
static get isInitiated(): Readonly<boolean>;
|
|
380
425
|
static get instance(): ForesightManager;
|
|
381
426
|
get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData>;
|
|
382
|
-
register({ element, callback, hitSlop, name, }: ForesightRegisterOptions): ForesightRegisterResult;
|
|
383
|
-
|
|
427
|
+
register({ element, callback, hitSlop, name, meta, reactivateAfter, }: ForesightRegisterOptions): ForesightRegisterResult;
|
|
428
|
+
unregister(element: ForesightElement, unregisterReason: ElementUnregisteredReason): void;
|
|
384
429
|
private updateNumericSettings;
|
|
385
430
|
private updateBooleanSetting;
|
|
386
431
|
alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
|
|
@@ -392,6 +437,8 @@ declare class ForesightManager {
|
|
|
392
437
|
*/
|
|
393
438
|
private handleDomMutations;
|
|
394
439
|
private updateHitCounters;
|
|
440
|
+
private makeElementActive;
|
|
441
|
+
private makeElementUnactive;
|
|
395
442
|
private callCallback;
|
|
396
443
|
private handlePositionChange;
|
|
397
444
|
private handlePositionChangeDataUpdates;
|
|
@@ -405,4 +452,4 @@ declare class ForesightManager {
|
|
|
405
452
|
private forceUpdateElementBounds;
|
|
406
453
|
}
|
|
407
454
|
|
|
408
|
-
export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type ElementDataUpdatedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightElement, type ForesightElementData, type ForesightEvent, ForesightManager, type ForesightManagerSettings, type Rect as ForesightRect, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HitSlop, type ManagerSettingsChangedEvent, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
|
|
455
|
+
export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, 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 HitSlop, type ManagerSettingsChangedEvent, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{PositionObserver as rt}from"position-observer";var P=class{constructor(t){this.head=0;this.count=0;if(t<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=t,this.buffer=new Array(t)}add(t){this.buffer[this.head]=t,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let t=(this.head-1+this.capacity)%this.capacity;return this.buffer[t]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let i=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[i,i]}let t=this.getFirst(),e=this.getLast();return[t,e]}resize(t){if(t<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(t===this.capacity)return;let e=this.getAllItems();if(this.capacity=t,this.buffer=new Array(t),this.head=0,this.count=0,e.length>t){let i=e.slice(-t);for(let n of i)this.add(n)}else for(let i of e)this.add(i)}getAllItems(){if(this.count===0)return[];let t=new Array(this.count);if(this.count<this.capacity)for(let e=0;e<this.count;e++)t[e]=this.buffer[e];else{let e=this.head;for(let i=0;i<this.capacity;i++){let n=(e+i)%this.capacity;t[i]=this.buffer[n]}}return t}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}};function u(o,t,e,i){return o<t?console.warn(`ForesightJS: "${i}" value ${o} is below minimum bound ${t}, clamping to ${t}`):o>e&&console.warn(`ForesightJS: "${i}" value ${o} is above maximum bound ${e}, clamping to ${e}`),Math.min(Math.max(o,t),e)}function j(o){let t=window.innerWidth||document.documentElement.clientWidth,e=window.innerHeight||document.documentElement.clientHeight;return o.top<e&&o.bottom>0&&o.left<t&&o.right>0}function F(o){if(typeof o=="number"){let t=u(o,0,2e3,"hitslop");return{top:t,left:t,right:t,bottom:t}}return{top:u(o.top,0,2e3,"hitslop - top"),left:u(o.left,0,2e3,"hitslop - left"),right:u(o.right,0,2e3,"hitslop - right"),bottom:u(o.bottom,0,2e3,"hitslop - bottom")}}function y(o,t){return{left:o.left-t.left,right:o.right+t.right,top:o.top-t.top,bottom:o.bottom+t.bottom}}function x(o,t){return!o||!t?o===t:o.left===t.left&&o.right===t.right&&o.top===t.top&&o.bottom===t.bottom}function E(o,t){return o.x>=t.left&&o.x<=t.right&&o.y>=t.top&&o.y<=t.bottom}function H(){let o=z(),t=K();return{isTouchDevice:o,isLimitedConnection:t,shouldRegister:!o&&!t}}function z(){return window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function K(){let o=navigator.connection;return o?/2g/.test(o.effectiveType)||o.saveData:!1}function O(o,t){return o!==void 0&&t!==o}function S(o,t,e){let i=0,n=1,r=t.x-o.x,l=t.y-o.y,s=(a,c)=>{if(a===0){if(c<0)return!1}else{let d=c/a;if(a<0){if(d>n)return!1;d>i&&(i=d)}else{if(d<i)return!1;d<n&&(n=d)}}return!0};return!s(-r,o.x-e.left)||!s(r,e.right-o.x)||!s(-l,o.y-e.top)||!s(l,e.bottom-o.y)?!1:i<=n}function N(o,t,e){let i=performance.now(),n={point:o,time:i},{x:r,y:l}=o;if(t.add(n),t.length<2)return{x:r,y:l};let[s,a]=t.getFirstLast();if(!s||!a)return{x:r,y:l};let c=(a.time-s.time)*.001;if(c===0)return{x:r,y:l};let d=a.point.x-s.point.x,M=a.point.y-s.point.y,L=d/c,C=M/c,I=e*.001,R=r+L*I,w=l+C*I;return{x:R,y:w}}var h=class{constructor(t){this.elements=t.dependencies.elements,this.callCallback=t.dependencies.callCallback,this.emit=t.dependencies.emit,this.abortController=new AbortController}abort(){this.abortController.abort()}handleError(t,e){console.error(`${this.constructor.name} error in ${e}:`,t)}};var T=class extends h{constructor(e){super(e);this.pendingMouseEvent=null;this.rafId=null;this.handleMouseMove=e=>{this.pendingMouseEvent=e,!this.rafId&&(this.rafId=requestAnimationFrame(()=>{this.pendingMouseEvent&&this.processMouseMovement(this.pendingMouseEvent),this.rafId=null}))};this.enableMousePrediction=e.settings.enableMousePrediction,this.trajectoryPredictionTime=e.settings.trajectoryPredictionTime,this.positionHistorySize=e.settings.positionHistorySize,this.trajectoryPositions=e.trajectoryPositions,this.initializeListeners()}initializeListeners(){let{signal:e}=this.abortController;document.addEventListener("mousemove",this.handleMouseMove,{signal:e})}updatePointerState(e){let i={x:e.clientX,y:e.clientY};this.trajectoryPositions.currentPoint=i,this.enableMousePrediction?this.trajectoryPositions.predictedPoint=N(i,this.trajectoryPositions.positions,this.trajectoryPredictionTime):this.trajectoryPositions.predictedPoint=i}cleanup(){this.abort(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingMouseEvent=null}processMouseMovement(e){try{this.updatePointerState(e);for(let i of this.elements.values()){if(!i.isIntersectingWithViewport)continue;let n=i.elementBounds.expandedRect;if(this.enableMousePrediction)S(this.trajectoryPositions.currentPoint,this.trajectoryPositions.predictedPoint,n)&&this.callCallback(i,{kind:"mouse",subType:"trajectory"});else if(E(this.trajectoryPositions.currentPoint,n)){this.callCallback(i,{kind:"mouse",subType:"hover"});return}}this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:this.enableMousePrediction,trajectoryPositions:this.trajectoryPositions})}catch(i){this.handleError(i,"processMouseMovement")}}};function A(o,t){let i=t.top-o.top,n=t.left-o.left;return i<-1?"down":i>1?"up":n<-1?"right":n>1?"left":"none"}function k(o,t,e){let{x:i,y:n}=o,r={x:i,y:n};switch(t){case"down":r.y+=e;break;case"up":r.y-=e;break;case"left":r.x-=e;break;case"right":r.x+=e;break;case"none":break;default:}return r}var b=class extends h{constructor(e){super(e);this.predictedScrollPoint=null;this.scrollDirection=null;this.scrollMargin=e.settings.scrollMargin,this.trajectoryPositions=e.trajectoryPositions}initializeListeners(){}cleanup(){this.abort(),this.resetScrollProps()}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(e,i){if(e.isIntersectingWithViewport)try{if(this.scrollDirection=this.scrollDirection??A(e.elementBounds.originalRect,i),this.scrollDirection==="none")return;this.predictedScrollPoint=this.predictedScrollPoint??k(this.trajectoryPositions.currentPoint,this.scrollDirection,this.scrollMargin),S(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,e.elementBounds.expandedRect)&&this.callCallback(e,{kind:"scroll",subType:this.scrollDirection}),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})}catch(n){this.handleError(n,"handleScrollPrefetch")}}handleError(e,i){super.handleError(e,i),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.trajectoryPositions.currentPoint,scrollDirection:"none"})}};import{tabbable as V}from"tabbable";function U(o,t,e,i){if(t!==null&&t>-1){let n=o?t-1:t+1;if(n>=0&&n<e.length&&e[n]===i)return n}return e.findIndex(n=>n===i)}var f=class extends h{constructor(e){super(e);this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=e=>{e.key==="Tab"&&(this.lastKeyDown=e)};this.handleFocusIn=e=>{try{if(!this.lastKeyDown)return;let i=e.target;if(!(i instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.tabbableElementsCache=V(document.documentElement));let n=this.lastKeyDown.shiftKey,r=U(n,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let l=[];for(let s=0;s<=this.tabOffset;s++){let a=n?r-s:r+s,c=this.tabbableElementsCache[a];c&&c instanceof Element&&this.elements.has(c)&&l.push(c)}l.forEach(s=>{let a=this.elements.get(s);a&&this.callCallback(a,{kind:"tab",subType:n?"reverse":"forwards"})})}catch(i){this.handleError(i,"handleFocusIn")}};this.tabOffset=e.settings.tabOffset,this.initializeListeners()}initializeListeners(){let{signal:e}=this.abortController;document.addEventListener("keydown",this.handleKeyDown,{signal:e}),document.addEventListener("focusin",this.handleFocusIn,{signal:e})}invalidateCache(){this.tabbableElementsCache=[],this.lastFocusedIndex=null}cleanup(){this.abort(),this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}};var D=class o{constructor(){this.elements=new Map;this.trajectoryPositions={positions:new P(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.isSetup=!1;this._globalCallbackHits={mouse:{hover:0,trajectory:0},tab:{forwards:0,reverse:0},scroll:{down:0,left:0,right:0,up:0},total:0};this._globalSettings={debug:!1,enableMousePrediction:!0,enableScrollPrediction:!0,positionHistorySize:8,trajectoryPredictionTime:120,scrollMargin:150,defaultHitSlop:{top:0,left:0,right:0,bottom:0},enableTabPrediction:!0,tabOffset:2};this.domObserver=null;this.positionObserver=null;this.eventListeners=new Map;this.mousePredictor=null;this.tabPredictor=null;this.scrollPredictor=null;this.handleDomMutations=t=>{t.length&&this.tabPredictor?.invalidateCache();for(let e of t)if(e.type==="childList"&&e.removedNodes.length>0)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};this.handlePositionChange=t=>{for(let e of t){let i=this.elements.get(e.target);i&&(this._globalSettings.enableScrollPrediction?this.scrollPredictor?.handleScrollPrefetch(i,e.boundingClientRect):E(this.trajectoryPositions.currentPoint,i.elementBounds.expandedRect)&&this.callCallback(i,{kind:"mouse",subType:"hover"}),this.handlePositionChangeDataUpdates(i,e))}this._globalSettings.enableScrollPrediction&&this.scrollPredictor?.resetScrollProps()};this.handlePositionChangeDataUpdates=(t,e)=>{let i=[],n=e.isIntersecting;t.isIntersectingWithViewport!==n&&(i.push("visibility"),t.isIntersectingWithViewport=n),n&&(i.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:e.boundingClientRect,expandedRect:y(e.boundingClientRect,t.elementBounds.hitSlop)}),i.length&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:i})}}static initialize(t){return this.isInitiated||(o.manager=new o),t!==void 0&&o.manager.alterGlobalSettings(t),o.manager}addEventListener(t,e,i){if(i?.signal?.aborted)return()=>{};let n=this.eventListeners.get(t)??[];n.push(e),this.eventListeners.set(t,n),i?.signal?.addEventListener("abort",()=>this.removeEventListener(t,e))}removeEventListener(t,e){let i=this.eventListeners.get(t);if(!i)return;let n=i.indexOf(e);n>-1&&i.splice(n,1)}emit(t){let e=this.eventListeners.get(t.type);e&&e.forEach((i,n)=>{try{i(t)}catch(r){console.error(`Error in ForesightManager event listener ${n} for ${t.type}:`,r)}})}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners,historyPositions:this.trajectoryPositions}}static get isInitiated(){return!!o.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:t,callback:e,hitSlop:i,name:n}){let{shouldRegister:r,isTouchDevice:l,isLimitedConnection:s}=H();if(!r)return{isLimitedConnection:s,isTouchDevice:l,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let a=t.getBoundingClientRect(),c=i?F(i):this._globalSettings.defaultHitSlop,d={element:t,callback:e,elementBounds:{originalRect:a,expandedRect:y(a,c),hitSlop:c},isHovering:!1,trajectoryHitData:{isTrajectoryHit:!1,trajectoryHitTime:0,trajectoryHitExpirationTimeoutId:void 0},name:n||t.id||"unnamed",isIntersectingWithViewport:j(a),isRunningCallback:!1,registerCount:(this.registeredElements.get(t)?.registerCount??0)+1};return this.elements.set(t,d),this.positionObserver?.observe(t),this.emit({type:"elementRegistered",timestamp:Date.now(),elementData:d}),{isTouchDevice:l,isLimitedConnection:s,isRegistered:!0,unregister:()=>this.unregister(t,"apiCall")}}unregister(t,e){if(!this.elements.has(t))return;let i=this.elements.get(t);i?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(i.trajectoryHitData.trajectoryHitExpirationTimeoutId),this.positionObserver?.unobserve(t),this.elements.delete(t);let n=this.elements.size===0&&this.isSetup;n&&this.removeGlobalListeners(),i&&this.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:e,wasLastElement:n})}updateNumericSettings(t,e,i,n){return O(t,this._globalSettings[e])?(this._globalSettings[e]=u(t,i,n,e),!0):!1}updateBooleanSetting(t,e){return O(t,this._globalSettings[e])?(this._globalSettings[e]=t,!0):!1}alterGlobalSettings(t){let e=[],i=this._globalSettings.trajectoryPredictionTime;this.updateNumericSettings(t?.trajectoryPredictionTime,"trajectoryPredictionTime",10,200)&&e.push({setting:"trajectoryPredictionTime",oldValue:i,newValue:this._globalSettings.trajectoryPredictionTime});let r=this._globalSettings.positionHistorySize;this.updateNumericSettings(t?.positionHistorySize,"positionHistorySize",2,30)&&(e.push({setting:"positionHistorySize",oldValue:r,newValue:this._globalSettings.positionHistorySize}),this.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize));let s=this._globalSettings.scrollMargin;if(this.updateNumericSettings(t?.scrollMargin,"scrollMargin",30,300)){let p=this._globalSettings.scrollMargin;this.scrollPredictor&&(this.scrollPredictor.scrollMargin=p),e.push({setting:"scrollMargin",oldValue:s,newValue:p})}let c=this._globalSettings.tabOffset;this.updateNumericSettings(t?.tabOffset,"tabOffset",0,20)&&(this.tabPredictor&&(this.tabPredictor.tabOffset=this._globalSettings.tabOffset),e.push({setting:"tabOffset",oldValue:c,newValue:this._globalSettings.tabOffset}));let M=this._globalSettings.enableMousePrediction;this.updateBooleanSetting(t?.enableMousePrediction,"enableMousePrediction")&&e.push({setting:"enableMousePrediction",oldValue:M,newValue:this._globalSettings.enableMousePrediction});let C=this._globalSettings.enableScrollPrediction;this.updateBooleanSetting(t?.enableScrollPrediction,"enableScrollPrediction")&&(this._globalSettings.enableScrollPrediction?this.scrollPredictor=new b({dependencies:{callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements},settings:{scrollMargin:this._globalSettings.scrollMargin},trajectoryPositions:this.trajectoryPositions}):(this.scrollPredictor?.cleanup(),this.scrollPredictor=null),e.push({setting:"enableScrollPrediction",oldValue:C,newValue:this._globalSettings.enableScrollPrediction}));let R=this._globalSettings.enableTabPrediction;if(this.updateBooleanSetting(t?.enableTabPrediction,"enableTabPrediction")&&(e.push({setting:"enableTabPrediction",oldValue:R,newValue:this._globalSettings.enableTabPrediction}),this._globalSettings.enableTabPrediction?this.tabPredictor=new f({dependencies:{callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements},settings:{tabOffset:this._globalSettings.tabOffset}}):(this.tabPredictor?.cleanup(),this.tabPredictor=null)),t?.defaultHitSlop!==void 0){let p=this._globalSettings.defaultHitSlop,_=F(t.defaultHitSlop);x(p,_)||(this._globalSettings.defaultHitSlop=_,e.push({setting:"defaultHitSlop",oldValue:p,newValue:_}),this.forceUpdateAllElementBounds())}e.length>0&&this.emit({type:"managerSettingsChanged",timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:e})}updateHitCounters(t){switch(t.kind){case"mouse":this._globalCallbackHits.mouse[t.subType]++;break;case"tab":this._globalCallbackHits.tab[t.subType]++;break;case"scroll":this._globalCallbackHits.scroll[t.subType]++;break;default:}this._globalCallbackHits.total++}callCallback(t,e){if(t.isRunningCallback)return;let i=async()=>{this.updateHitCounters(e),this.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:t,hitType:e});let n=performance.now();try{await t.callback(),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:t,hitType:e,elapsed:performance.now()-n,status:"success"})}catch(r){let l=r instanceof Error?r.message:String(r);console.error(`Error in callback for element ${t.name} (${t.element.tagName}):`,r),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:t,hitType:e,elapsed:performance.now()-n,status:"error",errorMessage:l})}this.unregister(t.element,"callbackHit")};t.isRunningCallback=!0,i()}initializeGlobalListeners(){if(this.isSetup||typeof window>"u"||typeof document>"u")return;let t=this._globalSettings,e={callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements};t.enableTabPrediction&&(this.tabPredictor=new f({dependencies:e,settings:{tabOffset:t.tabOffset}})),t.enableScrollPrediction&&(this.scrollPredictor=new b({dependencies:e,settings:{scrollMargin:t.scrollMargin},trajectoryPositions:this.trajectoryPositions})),this.mousePredictor=new T({dependencies:e,settings:{enableMousePrediction:t.enableMousePrediction,trajectoryPredictionTime:t.trajectoryPredictionTime,positionHistorySize:t.positionHistorySize},trajectoryPositions:this.trajectoryPositions}),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.positionObserver=new rt(this.handlePositionChange),this.isSetup=!0}removeGlobalListeners(){this.isSetup=!1,this.domObserver?.disconnect(),this.domObserver=null,this.positionObserver?.disconnect(),this.positionObserver=null,this.mousePredictor?.cleanup(),this.tabPredictor?.cleanup(),this.scrollPredictor?.cleanup()}forceUpdateAllElementBounds(){for(let[,t]of this.elements)t.isIntersectingWithViewport&&this.forceUpdateElementBounds(t)}forceUpdateElementBounds(t){let e=t.element.getBoundingClientRect(),i=y(e,t.elementBounds.hitSlop);if(!x(i,t.elementBounds.expandedRect)){let n={...t,elementBounds:{...t.elementBounds,originalRect:e,expandedRect:i}};this.elements.set(t.element,n),this.emit({type:"elementDataUpdated",elementData:n,updatedProps:["bounds"]})}}};export{D as ForesightManager};
|
|
1
|
+
import{PositionObserver as st}from"position-observer";var E=class{constructor(t){this.head=0;this.count=0;if(t<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=t,this.buffer=new Array(t)}add(t){this.buffer[this.head]=t,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let t=(this.head-1+this.capacity)%this.capacity;return this.buffer[t]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let i=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[i,i]}let t=this.getFirst(),e=this.getLast();return[t,e]}resize(t){if(t<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(t===this.capacity)return;let e=this.getAllItems();if(this.capacity=t,this.buffer=new Array(t),this.head=0,this.count=0,e.length>t){let i=e.slice(-t);for(let n of i)this.add(n)}else for(let i of e)this.add(i)}getAllItems(){if(this.count===0)return[];let t=new Array(this.count);if(this.count<this.capacity)for(let e=0;e<this.count;e++)t[e]=this.buffer[e];else{let e=this.head;for(let i=0;i<this.capacity;i++){let n=(e+i)%this.capacity;t[i]=this.buffer[n]}}return t}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}};function u(o,t,e,i){return o<t?console.warn(`ForesightJS: "${i}" value ${o} is below minimum bound ${t}, clamping to ${t}`):o>e&&console.warn(`ForesightJS: "${i}" value ${o} is above maximum bound ${e}, clamping to ${e}`),Math.min(Math.max(o,t),e)}function D(o){let t=window.innerWidth||document.documentElement.clientWidth,e=window.innerHeight||document.documentElement.clientHeight;return o.top<e&&o.bottom>0&&o.left<t&&o.right>0}function x(o){if(typeof o=="number"){let t=u(o,0,2e3,"hitslop");return{top:t,left:t,right:t,bottom:t}}return{top:u(o.top,0,2e3,"hitslop - top"),left:u(o.left,0,2e3,"hitslop - left"),right:u(o.right,0,2e3,"hitslop - right"),bottom:u(o.bottom,0,2e3,"hitslop - bottom")}}function S(o,t){return{left:o.left-t.left,right:o.right+t.right,top:o.top-t.top,bottom:o.bottom+t.bottom}}function O(o,t){return!o||!t?o===t:o.left===t.left&&o.right===t.right&&o.top===t.top&&o.bottom===t.bottom}function v(o,t){return o.x>=t.left&&o.x<=t.right&&o.y>=t.top&&o.y<=t.bottom}function w(){let o=z(),t=K();return{isTouchDevice:o,isLimitedConnection:t,shouldRegister:!o&&!t}}function z(){return window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function K(){let o=navigator.connection;return o?/2g/.test(o.effectiveType)||o.saveData:!1}function k(o,t){return o!==void 0&&t!==o}function T(o,t,e){let i=0,n=1,r=t.x-o.x,c=t.y-o.y,s=(a,l)=>{if(a===0){if(l<0)return!1}else{let d=l/a;if(a<0){if(d>n)return!1;d>i&&(i=d)}else{if(d<i)return!1;d<n&&(n=d)}}return!0};return!s(-r,o.x-e.left)||!s(r,e.right-o.x)||!s(-c,o.y-e.top)||!s(c,e.bottom-o.y)?!1:i<=n}function j(o,t,e){let i=performance.now(),n={point:o,time:i},{x:r,y:c}=o;if(t.add(n),t.length<2)return{x:r,y:c};let[s,a]=t.getFirstLast();if(!s||!a)return{x:r,y:c};let l=(a.time-s.time)*.001;if(l===0)return{x:r,y:c};let d=a.point.x-s.point.x,p=a.point.y-s.point.y,b=d/l,M=p/l,R=e*.001,_=r+b*R,A=c+M*R;return{x:_,y:A}}var h=class{constructor(t){this.elements=t.dependencies.elements,this.callCallback=t.dependencies.callCallback,this.emit=t.dependencies.emit,this.abortController=new AbortController}abort(){this.abortController.abort()}handleError(t,e){console.error(`${this.constructor.name} error in ${e}:`,t)}};var I=class extends h{constructor(e){super(e);this.pendingMouseEvent=null;this.rafId=null;this.handleMouseMove=e=>{this.pendingMouseEvent=e,!this.rafId&&(this.rafId=requestAnimationFrame(()=>{this.pendingMouseEvent&&this.processMouseMovement(this.pendingMouseEvent),this.rafId=null}))};this.enableMousePrediction=e.settings.enableMousePrediction,this.trajectoryPredictionTime=e.settings.trajectoryPredictionTime,this.positionHistorySize=e.settings.positionHistorySize,this.trajectoryPositions=e.trajectoryPositions,this.initializeListeners()}initializeListeners(){let{signal:e}=this.abortController;document.addEventListener("mousemove",this.handleMouseMove,{signal:e})}updatePointerState(e){let i={x:e.clientX,y:e.clientY};this.trajectoryPositions.currentPoint=i,this.enableMousePrediction?this.trajectoryPositions.predictedPoint=j(i,this.trajectoryPositions.positions,this.trajectoryPredictionTime):this.trajectoryPositions.predictedPoint=i}cleanup(){this.abort(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingMouseEvent=null}processMouseMovement(e){try{this.updatePointerState(e);for(let i of this.elements.values()){if(!i.isIntersectingWithViewport)continue;let n=i.elementBounds.expandedRect;if(this.enableMousePrediction)T(this.trajectoryPositions.currentPoint,this.trajectoryPositions.predictedPoint,n)&&this.callCallback(i,{kind:"mouse",subType:"trajectory"});else if(v(this.trajectoryPositions.currentPoint,n)){this.callCallback(i,{kind:"mouse",subType:"hover"});return}}this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:this.enableMousePrediction,trajectoryPositions:this.trajectoryPositions})}catch(i){this.handleError(i,"processMouseMovement")}}};function H(o,t){let i=t.top-o.top,n=t.left-o.left;return i<-1?"down":i>1?"up":n<-1?"right":n>1?"left":"none"}function N(o,t,e){let{x:i,y:n}=o,r={x:i,y:n};switch(t){case"down":r.y+=e;break;case"up":r.y-=e;break;case"left":r.x-=e;break;case"right":r.x+=e;break;case"none":break;default:}return r}var P=class extends h{constructor(e){super(e);this.predictedScrollPoint=null;this.scrollDirection=null;this.scrollMargin=e.settings.scrollMargin,this.trajectoryPositions=e.trajectoryPositions}initializeListeners(){}cleanup(){this.abort(),this.resetScrollProps()}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(e,i){if(e.isIntersectingWithViewport)try{if(this.scrollDirection=this.scrollDirection??H(e.elementBounds.originalRect,i),this.scrollDirection==="none")return;this.predictedScrollPoint=this.predictedScrollPoint??N(this.trajectoryPositions.currentPoint,this.scrollDirection,this.scrollMargin),T(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,e.elementBounds.expandedRect)&&this.callCallback(e,{kind:"scroll",subType:this.scrollDirection}),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})}catch(n){this.handleError(n,"handleScrollPrefetch")}}handleError(e,i){super.handleError(e,i),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.trajectoryPositions.currentPoint,scrollDirection:"none"})}};import{tabbable as V}from"tabbable";function U(o,t,e,i){if(t!==null&&t>-1){let n=o?t-1:t+1;if(n>=0&&n<e.length&&e[n]===i)return n}return e.findIndex(n=>n===i)}var y=class extends h{constructor(e){super(e);this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=e=>{e.key==="Tab"&&(this.lastKeyDown=e)};this.handleFocusIn=e=>{try{if(!this.lastKeyDown)return;let i=e.target;if(!(i instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.tabbableElementsCache=V(document.documentElement));let n=this.lastKeyDown.shiftKey,r=U(n,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let c=[];for(let s=0;s<=this.tabOffset;s++){let a=n?r-s:r+s,l=this.tabbableElementsCache[a];l&&l instanceof Element&&this.elements.has(l)&&c.push(l)}c.forEach(s=>{let a=this.elements.get(s);a&&this.callCallback(a,{kind:"tab",subType:n?"reverse":"forwards"})})}catch(i){this.handleError(i,"handleFocusIn")}};this.tabOffset=e.settings.tabOffset,this.initializeListeners()}initializeListeners(){let{signal:e}=this.abortController;document.addEventListener("keydown",this.handleKeyDown,{signal:e}),document.addEventListener("focusin",this.handleFocusIn,{signal:e})}invalidateCache(){this.tabbableElementsCache=[],this.lastFocusedIndex=null}cleanup(){this.abort(),this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}};var L=class o{constructor(){this.elements=new Map;this.trajectoryPositions={positions:new E(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.isSetup=!1;this._globalCallbackHits={mouse:{hover:0,trajectory:0},tab:{forwards:0,reverse:0},scroll:{down:0,left:0,right:0,up:0},total:0};this._globalSettings={debug:!1,enableMousePrediction:!0,enableScrollPrediction:!0,positionHistorySize:8,trajectoryPredictionTime:120,scrollMargin:150,defaultHitSlop:{top:0,left:0,right:0,bottom:0},enableTabPrediction:!0,tabOffset:2};this.domObserver=null;this.positionObserver=null;this.eventListeners=new Map;this.mousePredictor=null;this.tabPredictor=null;this.scrollPredictor=null;this.handleDomMutations=t=>{t.length&&this.tabPredictor?.invalidateCache();for(let e of t)if(e.type==="childList"&&e.removedNodes.length>0)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};this.handlePositionChange=t=>{for(let e of t){let i=this.elements.get(e.target);i&&(this._globalSettings.enableScrollPrediction?this.scrollPredictor?.handleScrollPrefetch(i,e.boundingClientRect):v(this.trajectoryPositions.currentPoint,i.elementBounds.expandedRect)&&this.callCallback(i,{kind:"mouse",subType:"hover"}),this.handlePositionChangeDataUpdates(i,e))}this._globalSettings.enableScrollPrediction&&this.scrollPredictor?.resetScrollProps()};this.handlePositionChangeDataUpdates=(t,e)=>{let i=[],n=e.isIntersecting;t.isIntersectingWithViewport!==n&&(i.push("visibility"),t.isIntersectingWithViewport=n),n&&(i.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:e.boundingClientRect,expandedRect:S(e.boundingClientRect,t.elementBounds.hitSlop)}),i.length&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:i})}}static initialize(t){return this.isInitiated||(o.manager=new o),t!==void 0&&o.manager.alterGlobalSettings(t),o.manager}addEventListener(t,e,i){if(i?.signal?.aborted)return()=>{};let n=this.eventListeners.get(t)??[];n.push(e),this.eventListeners.set(t,n),i?.signal?.addEventListener("abort",()=>this.removeEventListener(t,e))}removeEventListener(t,e){let i=this.eventListeners.get(t);if(!i)return;let n=i.indexOf(e);n>-1&&i.splice(n,1)}emit(t){let e=this.eventListeners.get(t.type);e&&e.forEach((i,n)=>{try{i(t)}catch(r){console.error(`Error in ForesightManager event listener ${n} for ${t.type}:`,r)}})}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners}}static get isInitiated(){return!!o.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:t,callback:e,hitSlop:i,name:n,meta:r,reactivateAfter:c}){let{shouldRegister:s,isTouchDevice:a,isLimitedConnection:l}=w();if(!s)return{isLimitedConnection:l,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};if(this.elements.has(t))return{isLimitedConnection:l,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let d=t.getBoundingClientRect(),p=i?x(i):this._globalSettings.defaultHitSlop,b={element:t,callback:e,elementBounds:{originalRect:d,expandedRect:S(d,p),hitSlop:p},isHovering:!1,trajectoryHitData:{isTrajectoryHit:!1,trajectoryHitTime:0,trajectoryHitExpirationTimeoutId:void 0},name:n||t.id||"unnamed",isIntersectingWithViewport:D(d),registerCount:(this.registeredElements.get(t)?.registerCount??0)+1,meta:r??{},callbackInfo:{callbackFiredCount:0,lastCallbackFiredAt:void 0,lastCallbackRuntime:void 0,reactivateAfter:c??1/0,isCallbackActive:!0,isRunningCallback:!1,lastCallbackStatus:void 0}};return this.elements.set(t,b),this.positionObserver?.observe(t),this.emit({type:"elementRegistered",timestamp:Date.now(),elementData:b}),{isTouchDevice:a,isLimitedConnection:l,isRegistered:!0,unregister:()=>{}}}unregister(t,e){if(!this.elements.has(t))return;let i=this.elements.get(t);i?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(i.trajectoryHitData.trajectoryHitExpirationTimeoutId),this.positionObserver?.unobserve(t),this.elements.delete(t);let n=this.elements.size===0&&this.isSetup;n&&this.removeGlobalListeners(),i&&this.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:e,wasLastElement:n})}updateNumericSettings(t,e,i,n){return k(t,this._globalSettings[e])?(this._globalSettings[e]=u(t,i,n,e),!0):!1}updateBooleanSetting(t,e){return k(t,this._globalSettings[e])?(this._globalSettings[e]=t,!0):!1}alterGlobalSettings(t){let e=[],i=this._globalSettings.trajectoryPredictionTime;this.updateNumericSettings(t?.trajectoryPredictionTime,"trajectoryPredictionTime",10,200)&&e.push({setting:"trajectoryPredictionTime",oldValue:i,newValue:this._globalSettings.trajectoryPredictionTime});let r=this._globalSettings.positionHistorySize;this.updateNumericSettings(t?.positionHistorySize,"positionHistorySize",2,30)&&(e.push({setting:"positionHistorySize",oldValue:r,newValue:this._globalSettings.positionHistorySize}),this.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize));let s=this._globalSettings.scrollMargin;if(this.updateNumericSettings(t?.scrollMargin,"scrollMargin",30,300)){let g=this._globalSettings.scrollMargin;this.scrollPredictor&&(this.scrollPredictor.scrollMargin=g),e.push({setting:"scrollMargin",oldValue:s,newValue:g})}let l=this._globalSettings.tabOffset;this.updateNumericSettings(t?.tabOffset,"tabOffset",0,20)&&(this.tabPredictor&&(this.tabPredictor.tabOffset=this._globalSettings.tabOffset),e.push({setting:"tabOffset",oldValue:l,newValue:this._globalSettings.tabOffset}));let p=this._globalSettings.enableMousePrediction;this.updateBooleanSetting(t?.enableMousePrediction,"enableMousePrediction")&&e.push({setting:"enableMousePrediction",oldValue:p,newValue:this._globalSettings.enableMousePrediction});let M=this._globalSettings.enableScrollPrediction;this.updateBooleanSetting(t?.enableScrollPrediction,"enableScrollPrediction")&&(this._globalSettings.enableScrollPrediction?this.scrollPredictor=new P({dependencies:{callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements},settings:{scrollMargin:this._globalSettings.scrollMargin},trajectoryPositions:this.trajectoryPositions}):(this.scrollPredictor?.cleanup(),this.scrollPredictor=null),e.push({setting:"enableScrollPrediction",oldValue:M,newValue:this._globalSettings.enableScrollPrediction}));let _=this._globalSettings.enableTabPrediction;if(this.updateBooleanSetting(t?.enableTabPrediction,"enableTabPrediction")&&(e.push({setting:"enableTabPrediction",oldValue:_,newValue:this._globalSettings.enableTabPrediction}),this._globalSettings.enableTabPrediction?this.tabPredictor=new y({dependencies:{callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements},settings:{tabOffset:this._globalSettings.tabOffset}}):(this.tabPredictor?.cleanup(),this.tabPredictor=null)),t?.defaultHitSlop!==void 0){let g=this._globalSettings.defaultHitSlop,F=x(t.defaultHitSlop);O(g,F)||(this._globalSettings.defaultHitSlop=F,e.push({setting:"defaultHitSlop",oldValue:g,newValue:F}),this.forceUpdateAllElementBounds())}e.length>0&&this.emit({type:"managerSettingsChanged",timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:e})}updateHitCounters(t){switch(t.kind){case"mouse":this._globalCallbackHits.mouse[t.subType]++;break;case"tab":this._globalCallbackHits.tab[t.subType]++;break;case"scroll":this._globalCallbackHits.scroll[t.subType]++;break;default:}this._globalCallbackHits.total++}makeElementActive(t){t.callbackInfo.isRunningCallback||(t.callbackInfo.isCallbackActive=!0,this.positionObserver?.observe(t.element),this.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()}))}makeElementUnactive(t){t.callbackInfo.callbackFiredCount++,t.callbackInfo.lastCallbackFiredAt=Date.now(),t.callbackInfo.isRunningCallback=!0,t.callbackInfo.isCallbackActive=!1,t?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(t.trajectoryHitData.trajectoryHitExpirationTimeoutId),this.positionObserver?.unobserve(t.element)}callCallback(t,e){if(t.callbackInfo.isRunningCallback||!t.callbackInfo.isCallbackActive)return;this.makeElementUnactive(t),(async()=>{this.updateHitCounters(e),this.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:t,hitType:e});let n=performance.now();try{await t.callback(),t.callbackInfo.lastCallbackRuntime=performance.now()-n,t.callbackInfo.lastCallbackStatus="success",this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:t,hitType:e,elapsed:performance.now()-n,status:"success"})}catch(r){let c=r instanceof Error?r.message:String(r);console.error(`Error in callback for element ${t.name} (${t.element.tagName}):`,r),t.callbackInfo.lastCallbackRuntime=performance.now()-n,t.callbackInfo.lastCallbackStatus="error",this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:t,hitType:e,elapsed:t.callbackInfo.lastCallbackRuntime,status:"error",errorMessage:c})}t.callbackInfo.isRunningCallback=!1,t.callbackInfo.reactivateAfter!==1/0&&t.callbackInfo.reactivateAfter>0&&setTimeout(()=>{this.makeElementActive(t)},t.callbackInfo.reactivateAfter)})()}initializeGlobalListeners(){if(this.isSetup||typeof window>"u"||typeof document>"u")return;let t=this._globalSettings,e={callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),elements:this.elements};t.enableTabPrediction&&(this.tabPredictor=new y({dependencies:e,settings:{tabOffset:t.tabOffset}})),t.enableScrollPrediction&&(this.scrollPredictor=new P({dependencies:e,settings:{scrollMargin:t.scrollMargin},trajectoryPositions:this.trajectoryPositions})),this.mousePredictor=new I({dependencies:e,settings:{enableMousePrediction:t.enableMousePrediction,trajectoryPredictionTime:t.trajectoryPredictionTime,positionHistorySize:t.positionHistorySize},trajectoryPositions:this.trajectoryPositions}),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.positionObserver=new st(this.handlePositionChange),this.isSetup=!0}removeGlobalListeners(){this.isSetup=!1,this.domObserver?.disconnect(),this.domObserver=null,this.positionObserver?.disconnect(),this.positionObserver=null,this.mousePredictor?.cleanup(),this.tabPredictor?.cleanup(),this.scrollPredictor?.cleanup()}forceUpdateAllElementBounds(){for(let[,t]of this.elements)t.isIntersectingWithViewport&&this.forceUpdateElementBounds(t)}forceUpdateElementBounds(t){let e=t.element.getBoundingClientRect(),i=S(e,t.elementBounds.hitSlop);if(!O(i,t.elementBounds.expandedRect)){let n={...t,elementBounds:{...t.elementBounds,originalRect:e,expandedRect:i}};this.elements.set(t.element,n),this.emit({type:"elementDataUpdated",elementData:n,updatedProps:["bounds"]})}}};export{L as ForesightManager};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/foresight/ForesightManager.ts","../src/helpers/CircularBuffer.ts","../src/helpers/clampNumber.ts","../src/helpers/initialViewportState.ts","../src/helpers/rectAndHitSlop.ts","../src/helpers/shouldRegister.ts","../src/helpers/shouldUpdateSetting.ts","../src/helpers/lineSigmentIntersectsRect.ts","../src/helpers/predictNextMousePosition.ts","../src/foresight/BasePredictor.ts","../src/foresight/MousePredictor.ts","../src/helpers/getScrollDirection.ts","../src/helpers/predictNextScrollPosition.ts","../src/foresight/ScrollPredictor.ts","../src/foresight/TabPredictor.ts","../src/helpers/getFocusedElementIndex.ts"],"sourcesContent":["import { PositionObserver, PositionObserverEntry } from \"position-observer\"\r\nimport {\r\n DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n DEFAULT_ENABLE_TAB_PREDICTION,\r\n DEFAULT_HITSLOP,\r\n DEFAULT_POSITION_HISTORY_SIZE,\r\n DEFAULT_SCROLL_MARGIN,\r\n DEFAULT_TAB_OFFSET,\r\n DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n MAX_POSITION_HISTORY_SIZE,\r\n MAX_SCROLL_MARGIN,\r\n MAX_TAB_OFFSET,\r\n MAX_TRAJECTORY_PREDICTION_TIME,\r\n MIN_POSITION_HISTORY_SIZE,\r\n MIN_SCROLL_MARGIN,\r\n MIN_TAB_OFFSET,\r\n MIN_TRAJECTORY_PREDICTION_TIME,\r\n} from \"../constants\"\r\nimport { CircularBuffer } from \"../helpers/CircularBuffer\"\r\nimport { clampNumber } from \"../helpers/clampNumber\"\r\nimport { initialViewportState } from \"../helpers/initialViewportState\"\r\nimport {\r\n areRectsEqual,\r\n getExpandedRect,\r\n isPointInRectangle,\r\n normalizeHitSlop,\r\n} from \"../helpers/rectAndHitSlop\"\r\nimport { evaluateRegistrationConditions } from \"../helpers/shouldRegister\"\r\nimport { shouldUpdateSetting } from \"../helpers/shouldUpdateSetting\"\r\nimport type {\r\n CallbackHits,\r\n CallbackHitType,\r\n ElementUnregisteredReason,\r\n ForesightElement,\r\n ForesightElementData,\r\n ForesightEvent,\r\n ForesightEventListener,\r\n ForesightEventMap,\r\n ForesightManagerData,\r\n ForesightManagerSettings,\r\n ForesightRegisterOptions,\r\n ForesightRegisterResult,\r\n ManagerBooleanSettingKeys,\r\n NumericSettingKeys,\r\n TrajectoryPositions,\r\n UpdatedDataPropertyNames,\r\n UpdatedManagerSetting,\r\n UpdateForsightManagerSettings,\r\n} from \"../types/types\"\r\nimport type { PredictorDependencies } from \"./BasePredictor\"\r\nimport { MousePredictor } from \"./MousePredictor\"\r\nimport { ScrollPredictor } from \"./ScrollPredictor\"\r\nimport { TabPredictor } from \"./TabPredictor\"\r\n\r\n/**\r\n * Manages the prediction of user intent based on mouse trajectory and element interactions.\r\n *\r\n * ForesightManager is a singleton class responsible for:\r\n * - Registering HTML elements to monitor.\r\n * - Tracking mouse movements and predicting future cursor positions.\r\n * - Detecting when a predicted trajectory intersects with a registered element's bounds.\r\n * - Invoking callbacks associated with elements upon predicted or actual interaction.\r\n * - Optionally unregistering elements after their callback is triggered.\r\n * - Handling global settings for prediction behavior (e.g., history size, prediction time).\r\n * - Automatically updating element bounds on resize using {@link ResizeObserver}.\r\n * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.\r\n * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.\r\n *\r\n * It should be initialized once using {@link ForesightManager.initialize} and then\r\n * accessed via the static getter {@link ForesightManager.instance}.\r\n */\r\n\r\nexport class ForesightManager {\r\n private static manager: ForesightManager\r\n private elements: Map<ForesightElement, ForesightElementData> = new Map()\r\n private trajectoryPositions: TrajectoryPositions = {\r\n positions: new CircularBuffer(DEFAULT_POSITION_HISTORY_SIZE),\r\n currentPoint: { x: 0, y: 0 },\r\n predictedPoint: { x: 0, y: 0 },\r\n }\r\n private isSetup: boolean = false\r\n private _globalCallbackHits: CallbackHits = {\r\n mouse: {\r\n hover: 0,\r\n trajectory: 0,\r\n },\r\n tab: {\r\n forwards: 0,\r\n reverse: 0,\r\n },\r\n scroll: {\r\n down: 0,\r\n left: 0,\r\n right: 0,\r\n up: 0,\r\n },\r\n total: 0,\r\n }\r\n private _globalSettings: ForesightManagerSettings = {\r\n debug: false,\r\n enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,\r\n trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n scrollMargin: DEFAULT_SCROLL_MARGIN,\r\n defaultHitSlop: {\r\n top: DEFAULT_HITSLOP,\r\n left: DEFAULT_HITSLOP,\r\n right: DEFAULT_HITSLOP,\r\n bottom: DEFAULT_HITSLOP,\r\n },\r\n enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,\r\n tabOffset: DEFAULT_TAB_OFFSET,\r\n }\r\n\r\n private domObserver: MutationObserver | null = null\r\n private positionObserver: PositionObserver | null = null\r\n private eventListeners: Map<ForesightEvent, ForesightEventListener[]> = new Map()\r\n private mousePredictor: MousePredictor | null = null\r\n private tabPredictor: TabPredictor | null = null\r\n private scrollPredictor: ScrollPredictor | null = null\r\n private constructor() {}\r\n\r\n public static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager {\r\n if (!this.isInitiated) {\r\n ForesightManager.manager = new ForesightManager()\r\n }\r\n if (props !== undefined) {\r\n ForesightManager.manager.alterGlobalSettings(props)\r\n }\r\n\r\n return ForesightManager.manager\r\n }\r\n\r\n public addEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>,\r\n options?: { signal?: AbortSignal }\r\n ) {\r\n if (options?.signal?.aborted) {\r\n return () => {}\r\n }\r\n const listeners = this.eventListeners.get(eventType) ?? []\r\n listeners.push(listener as ForesightEventListener)\r\n this.eventListeners.set(eventType, listeners)\r\n options?.signal?.addEventListener(\"abort\", () => this.removeEventListener(eventType, listener))\r\n }\r\n\r\n public removeEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>\r\n ): void {\r\n const listeners = this.eventListeners.get(eventType)\r\n if (!listeners) {\r\n return\r\n }\r\n const index = listeners.indexOf(listener as ForesightEventListener)\r\n if (index > -1) {\r\n listeners.splice(index, 1)\r\n }\r\n }\r\n\r\n private emit<K extends ForesightEvent>(event: ForesightEventMap[K]): void {\r\n const listeners = this.eventListeners.get(event.type)\r\n if (!listeners) {\r\n return\r\n }\r\n\r\n listeners.forEach((listener, index) => {\r\n try {\r\n listener(event)\r\n } catch (error) {\r\n console.error(`Error in ForesightManager event listener ${index} for ${event.type}:`, error)\r\n }\r\n })\r\n }\r\n\r\n public get getManagerData(): Readonly<ForesightManagerData> {\r\n return {\r\n registeredElements: this.elements,\r\n globalSettings: this._globalSettings,\r\n globalCallbackHits: this._globalCallbackHits,\r\n eventListeners: this.eventListeners,\r\n historyPositions: this.trajectoryPositions,\r\n }\r\n }\r\n\r\n public static get isInitiated(): Readonly<boolean> {\r\n return !!ForesightManager.manager\r\n }\r\n\r\n public static get instance(): ForesightManager {\r\n return this.initialize()\r\n }\r\n\r\n public get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData> {\r\n return this.elements\r\n }\r\n\r\n public register({\r\n element,\r\n callback,\r\n hitSlop,\r\n name,\r\n }: ForesightRegisterOptions): ForesightRegisterResult {\r\n const { shouldRegister, isTouchDevice, isLimitedConnection } = evaluateRegistrationConditions()\r\n if (!shouldRegister) {\r\n return {\r\n isLimitedConnection,\r\n isTouchDevice,\r\n isRegistered: false,\r\n unregister: () => {},\r\n }\r\n }\r\n\r\n // Setup global listeners on every first element added to the manager. It gets removed again when the map is emptied\r\n if (!this.isSetup) {\r\n this.initializeGlobalListeners()\r\n }\r\n\r\n const initialRect = element.getBoundingClientRect()\r\n\r\n const normalizedHitSlop = hitSlop\r\n ? normalizeHitSlop(hitSlop)\r\n : this._globalSettings.defaultHitSlop\r\n\r\n const elementData: ForesightElementData = {\r\n element: element,\r\n callback,\r\n elementBounds: {\r\n originalRect: initialRect,\r\n expandedRect: getExpandedRect(initialRect, normalizedHitSlop),\r\n hitSlop: normalizedHitSlop,\r\n },\r\n isHovering: false,\r\n trajectoryHitData: {\r\n isTrajectoryHit: false,\r\n trajectoryHitTime: 0,\r\n trajectoryHitExpirationTimeoutId: undefined,\r\n },\r\n name: name || element.id || \"unnamed\",\r\n isIntersectingWithViewport: initialViewportState(initialRect),\r\n isRunningCallback: false,\r\n registerCount: (this.registeredElements.get(element)?.registerCount ?? 0) + 1,\r\n }\r\n\r\n this.elements.set(element, elementData)\r\n\r\n this.positionObserver?.observe(element)\r\n\r\n this.emit({\r\n type: \"elementRegistered\",\r\n timestamp: Date.now(),\r\n elementData,\r\n })\r\n\r\n return {\r\n isTouchDevice,\r\n isLimitedConnection,\r\n isRegistered: true,\r\n unregister: () => this.unregister(element, \"apiCall\"),\r\n }\r\n }\r\n\r\n private unregister(element: ForesightElement, unregisterReason: ElementUnregisteredReason) {\r\n if (!this.elements.has(element)) {\r\n return\r\n }\r\n\r\n const elementData = this.elements.get(element)\r\n\r\n // Clear any pending trajectory expiration timeout\r\n if (elementData?.trajectoryHitData.trajectoryHitExpirationTimeoutId) {\r\n clearTimeout(elementData.trajectoryHitData.trajectoryHitExpirationTimeoutId)\r\n }\r\n\r\n this.positionObserver?.unobserve(element)\r\n this.elements.delete(element)\r\n\r\n const wasLastElement = this.elements.size === 0 && this.isSetup\r\n\r\n if (wasLastElement) {\r\n this.removeGlobalListeners()\r\n }\r\n\r\n if (elementData) {\r\n this.emit({\r\n type: \"elementUnregistered\",\r\n elementData: elementData,\r\n timestamp: Date.now(),\r\n unregisterReason: unregisterReason,\r\n wasLastElement: wasLastElement,\r\n })\r\n }\r\n }\r\n\r\n private updateNumericSettings(\r\n newValue: number | undefined,\r\n setting: NumericSettingKeys,\r\n min: number,\r\n max: number\r\n ) {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n\r\n this._globalSettings[setting] = clampNumber(newValue, min, max, setting)\r\n\r\n return true\r\n }\r\n\r\n private updateBooleanSetting(\r\n newValue: boolean | undefined,\r\n setting: ManagerBooleanSettingKeys\r\n ): boolean {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n this._globalSettings[setting] = newValue\r\n return true\r\n }\r\n\r\n public alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void {\r\n const changedSettings: UpdatedManagerSetting[] = []\r\n\r\n const oldTrajectoryPredictionTime = this._globalSettings.trajectoryPredictionTime\r\n const trajectoryPredictionTimeChanged = this.updateNumericSettings(\r\n props?.trajectoryPredictionTime,\r\n \"trajectoryPredictionTime\",\r\n MIN_TRAJECTORY_PREDICTION_TIME,\r\n MAX_TRAJECTORY_PREDICTION_TIME\r\n )\r\n if (trajectoryPredictionTimeChanged) {\r\n changedSettings.push({\r\n setting: \"trajectoryPredictionTime\",\r\n oldValue: oldTrajectoryPredictionTime,\r\n newValue: this._globalSettings.trajectoryPredictionTime,\r\n })\r\n }\r\n\r\n const oldPositionHistorySize = this._globalSettings.positionHistorySize\r\n const positionHistorySizeChanged = this.updateNumericSettings(\r\n props?.positionHistorySize,\r\n \"positionHistorySize\",\r\n MIN_POSITION_HISTORY_SIZE,\r\n MAX_POSITION_HISTORY_SIZE\r\n )\r\n if (positionHistorySizeChanged) {\r\n changedSettings.push({\r\n setting: \"positionHistorySize\",\r\n oldValue: oldPositionHistorySize,\r\n newValue: this._globalSettings.positionHistorySize,\r\n })\r\n this.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize)\r\n }\r\n\r\n const oldScrollMargin = this._globalSettings.scrollMargin\r\n const scrollMarginChanged = this.updateNumericSettings(\r\n props?.scrollMargin,\r\n \"scrollMargin\",\r\n MIN_SCROLL_MARGIN,\r\n MAX_SCROLL_MARGIN\r\n )\r\n if (scrollMarginChanged) {\r\n const newValue = this._globalSettings.scrollMargin\r\n if (this.scrollPredictor) {\r\n this.scrollPredictor.scrollMargin = newValue\r\n }\r\n changedSettings.push({\r\n setting: \"scrollMargin\",\r\n oldValue: oldScrollMargin,\r\n newValue: newValue,\r\n })\r\n }\r\n\r\n const oldTabOffset = this._globalSettings.tabOffset\r\n const tabOffsetChanged = this.updateNumericSettings(\r\n props?.tabOffset,\r\n \"tabOffset\",\r\n MIN_TAB_OFFSET,\r\n MAX_TAB_OFFSET\r\n )\r\n if (tabOffsetChanged) {\r\n if (this.tabPredictor) {\r\n this.tabPredictor.tabOffset = this._globalSettings.tabOffset\r\n }\r\n changedSettings.push({\r\n setting: \"tabOffset\",\r\n oldValue: oldTabOffset,\r\n newValue: this._globalSettings.tabOffset,\r\n })\r\n }\r\n\r\n const oldEnableMousePrediction = this._globalSettings.enableMousePrediction\r\n const enableMousePredictionChanged = this.updateBooleanSetting(\r\n props?.enableMousePrediction,\r\n \"enableMousePrediction\"\r\n )\r\n if (enableMousePredictionChanged) {\r\n changedSettings.push({\r\n setting: \"enableMousePrediction\",\r\n oldValue: oldEnableMousePrediction,\r\n newValue: this._globalSettings.enableMousePrediction,\r\n })\r\n }\r\n\r\n const oldEnableScrollPrediction = this._globalSettings.enableScrollPrediction\r\n const enableScrollPredictionChanged = this.updateBooleanSetting(\r\n props?.enableScrollPrediction,\r\n \"enableScrollPrediction\"\r\n )\r\n if (enableScrollPredictionChanged) {\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor = new ScrollPredictor({\r\n dependencies: {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n },\r\n settings: {\r\n scrollMargin: this._globalSettings.scrollMargin,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n } else {\r\n this.scrollPredictor?.cleanup()\r\n this.scrollPredictor = null\r\n }\r\n changedSettings.push({\r\n setting: \"enableScrollPrediction\",\r\n oldValue: oldEnableScrollPrediction,\r\n newValue: this._globalSettings.enableScrollPrediction,\r\n })\r\n }\r\n\r\n const oldEnableTabPrediction = this._globalSettings.enableTabPrediction\r\n const enableTabPredictionChanged = this.updateBooleanSetting(\r\n props?.enableTabPrediction,\r\n \"enableTabPrediction\"\r\n )\r\n if (enableTabPredictionChanged) {\r\n changedSettings.push({\r\n setting: \"enableTabPrediction\",\r\n oldValue: oldEnableTabPrediction,\r\n newValue: this._globalSettings.enableTabPrediction,\r\n })\r\n if (this._globalSettings.enableTabPrediction) {\r\n this.tabPredictor = new TabPredictor({\r\n dependencies: {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n },\r\n settings: {\r\n tabOffset: this._globalSettings.tabOffset,\r\n },\r\n })\r\n } else {\r\n this.tabPredictor?.cleanup()\r\n this.tabPredictor = null\r\n }\r\n }\r\n\r\n if (props?.defaultHitSlop !== undefined) {\r\n const oldHitSlop = this._globalSettings.defaultHitSlop\r\n const normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop)\r\n\r\n if (!areRectsEqual(oldHitSlop, normalizedNewHitSlop)) {\r\n this._globalSettings.defaultHitSlop = normalizedNewHitSlop\r\n changedSettings.push({\r\n setting: \"defaultHitSlop\",\r\n oldValue: oldHitSlop,\r\n newValue: normalizedNewHitSlop,\r\n })\r\n this.forceUpdateAllElementBounds()\r\n }\r\n }\r\n\r\n if (changedSettings.length > 0) {\r\n this.emit({\r\n type: \"managerSettingsChanged\",\r\n timestamp: Date.now(),\r\n managerData: this.getManagerData,\r\n updatedSettings: changedSettings,\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.\r\n *\r\n * @param mutationsList - Array of MutationRecord objects describing the DOM changes\r\n *\r\n */\r\n private handleDomMutations = (mutationsList: MutationRecord[]) => {\r\n // Invalidate tabbale elements cache\r\n if (mutationsList.length) {\r\n this.tabPredictor?.invalidateCache()\r\n }\r\n for (const mutation of mutationsList) {\r\n if (mutation.type === \"childList\" && mutation.removedNodes.length > 0) {\r\n for (const element of this.elements.keys()) {\r\n if (!element.isConnected) {\r\n this.unregister(element, \"disconnected\")\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n private updateHitCounters(callbackHitType: CallbackHitType) {\r\n switch (callbackHitType.kind) {\r\n case \"mouse\":\r\n this._globalCallbackHits.mouse[callbackHitType.subType]++\r\n break\r\n case \"tab\":\r\n this._globalCallbackHits.tab[callbackHitType.subType]++\r\n break\r\n case \"scroll\":\r\n this._globalCallbackHits.scroll[callbackHitType.subType]++\r\n break\r\n default:\r\n callbackHitType satisfies never\r\n }\r\n this._globalCallbackHits.total++\r\n }\r\n\r\n private callCallback(elementData: ForesightElementData, callbackHitType: CallbackHitType) {\r\n if (elementData.isRunningCallback) {\r\n return\r\n }\r\n // We have this async wrapper so we can time exactly how long the callback takes\r\n const asyncCallbackWrapper = async () => {\r\n this.updateHitCounters(callbackHitType)\r\n this.emit({\r\n type: \"callbackInvoked\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n })\r\n const start = performance.now()\r\n try {\r\n await elementData.callback()\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: performance.now() - start,\r\n status: \"success\",\r\n })\r\n } catch (error) {\r\n const errorMessage = error instanceof Error ? error.message : String(error)\r\n console.error(\r\n `Error in callback for element ${elementData.name} (${elementData.element.tagName}):`,\r\n error\r\n )\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: performance.now() - start,\r\n status: \"error\",\r\n errorMessage,\r\n })\r\n }\r\n this.unregister(elementData.element, \"callbackHit\")\r\n }\r\n elementData.isRunningCallback = true\r\n asyncCallbackWrapper()\r\n }\r\n\r\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\r\n for (const entry of entries) {\r\n const elementData = this.elements.get(entry.target)\r\n if (!elementData) {\r\n continue\r\n }\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor?.handleScrollPrefetch(elementData, entry.boundingClientRect)\r\n } else {\r\n // If we dont check for scroll prediction, check if the user is hovering over the element during a scroll instead\r\n if (\r\n isPointInRectangle(\r\n this.trajectoryPositions.currentPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"mouse\",\r\n subType: \"hover\",\r\n })\r\n }\r\n }\r\n // Always call handlePositionChangeDataUpdates AFTER handleScrollPrefetch since handlePositionChangeDataUpdates alters the elementData\r\n this.handlePositionChangeDataUpdates(elementData, entry)\r\n }\r\n\r\n // End batch processing for scroll prediction\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor?.resetScrollProps()\r\n }\r\n }\r\n\r\n private handlePositionChangeDataUpdates = (\r\n elementData: ForesightElementData,\r\n entry: PositionObserverEntry\r\n ) => {\r\n const updatedProps: UpdatedDataPropertyNames[] = []\r\n const isNowIntersecting = entry.isIntersecting\r\n\r\n // Track visibility changes\r\n if (elementData.isIntersectingWithViewport !== isNowIntersecting) {\r\n updatedProps.push(\"visibility\")\r\n elementData.isIntersectingWithViewport = isNowIntersecting\r\n }\r\n\r\n // Handle bounds updates for intersecting elements\r\n if (isNowIntersecting) {\r\n updatedProps.push(\"bounds\")\r\n elementData.elementBounds = {\r\n hitSlop: elementData.elementBounds.hitSlop,\r\n originalRect: entry.boundingClientRect,\r\n expandedRect: getExpandedRect(entry.boundingClientRect, elementData.elementBounds.hitSlop),\r\n }\r\n }\r\n if (updatedProps.length) {\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: elementData,\r\n updatedProps,\r\n })\r\n }\r\n }\r\n\r\n private initializeGlobalListeners() {\r\n if (this.isSetup) {\r\n return\r\n }\r\n // To avoid setting up listeners while ssr\r\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\r\n return\r\n }\r\n\r\n const settings = this._globalSettings\r\n const dependencies: PredictorDependencies = {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n }\r\n\r\n if (settings.enableTabPrediction) {\r\n this.tabPredictor = new TabPredictor({\r\n dependencies,\r\n settings: {\r\n tabOffset: settings.tabOffset,\r\n },\r\n })\r\n }\r\n\r\n if (settings.enableScrollPrediction) {\r\n this.scrollPredictor = new ScrollPredictor({\r\n dependencies,\r\n settings: {\r\n scrollMargin: settings.scrollMargin,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n }\r\n\r\n // Always initialize mouse since if its not enabled we still check for hover and need it for scroll\r\n this.mousePredictor = new MousePredictor({\r\n dependencies,\r\n settings: {\r\n enableMousePrediction: settings.enableMousePrediction,\r\n trajectoryPredictionTime: settings.trajectoryPredictionTime,\r\n positionHistorySize: settings.positionHistorySize,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n\r\n //Mutation observer is to automatically unregister elements when they leave the DOM. Its a fail-safe for if the user forgets to do it.\r\n this.domObserver = new MutationObserver(this.handleDomMutations)\r\n this.domObserver.observe(document.documentElement, {\r\n childList: true,\r\n subtree: true,\r\n attributes: false,\r\n })\r\n\r\n // Handles all position based changes and update the rects of the elements. completely async to avoid dirtying the main thread.\r\n // Handles resize of elements\r\n // Handles resize of viewport\r\n // Handles scrolling\r\n this.positionObserver = new PositionObserver(this.handlePositionChange)\r\n\r\n this.isSetup = true\r\n }\r\n\r\n private removeGlobalListeners() {\r\n this.isSetup = false\r\n this.domObserver?.disconnect()\r\n this.domObserver = null\r\n this.positionObserver?.disconnect()\r\n this.positionObserver = null\r\n this.mousePredictor?.cleanup()\r\n this.tabPredictor?.cleanup()\r\n this.scrollPredictor?.cleanup()\r\n }\r\n\r\n private forceUpdateAllElementBounds() {\r\n for (const [, elementData] of this.elements) {\r\n if (elementData.isIntersectingWithViewport) {\r\n this.forceUpdateElementBounds(elementData)\r\n }\r\n }\r\n }\r\n /**\r\n * 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.\r\n * We need an observer for that\r\n */\r\n private forceUpdateElementBounds(elementData: ForesightElementData) {\r\n const newOriginalRect = elementData.element.getBoundingClientRect()\r\n const expandedRect = getExpandedRect(newOriginalRect, elementData.elementBounds.hitSlop)\r\n\r\n if (!areRectsEqual(expandedRect, elementData.elementBounds.expandedRect)) {\r\n const updatedElementData = {\r\n ...elementData,\r\n elementBounds: {\r\n ...elementData.elementBounds,\r\n originalRect: newOriginalRect,\r\n expandedRect,\r\n },\r\n }\r\n\r\n this.elements.set(elementData.element, updatedElementData)\r\n\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: updatedElementData,\r\n updatedProps: [\"bounds\"],\r\n })\r\n }\r\n }\r\n}\r\n","export class CircularBuffer<T> {\r\n private buffer: T[]\r\n private head: number = 0\r\n private count: number = 0\r\n private capacity: number\r\n\r\n constructor(capacity: number) {\r\n if (capacity <= 0) {\r\n throw new Error('CircularBuffer capacity must be greater than 0')\r\n }\r\n this.capacity = capacity\r\n this.buffer = new Array(capacity)\r\n }\r\n\r\n add(item: T): void {\r\n this.buffer[this.head] = item\r\n this.head = (this.head + 1) % this.capacity\r\n \r\n if (this.count < this.capacity) {\r\n this.count++\r\n }\r\n }\r\n\r\n getFirst(): T | undefined {\r\n if (this.count === 0) {\r\n return undefined\r\n }\r\n\r\n if (this.count < this.capacity) {\r\n return this.buffer[0]\r\n } else {\r\n return this.buffer[this.head]\r\n }\r\n }\r\n\r\n getLast(): T | undefined {\r\n if (this.count === 0) {\r\n return undefined\r\n }\r\n\r\n if (this.count < this.capacity) {\r\n return this.buffer[this.count - 1]\r\n } else {\r\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity\r\n return this.buffer[lastIndex]\r\n }\r\n }\r\n\r\n getFirstLast(): [T | undefined, T | undefined] {\r\n if (this.count === 0) {\r\n return [undefined, undefined]\r\n }\r\n\r\n if (this.count === 1) {\r\n const item = this.count < this.capacity ? this.buffer[0] : this.buffer[this.head]\r\n return [item, item]\r\n }\r\n\r\n const first = this.getFirst()\r\n const last = this.getLast()\r\n return [first, last]\r\n }\r\n\r\n resize(newCapacity: number): void {\r\n if (newCapacity <= 0) {\r\n throw new Error('CircularBuffer capacity must be greater than 0')\r\n }\r\n\r\n if (newCapacity === this.capacity) {\r\n return\r\n }\r\n\r\n const currentItems = this.getAllItems()\r\n this.capacity = newCapacity\r\n this.buffer = new Array(newCapacity)\r\n this.head = 0\r\n this.count = 0\r\n\r\n if (currentItems.length > newCapacity) {\r\n const itemsToKeep = currentItems.slice(-newCapacity)\r\n for (const item of itemsToKeep) {\r\n this.add(item)\r\n }\r\n } else {\r\n for (const item of currentItems) {\r\n this.add(item)\r\n }\r\n }\r\n }\r\n\r\n private getAllItems(): T[] {\r\n if (this.count === 0) {\r\n return []\r\n }\r\n\r\n const result: T[] = new Array(this.count)\r\n \r\n if (this.count < this.capacity) {\r\n for (let i = 0; i < this.count; i++) {\r\n result[i] = this.buffer[i]\r\n }\r\n } else {\r\n const startIndex = this.head\r\n for (let i = 0; i < this.capacity; i++) {\r\n const bufferIndex = (startIndex + i) % this.capacity\r\n result[i] = this.buffer[bufferIndex]\r\n }\r\n }\r\n \r\n return result\r\n }\r\n\r\n clear(): void {\r\n this.head = 0\r\n this.count = 0\r\n }\r\n\r\n get length(): number {\r\n return this.count\r\n }\r\n\r\n get size(): number {\r\n return this.capacity\r\n }\r\n\r\n get isFull(): boolean {\r\n return this.count === this.capacity\r\n }\r\n\r\n get isEmpty(): boolean {\r\n return this.count === 0\r\n }\r\n}","export function clampNumber(\n number: number,\n lowerBound: number,\n upperBound: number,\n settingName: string\n) {\n if (number < lowerBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is below minimum bound ${lowerBound}, clamping to ${lowerBound}`\n )\n } else if (number > upperBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is above maximum bound ${upperBound}, clamping to ${upperBound}`\n )\n }\n\n return Math.min(Math.max(number, lowerBound), upperBound)\n}\n","export function initialViewportState(rect: DOMRect) {\n const viewportWidth = window.innerWidth || document.documentElement.clientWidth\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight\n\n return rect.top < viewportHeight && rect.bottom > 0 && rect.left < viewportWidth && rect.right > 0\n}\n","import type { HitSlop, Point, Rect } from \"../types/types\"\nimport { MAX_HITSLOP, MIN_HITSLOP } from \"../constants\"\nimport { clampNumber } from \"./clampNumber\"\n\n/**\n * Normalizes a `hitSlop` value into a {@link Rect} object.\n * If `hitSlop` is a number, it's applied uniformly to all sides (top, left, right, bottom).\n * If `hitSlop` is already a `Rect` object, it's returned as is.\n *\n * @param hitSlop - A number for uniform slop, or a {@link Rect} object for specific slop per side.\n * @returns A {@link Rect} object with `top`, `left`, `right`, and `bottom` properties.\n */\nexport function normalizeHitSlop(hitSlop: HitSlop): Rect {\n if (typeof hitSlop === \"number\") {\n const clampedValue = clampNumber(hitSlop, MIN_HITSLOP, MAX_HITSLOP, \"hitslop\")\n return {\n top: clampedValue,\n left: clampedValue,\n right: clampedValue,\n bottom: clampedValue,\n }\n }\n\n return {\n top: clampNumber(hitSlop.top, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - top\"),\n left: clampNumber(hitSlop.left, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - left\"),\n right: clampNumber(hitSlop.right, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - right\"),\n bottom: clampNumber(hitSlop.bottom, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - bottom\"),\n }\n}\n\n/**\n * Calculates an expanded rectangle by applying a `hitSlop` to a base rectangle.\n * The `hitSlop` values define how much to extend each side of the `baseRect` outwards.\n *\n * @param baseRect - The original {@link Rect} or `DOMRect` to expand.\n * @param hitSlop - A {@link Rect} object defining how much to expand each side\n * (e.g., `hitSlop.left` expands the left boundary further to the left).\n * @returns A new {@link Rect} object representing the expanded area.\n */\nexport function getExpandedRect(baseRect: Rect | DOMRect, hitSlop: Rect): Rect {\n return {\n left: baseRect.left - hitSlop.left,\n right: baseRect.right + hitSlop.right,\n top: baseRect.top - hitSlop.top,\n bottom: baseRect.bottom + hitSlop.bottom,\n }\n}\n\n/**\n * Checks if two rectangle objects are equal by comparing their respective\n * `top`, `left`, `right`, and `bottom` properties.\n * Handles cases where one or both rects might be null or undefined.\n *\n * @param rect1 - The first {@link Rect} object to compare.\n * @param rect2 - The second {@link Rect} object to compare.\n * @returns `true` if the rectangles have identical dimensions or if both are null/undefined,\n * `false` otherwise.\n */\nexport function areRectsEqual(rect1: Rect, rect2: Rect): boolean {\n if (!rect1 || !rect2) return rect1 === rect2\n return (\n rect1.left === rect2.left &&\n rect1.right === rect2.right &&\n rect1.top === rect2.top &&\n rect1.bottom === rect2.bottom\n )\n}\n\nexport function isPointInRectangle(point: Point, rect: Rect): boolean {\n return (\n point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom\n )\n}\n","type ShouldRegister = {\n shouldRegister: boolean\n isTouchDevice: boolean\n isLimitedConnection: boolean\n}\n\nexport function evaluateRegistrationConditions(): ShouldRegister {\n const isTouchDevice = userUsesTouchDevice()\n const isLimitedConnection = hasConnectionLimitations()\n const shouldRegister = !isTouchDevice && !isLimitedConnection\n return { isTouchDevice, isLimitedConnection, shouldRegister }\n}\n\n/**\n * Detects if the current device is likely a touch-enabled device.\n * It checks for coarse pointer media query and the presence of touch points.\n *\n * @returns `true` if the device is likely touch-enabled, `false` otherwise.\n */\nfunction userUsesTouchDevice(): boolean {\n return window.matchMedia(\"(pointer: coarse)\").matches && navigator.maxTouchPoints > 0\n}\n\n/**\n * Checks if the user has connection limitations (slow network or data saver enabled).\n *\n * @returns {boolean} True if connection is limited, false if safe to prefetch\n * @example\n * if (!hasConnectionLimitations()) {\n * prefetchResource('/api/data');\n * }\n */\nfunction hasConnectionLimitations(): boolean {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const connection = (navigator as any).connection\n if (!connection) return false\n\n return /2g/.test(connection.effectiveType) || connection.saveData\n}\n","/**\n * Checks if a setting should be updated.\n * Returns true if the newValue is defined and different from the currentValue.\n * Uses a type predicate to narrow the type of newValue in the calling scope.\n *\n * @param newValue The potentially new value for the setting (can be undefined).\n * @param currentValue The current value of the setting.\n * @returns True if the setting should be updated, false otherwise.\n */\nexport function shouldUpdateSetting<T>(\n newValue: T | undefined,\n currentValue: T\n): newValue is NonNullable<T> {\n // NonNullable<T> ensures that if T itself could be undefined (e.g. T = number | undefined),\n // the predicate narrows to the non-undefined part (e.g. number).\n // If T is already non-nullable (e.g. T = number), it remains T (e.g. number).\n return newValue !== undefined && currentValue !== newValue\n}\n","import type { Point, Rect } from \"../types/types\"\n\n/**\n * Determines if a line segment intersects with a given rectangle.\n * This function implements the Liang-Barsky line clipping algorithm.\n *\n * @param p1 - The starting {@link Point} of the line segment.\n * @param p2 - The ending {@link Point} of the line segment.\n * @param rect - The {@link Rect} to check for intersection.\n * @returns `true` if the line segment intersects the rectangle, `false` otherwise.\n */\nexport function lineSegmentIntersectsRect(p1: Point, p2: Point, rect: Rect): boolean {\n let t0 = 0.0\n let t1 = 1.0\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n\n const clipTest = (p: number, q: number): boolean => {\n if (p === 0) {\n // Line is parallel to this clipping edge\n if (q < 0) return false // Line is outside the clipping edge\n } else {\n const r = q / p\n if (p < 0) {\n // Line proceeds from outside to inside\n if (r > t1) return false // Line segment ends before crossing edge\n if (r > t0) t0 = r // Update entry point\n } else {\n // Line proceeds from inside to outside\n if (r < t0) return false // Line segment starts after crossing edge\n if (r < t1) t1 = r // Update exit point\n }\n }\n return true\n }\n\n // Clip against all four edges of the rectangle\n if (!clipTest(-dx, p1.x - rect.left)) return false // Left edge\n if (!clipTest(dx, rect.right - p1.x)) return false // Right edge\n if (!clipTest(-dy, p1.y - rect.top)) return false // Top edge\n if (!clipTest(dy, rect.bottom - p1.y)) return false // Bottom edge\n\n // If t0 <= t1, the line segment intersects the rectangle (or lies within it)\n return t0 <= t1\n}\n","import type { MousePosition, Point } from \"../types/types\"\nimport type { CircularBuffer } from \"./CircularBuffer\"\n\n/**\n * Predicts the next mouse position based on a history of recent movements.\n * It calculates velocity from the historical data and extrapolates a future point.\n * The `buffer` is mutated by this function: the new `currentPoint` is added,\n * automatically overwriting the oldest entry when the buffer is full.\n *\n * @param currentPoint - The current actual mouse coordinates.\n * @param buffer - A circular buffer of previous mouse positions with timestamps.\n * This buffer will be modified by this function.\n * @param trajectoryPredictionTimeInMs - How far into the future (in milliseconds)\n * to predict the mouse position.\n * @returns The predicted {@link Point} (x, y coordinates). If history is insufficient\n * (less than 2 points) or time delta is zero, returns the `currentPoint`.\n */\nexport function predictNextMousePosition(\n currentPoint: Point,\n buffer: CircularBuffer<MousePosition>,\n trajectoryPredictionTimeInMs: number\n): Point {\n const now = performance.now()\n const currentPosition: MousePosition = { point: currentPoint, time: now }\n const { x, y } = currentPoint\n\n buffer.add(currentPosition)\n\n if (buffer.length < 2) {\n return { x, y }\n }\n\n const [first, last] = buffer.getFirstLast()\n if (!first || !last) {\n return { x, y }\n }\n const dt = (last.time - first.time) * 0.001\n\n if (dt === 0) {\n return { x, y }\n }\n\n const dx = last.point.x - first.point.x\n const dy = last.point.y - first.point.y\n const vx = dx / dt\n const vy = dy / dt\n\n const trajectoryPredictionTimeInSeconds = trajectoryPredictionTimeInMs * 0.001\n const predictedX = x + vx * trajectoryPredictionTimeInSeconds\n const predictedY = y + vy * trajectoryPredictionTimeInSeconds\n\n return { x: predictedX, y: predictedY }\n}\n","import type {\r\n CallbackHitType,\r\n ForesightElement,\r\n ForesightElementData,\r\n ForesightEvent,\r\n ForesightEventMap,\r\n} from \"../types/types\"\r\n\r\nexport type CallCallbackFunction = (\r\n elementData: ForesightElementData,\r\n callbackHitType: CallbackHitType\r\n) => void\r\n\r\nexport type EmitFunction = <K extends ForesightEvent>(event: ForesightEventMap[K]) => void\r\n\r\nexport type PredictorDependencies = {\r\n elements: ReadonlyMap<ForesightElement, ForesightElementData>\r\n callCallback: CallCallbackFunction\r\n emit: EmitFunction\r\n}\r\n\r\nexport interface BasePredictorConfig {\r\n dependencies: PredictorDependencies\r\n}\r\n\r\nexport type PredictorProps = PredictorDependencies\r\n\r\nexport abstract class BasePredictor {\r\n protected abortController: AbortController\r\n protected elements: ReadonlyMap<ForesightElement, ForesightElementData>\r\n protected callCallback: CallCallbackFunction\r\n protected emit: EmitFunction\r\n \r\n constructor(config: BasePredictorConfig) {\r\n this.elements = config.dependencies.elements\r\n this.callCallback = config.dependencies.callCallback\r\n this.emit = config.dependencies.emit\r\n this.abortController = new AbortController()\r\n }\r\n \r\n protected abort(): void {\r\n this.abortController.abort()\r\n }\r\n\r\n public abstract cleanup(): void\r\n protected abstract initializeListeners(): void\r\n \r\n protected handleError(error: unknown, context: string): void {\r\n console.error(`${this.constructor.name} error in ${context}:`, error)\r\n }\r\n}\r\n","import { lineSegmentIntersectsRect } from \"../helpers/lineSigmentIntersectsRect\"\r\nimport { predictNextMousePosition } from \"../helpers/predictNextMousePosition\"\r\nimport { isPointInRectangle } from \"../helpers/rectAndHitSlop\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\nimport type { TrajectoryPositions } from \"../types/types\"\r\n\r\nexport interface MousePredictorSettings {\r\n enableMousePrediction: boolean\r\n trajectoryPredictionTime: number\r\n positionHistorySize: number\r\n}\r\n\r\nexport interface MousePredictorConfig extends BasePredictorConfig {\r\n settings: MousePredictorSettings\r\n trajectoryPositions: TrajectoryPositions\r\n}\r\n\r\nexport class MousePredictor extends BasePredictor {\r\n private pendingMouseEvent: MouseEvent | null = null\r\n private rafId: number | null = null\r\n private enableMousePrediction: boolean\r\n public trajectoryPredictionTime: number\r\n public positionHistorySize: number\r\n private trajectoryPositions: TrajectoryPositions\r\n\r\n constructor(config: MousePredictorConfig) {\r\n super(config)\r\n this.enableMousePrediction = config.settings.enableMousePrediction\r\n this.trajectoryPredictionTime = config.settings.trajectoryPredictionTime\r\n this.positionHistorySize = config.settings.positionHistorySize\r\n this.trajectoryPositions = config.trajectoryPositions\r\n this.initializeListeners()\r\n }\r\n protected initializeListeners() {\r\n const { signal } = this.abortController\r\n document.addEventListener(\"mousemove\", this.handleMouseMove, { signal })\r\n }\r\n\r\n private handleMouseMove = (e: MouseEvent) => {\r\n this.pendingMouseEvent = e\r\n if (this.rafId) return\r\n\r\n this.rafId = requestAnimationFrame(() => {\r\n if (this.pendingMouseEvent) {\r\n this.processMouseMovement(this.pendingMouseEvent)\r\n }\r\n this.rafId = null\r\n })\r\n }\r\n private updatePointerState(e: MouseEvent): void {\r\n const currentPoint = { x: e.clientX, y: e.clientY }\r\n this.trajectoryPositions.currentPoint = currentPoint\r\n if (this.enableMousePrediction) {\r\n this.trajectoryPositions.predictedPoint = predictNextMousePosition(\r\n currentPoint,\r\n this.trajectoryPositions.positions,\r\n this.trajectoryPredictionTime\r\n )\r\n } else {\r\n this.trajectoryPositions.predictedPoint = currentPoint\r\n }\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n if (this.rafId) {\r\n cancelAnimationFrame(this.rafId)\r\n this.rafId = null\r\n }\r\n this.pendingMouseEvent = null\r\n }\r\n\r\n private processMouseMovement(e: MouseEvent): void {\r\n try {\r\n this.updatePointerState(e)\r\n\r\n // Use for...of instead of forEach for better performance in hot code path\r\n // Avoids function call overhead and iterator creation on every mouse move\r\n for (const currentData of this.elements.values()) {\r\n if (!currentData.isIntersectingWithViewport) {\r\n continue\r\n }\r\n const expandedRect = currentData.elementBounds.expandedRect\r\n\r\n if (!this.enableMousePrediction) {\r\n if (isPointInRectangle(this.trajectoryPositions.currentPoint, expandedRect)) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"hover\" })\r\n return\r\n }\r\n // when enable mouse prediction is off, we only check if the mouse is physically hovering over the element\r\n } else if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.trajectoryPositions.predictedPoint,\r\n expandedRect\r\n )\r\n ) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"trajectory\" })\r\n }\r\n }\r\n\r\n this.emit({\r\n type: \"mouseTrajectoryUpdate\",\r\n predictionEnabled: this.enableMousePrediction,\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"processMouseMovement\")\r\n }\r\n }\r\n}\r\n","import type { Rect, ScrollDirection } from \"../types/types\"\n\nexport function getScrollDirection(oldRect: Rect, newRect: Rect): ScrollDirection {\n const scrollThreshold = 1\n const deltaY = newRect.top - oldRect.top\n const deltaX = newRect.left - oldRect.left\n\n // Check vertical scroll first (most common)\n if (deltaY < -scrollThreshold) {\n return \"down\" // Element moved up in viewport = scrolled down\n } else if (deltaY > scrollThreshold) {\n return \"up\" // Element moved down in viewport = scrolled up\n }\n\n // Check horizontal scroll\n if (deltaX < -scrollThreshold) {\n return \"right\" // Element moved left in viewport = scrolled right\n } else if (deltaX > scrollThreshold) {\n return \"left\" // Element moved right in viewport = scrolled left\n }\n\n return \"none\" // No significant movement detected\n}\n","import type { Point, ScrollDirection } from \"../types/types\"\n\nexport function predictNextScrollPosition(\n currentPoint: Point,\n direction: ScrollDirection,\n scrollMargin: number\n) {\n const { x, y } = currentPoint\n const predictedPoint = { x, y }\n\n switch (direction) {\n case \"down\":\n predictedPoint.y += scrollMargin\n break\n case \"up\":\n predictedPoint.y -= scrollMargin\n break\n case \"left\":\n predictedPoint.x -= scrollMargin\n break\n case \"right\":\n predictedPoint.x += scrollMargin\n break\n case \"none\":\n break\n default:\n direction satisfies never\n }\n return predictedPoint\n}\n","import type {\r\n ForesightElementData,\r\n Point,\r\n ScrollDirection,\r\n TrajectoryPositions,\r\n} from \"../types/types\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\nimport { getScrollDirection } from \"../helpers/getScrollDirection\"\r\nimport { predictNextScrollPosition } from \"../helpers/predictNextScrollPosition\"\r\nimport { lineSegmentIntersectsRect } from \"../helpers/lineSigmentIntersectsRect\"\r\n\r\nexport interface ScrollPredictorSettings {\r\n scrollMargin: number\r\n}\r\n\r\nexport interface ScrollPredictorConfig extends BasePredictorConfig {\r\n settings: ScrollPredictorSettings\r\n trajectoryPositions: Readonly<TrajectoryPositions>\r\n}\r\n\r\nexport class ScrollPredictor extends BasePredictor {\r\n protected initializeListeners(): void {\r\n // ScrollPredictor doesn't need direct event listeners\r\n // as it's called by the ForesightManager during position changes\r\n }\r\n\r\n private predictedScrollPoint: Point | null = null\r\n private scrollDirection: ScrollDirection | null = null\r\n public scrollMargin: number\r\n private trajectoryPositions: Readonly<TrajectoryPositions>\r\n\r\n constructor(config: ScrollPredictorConfig) {\r\n super(config)\r\n this.scrollMargin = config.settings.scrollMargin\r\n this.trajectoryPositions = config.trajectoryPositions\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n this.resetScrollProps()\r\n }\r\n\r\n public resetScrollProps(): void {\r\n this.scrollDirection = null\r\n this.predictedScrollPoint = null\r\n }\r\n\r\n public handleScrollPrefetch(elementData: ForesightElementData, newRect: DOMRect): void {\r\n if (!elementData.isIntersectingWithViewport) {\r\n return\r\n }\r\n\r\n try {\r\n // ONCE per handlePositionChange batch we decide what the scroll direction is\r\n this.scrollDirection =\r\n this.scrollDirection ?? getScrollDirection(elementData.elementBounds.originalRect, newRect)\r\n\r\n if (this.scrollDirection === \"none\") {\r\n return\r\n }\r\n\r\n // ONCE per handlePositionChange batch we decide the predicted scroll point\r\n this.predictedScrollPoint =\r\n this.predictedScrollPoint ??\r\n predictNextScrollPosition(\r\n this.trajectoryPositions.currentPoint,\r\n this.scrollDirection,\r\n this.scrollMargin\r\n )\r\n\r\n // Check if the scroll is going to intersect with an registered element\r\n if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.predictedScrollPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"scroll\",\r\n subType: this.scrollDirection,\r\n })\r\n }\r\n\r\n this.emit({\r\n type: \"scrollTrajectoryUpdate\",\r\n currentPoint: this.trajectoryPositions.currentPoint,\r\n predictedPoint: this.predictedScrollPoint,\r\n scrollDirection: this.scrollDirection,\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"handleScrollPrefetch\")\r\n }\r\n }\r\n\r\n protected handleError(error: unknown, context: string): void {\r\n super.handleError(error, context)\r\n\r\n // Emit fallback event on error\r\n this.emit({\r\n type: \"scrollTrajectoryUpdate\",\r\n currentPoint: this.trajectoryPositions.currentPoint,\r\n predictedPoint: this.trajectoryPositions.currentPoint,\r\n scrollDirection: \"none\",\r\n })\r\n }\r\n}\r\n","import { tabbable, type FocusableElement } from \"tabbable\"\r\nimport { getFocusedElementIndex } from \"../helpers/getFocusedElementIndex\"\r\nimport type { ForesightElement } from \"../types/types\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\n\r\nexport interface TabPredictorSettings {\r\n tabOffset: number\r\n}\r\n\r\nexport interface TabPredictorConfig extends BasePredictorConfig {\r\n settings: TabPredictorSettings\r\n}\r\n\r\n/**\r\n * Manages the prediction of user intent based on Tab key navigation.\r\n *\r\n * This class is a specialist module controlled by the ForesightManager.\r\n * Its responsibilities are:\r\n * - Listening for `keydown` and `focusin` events to detect tabbing.\r\n * - Caching the list of tabbable elements on the page for performance.\r\n * - Invalidating the cache when the DOM changes.\r\n * - Predicting which registered elements the user is about to focus.\r\n * - Calling a provided callback when a prediction is made.\r\n */\r\nexport class TabPredictor extends BasePredictor {\r\n // Internal state for tab prediction\r\n private lastKeyDown: KeyboardEvent | null = null\r\n private tabbableElementsCache: FocusableElement[] = []\r\n private lastFocusedIndex: number | null = null\r\n public tabOffset: number\r\n\r\n constructor(config: TabPredictorConfig) {\r\n super(config)\r\n this.tabOffset = config.settings.tabOffset\r\n this.initializeListeners()\r\n }\r\n\r\n protected initializeListeners(): void {\r\n const { signal } = this.abortController\r\n document.addEventListener(\"keydown\", this.handleKeyDown, { signal })\r\n document.addEventListener(\"focusin\", this.handleFocusIn, { signal })\r\n }\r\n\r\n public invalidateCache() {\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n this.lastKeyDown = null\r\n }\r\n\r\n // We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.\r\n // We dont use handleKeyDown for the full event because of 2 main reasons:\r\n // 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)\r\n // 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)\r\n private handleKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Tab\") {\r\n this.lastKeyDown = e\r\n }\r\n }\r\n\r\n private handleFocusIn = (e: FocusEvent) => {\r\n try {\r\n if (!this.lastKeyDown) {\r\n return\r\n }\r\n const targetElement = e.target\r\n if (!(targetElement instanceof HTMLElement)) {\r\n return\r\n }\r\n\r\n // tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values\r\n if (!this.tabbableElementsCache.length || this.lastFocusedIndex === -1) {\r\n this.tabbableElementsCache = tabbable(document.documentElement)\r\n }\r\n\r\n const isReversed = this.lastKeyDown.shiftKey\r\n\r\n const currentIndex: number = getFocusedElementIndex(\r\n isReversed,\r\n this.lastFocusedIndex,\r\n this.tabbableElementsCache,\r\n targetElement\r\n )\r\n\r\n this.lastFocusedIndex = currentIndex\r\n\r\n this.lastKeyDown = null\r\n const elementsToPredict: ForesightElement[] = []\r\n for (let i = 0; i <= this.tabOffset; i++) {\r\n const elementIndex = isReversed ? currentIndex - i : currentIndex + i\r\n const element = this.tabbableElementsCache[elementIndex]\r\n\r\n // Type guard: ensure element exists and is a valid ForesightElement\r\n if (element && element instanceof Element && this.elements.has(element)) {\r\n elementsToPredict.push(element)\r\n }\r\n }\r\n\r\n elementsToPredict.forEach(element => {\r\n const elementData = this.elements.get(element)\r\n if (elementData) {\r\n this.callCallback(elementData, {\r\n kind: \"tab\",\r\n subType: isReversed ? \"reverse\" : \"forwards\",\r\n })\r\n }\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"handleFocusIn\")\r\n }\r\n }\r\n}\r\n","import type { FocusableElement } from \"tabbable\"\r\n\r\n/**\r\n * Finds the index of a focused element within a cache of tabbable elements.\r\n * It uses a predictive search for O(1) performance in the common case of\r\n * sequential tabbing, and falls back to a linear search O(n) if the\r\n * prediction fails.\r\n *\r\n * @param isReversed - True if the user is tabbing backward (Shift+Tab).\r\n * @param lastFocusedIndex - The index of the previously focused element, or null if none.\r\n * @param tabbableElementsCache - The array of all tabbable elements.\r\n * @param targetElement - The new HTML element that has received focus.\r\n * @returns The index of the targetElement in the cache, or -1 if not found.\r\n */\r\nexport function getFocusedElementIndex(\r\n isReversed: boolean,\r\n lastFocusedIndex: number | null,\r\n tabbableElementsCache: FocusableElement[],\r\n targetElement: HTMLElement\r\n): number {\r\n // First, try to predict the next index based on the last known position.\r\n if (lastFocusedIndex !== null && lastFocusedIndex > -1) {\r\n const predictedIndex = isReversed ? lastFocusedIndex - 1 : lastFocusedIndex + 1\r\n\r\n // Check if the prediction is valid and correct.\r\n if (\r\n predictedIndex >= 0 &&\r\n predictedIndex < tabbableElementsCache.length &&\r\n tabbableElementsCache[predictedIndex] === targetElement\r\n ) {\r\n return predictedIndex\r\n }\r\n }\r\n\r\n return tabbableElementsCache.findIndex(element => element === targetElement)\r\n}\r\n"],"mappings":"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","names":["PositionObserver","CircularBuffer","capacity","item","lastIndex","first","last","newCapacity","currentItems","itemsToKeep","result","i","startIndex","bufferIndex","clampNumber","number","lowerBound","upperBound","settingName","initialViewportState","rect","viewportWidth","viewportHeight","normalizeHitSlop","hitSlop","clampedValue","clampNumber","getExpandedRect","baseRect","areRectsEqual","rect1","rect2","isPointInRectangle","point","rect","evaluateRegistrationConditions","isTouchDevice","userUsesTouchDevice","isLimitedConnection","hasConnectionLimitations","connection","shouldUpdateSetting","newValue","currentValue","lineSegmentIntersectsRect","p1","p2","rect","t0","t1","dx","dy","clipTest","p","q","r","predictNextMousePosition","currentPoint","buffer","trajectoryPredictionTimeInMs","now","currentPosition","x","y","first","last","dt","dx","dy","vx","vy","trajectoryPredictionTimeInSeconds","predictedX","predictedY","BasePredictor","config","error","context","MousePredictor","BasePredictor","config","signal","currentPoint","predictNextMousePosition","currentData","expandedRect","lineSegmentIntersectsRect","isPointInRectangle","error","getScrollDirection","oldRect","newRect","deltaY","deltaX","predictNextScrollPosition","currentPoint","direction","scrollMargin","x","y","predictedPoint","ScrollPredictor","BasePredictor","config","elementData","newRect","getScrollDirection","predictNextScrollPosition","lineSegmentIntersectsRect","error","context","tabbable","getFocusedElementIndex","isReversed","lastFocusedIndex","tabbableElementsCache","targetElement","predictedIndex","element","TabPredictor","BasePredictor","config","targetElement","tabbable","isReversed","currentIndex","getFocusedElementIndex","elementsToPredict","i","elementIndex","element","elementData","error","signal","ForesightManager","_ForesightManager","CircularBuffer","mutationsList","mutation","element","entries","entry","elementData","isPointInRectangle","updatedProps","isNowIntersecting","getExpandedRect","props","eventType","listener","options","listeners","index","event","error","callback","hitSlop","name","shouldRegister","isTouchDevice","isLimitedConnection","evaluateRegistrationConditions","initialRect","normalizedHitSlop","normalizeHitSlop","initialViewportState","unregisterReason","wasLastElement","newValue","setting","min","max","shouldUpdateSetting","clampNumber","changedSettings","oldTrajectoryPredictionTime","oldPositionHistorySize","oldScrollMargin","oldTabOffset","oldEnableMousePrediction","oldEnableScrollPrediction","ScrollPredictor","oldEnableTabPrediction","TabPredictor","oldHitSlop","normalizedNewHitSlop","areRectsEqual","callbackHitType","asyncCallbackWrapper","start","errorMessage","settings","dependencies","MousePredictor","PositionObserver","newOriginalRect","expandedRect","updatedElementData"]}
|
|
1
|
+
{"version":3,"sources":["../src/foresight/ForesightManager.ts","../src/helpers/CircularBuffer.ts","../src/helpers/clampNumber.ts","../src/helpers/initialViewportState.ts","../src/helpers/rectAndHitSlop.ts","../src/helpers/shouldRegister.ts","../src/helpers/shouldUpdateSetting.ts","../src/helpers/lineSigmentIntersectsRect.ts","../src/helpers/predictNextMousePosition.ts","../src/foresight/BasePredictor.ts","../src/foresight/MousePredictor.ts","../src/helpers/getScrollDirection.ts","../src/helpers/predictNextScrollPosition.ts","../src/foresight/ScrollPredictor.ts","../src/foresight/TabPredictor.ts","../src/helpers/getFocusedElementIndex.ts"],"sourcesContent":["import { PositionObserver, PositionObserverEntry } from \"position-observer\"\r\nimport {\r\n DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n DEFAULT_ENABLE_TAB_PREDICTION,\r\n DEFAULT_HITSLOP,\r\n DEFAULT_POSITION_HISTORY_SIZE,\r\n DEFAULT_SCROLL_MARGIN,\r\n DEFAULT_STALE_TIME,\r\n DEFAULT_TAB_OFFSET,\r\n DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n MAX_POSITION_HISTORY_SIZE,\r\n MAX_SCROLL_MARGIN,\r\n MAX_TAB_OFFSET,\r\n MAX_TRAJECTORY_PREDICTION_TIME,\r\n MIN_POSITION_HISTORY_SIZE,\r\n MIN_SCROLL_MARGIN,\r\n MIN_TAB_OFFSET,\r\n MIN_TRAJECTORY_PREDICTION_TIME,\r\n} from \"../constants\"\r\nimport { CircularBuffer } from \"../helpers/CircularBuffer\"\r\nimport { clampNumber } from \"../helpers/clampNumber\"\r\nimport { initialViewportState } from \"../helpers/initialViewportState\"\r\nimport {\r\n areRectsEqual,\r\n getExpandedRect,\r\n isPointInRectangle,\r\n normalizeHitSlop,\r\n} from \"../helpers/rectAndHitSlop\"\r\nimport { evaluateRegistrationConditions } from \"../helpers/shouldRegister\"\r\nimport { shouldUpdateSetting } from \"../helpers/shouldUpdateSetting\"\r\nimport type {\r\n CallbackHits,\r\n CallbackHitType,\r\n ElementUnregisteredReason,\r\n ForesightElement,\r\n ForesightElementData,\r\n ForesightEvent,\r\n ForesightEventListener,\r\n ForesightEventMap,\r\n ForesightManagerData,\r\n ForesightManagerSettings,\r\n ForesightRegisterOptions,\r\n ForesightRegisterResult,\r\n ManagerBooleanSettingKeys,\r\n NumericSettingKeys,\r\n TrajectoryPositions,\r\n UpdatedDataPropertyNames,\r\n UpdatedManagerSetting,\r\n UpdateForsightManagerSettings,\r\n} from \"../types/types\"\r\nimport type { PredictorDependencies } from \"./BasePredictor\"\r\nimport { MousePredictor } from \"./MousePredictor\"\r\nimport { ScrollPredictor } from \"./ScrollPredictor\"\r\nimport { TabPredictor } from \"./TabPredictor\"\r\n\r\n/**\r\n * Manages the prediction of user intent based on mouse trajectory and element interactions.\r\n *\r\n * ForesightManager is a singleton class responsible for:\r\n * - Registering HTML elements to monitor.\r\n * - Tracking mouse movements and predicting future cursor positions.\r\n * - Detecting when a predicted trajectory intersects with a registered element's bounds.\r\n * - Invoking callbacks associated with elements upon predicted or actual interaction.\r\n * - Optionally unregistering elements after their callback is triggered.\r\n * - Handling global settings for prediction behavior (e.g., history size, prediction time).\r\n * - Automatically updating element bounds on resize using {@link ResizeObserver}.\r\n * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.\r\n * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.\r\n *\r\n * It should be initialized once using {@link ForesightManager.initialize} and then\r\n * accessed via the static getter {@link ForesightManager.instance}.\r\n */\r\n\r\nexport class ForesightManager {\r\n private static manager: ForesightManager\r\n private elements: Map<ForesightElement, ForesightElementData> = new Map()\r\n private trajectoryPositions: TrajectoryPositions = {\r\n positions: new CircularBuffer(DEFAULT_POSITION_HISTORY_SIZE),\r\n currentPoint: { x: 0, y: 0 },\r\n predictedPoint: { x: 0, y: 0 },\r\n }\r\n private isSetup: boolean = false\r\n private _globalCallbackHits: CallbackHits = {\r\n mouse: {\r\n hover: 0,\r\n trajectory: 0,\r\n },\r\n tab: {\r\n forwards: 0,\r\n reverse: 0,\r\n },\r\n scroll: {\r\n down: 0,\r\n left: 0,\r\n right: 0,\r\n up: 0,\r\n },\r\n total: 0,\r\n }\r\n private _globalSettings: ForesightManagerSettings = {\r\n debug: false,\r\n enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,\r\n trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n scrollMargin: DEFAULT_SCROLL_MARGIN,\r\n defaultHitSlop: {\r\n top: DEFAULT_HITSLOP,\r\n left: DEFAULT_HITSLOP,\r\n right: DEFAULT_HITSLOP,\r\n bottom: DEFAULT_HITSLOP,\r\n },\r\n enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,\r\n tabOffset: DEFAULT_TAB_OFFSET,\r\n }\r\n\r\n private domObserver: MutationObserver | null = null\r\n private positionObserver: PositionObserver | null = null\r\n private eventListeners: Map<ForesightEvent, ForesightEventListener[]> = new Map()\r\n private mousePredictor: MousePredictor | null = null\r\n private tabPredictor: TabPredictor | null = null\r\n private scrollPredictor: ScrollPredictor | null = null\r\n private constructor() {}\r\n\r\n public static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager {\r\n if (!this.isInitiated) {\r\n ForesightManager.manager = new ForesightManager()\r\n }\r\n if (props !== undefined) {\r\n ForesightManager.manager.alterGlobalSettings(props)\r\n }\r\n\r\n return ForesightManager.manager\r\n }\r\n\r\n public addEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>,\r\n options?: { signal?: AbortSignal }\r\n ) {\r\n if (options?.signal?.aborted) {\r\n return () => {}\r\n }\r\n const listeners = this.eventListeners.get(eventType) ?? []\r\n listeners.push(listener as ForesightEventListener)\r\n this.eventListeners.set(eventType, listeners)\r\n options?.signal?.addEventListener(\"abort\", () => this.removeEventListener(eventType, listener))\r\n }\r\n\r\n public removeEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>\r\n ): void {\r\n const listeners = this.eventListeners.get(eventType)\r\n if (!listeners) {\r\n return\r\n }\r\n const index = listeners.indexOf(listener as ForesightEventListener)\r\n if (index > -1) {\r\n listeners.splice(index, 1)\r\n }\r\n }\r\n\r\n private emit<K extends ForesightEvent>(event: ForesightEventMap[K]): void {\r\n const listeners = this.eventListeners.get(event.type)\r\n if (!listeners) {\r\n return\r\n }\r\n\r\n listeners.forEach((listener, index) => {\r\n try {\r\n listener(event)\r\n } catch (error) {\r\n console.error(`Error in ForesightManager event listener ${index} for ${event.type}:`, error)\r\n }\r\n })\r\n }\r\n\r\n public get getManagerData(): Readonly<ForesightManagerData> {\r\n return {\r\n registeredElements: this.elements,\r\n globalSettings: this._globalSettings,\r\n globalCallbackHits: this._globalCallbackHits,\r\n eventListeners: this.eventListeners,\r\n }\r\n }\r\n\r\n public static get isInitiated(): Readonly<boolean> {\r\n return !!ForesightManager.manager\r\n }\r\n\r\n public static get instance(): ForesightManager {\r\n return this.initialize()\r\n }\r\n\r\n public get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData> {\r\n return this.elements\r\n }\r\n\r\n public register({\r\n element,\r\n callback,\r\n hitSlop,\r\n name,\r\n meta,\r\n reactivateAfter,\r\n }: ForesightRegisterOptions): ForesightRegisterResult {\r\n const { shouldRegister, isTouchDevice, isLimitedConnection } = evaluateRegistrationConditions()\r\n if (!shouldRegister) {\r\n return {\r\n isLimitedConnection,\r\n isTouchDevice,\r\n isRegistered: false,\r\n unregister: () => {},\r\n }\r\n }\r\n if (this.elements.has(element)) {\r\n return {\r\n isLimitedConnection,\r\n isTouchDevice,\r\n isRegistered: false,\r\n unregister: () => {},\r\n }\r\n }\r\n\r\n // Setup global listeners on every first element added to the manager. It gets removed again when the map is emptied\r\n if (!this.isSetup) {\r\n this.initializeGlobalListeners()\r\n }\r\n\r\n const initialRect = element.getBoundingClientRect()\r\n\r\n const normalizedHitSlop = hitSlop\r\n ? normalizeHitSlop(hitSlop)\r\n : this._globalSettings.defaultHitSlop\r\n\r\n const elementData: ForesightElementData = {\r\n element: element,\r\n callback,\r\n elementBounds: {\r\n originalRect: initialRect,\r\n expandedRect: getExpandedRect(initialRect, normalizedHitSlop),\r\n hitSlop: normalizedHitSlop,\r\n },\r\n isHovering: false,\r\n trajectoryHitData: {\r\n isTrajectoryHit: false,\r\n trajectoryHitTime: 0,\r\n trajectoryHitExpirationTimeoutId: undefined,\r\n },\r\n name: name || element.id || \"unnamed\",\r\n isIntersectingWithViewport: initialViewportState(initialRect),\r\n\r\n registerCount: (this.registeredElements.get(element)?.registerCount ?? 0) + 1,\r\n meta: meta ?? {},\r\n callbackInfo: {\r\n callbackFiredCount: 0,\r\n lastCallbackFiredAt: undefined,\r\n lastCallbackRuntime: undefined,\r\n reactivateAfter: reactivateAfter ?? DEFAULT_STALE_TIME,\r\n isCallbackActive: true,\r\n isRunningCallback: false,\r\n lastCallbackStatus: undefined,\r\n },\r\n }\r\n\r\n this.elements.set(element, elementData)\r\n\r\n this.positionObserver?.observe(element)\r\n\r\n this.emit({\r\n type: \"elementRegistered\",\r\n timestamp: Date.now(),\r\n elementData,\r\n })\r\n\r\n return {\r\n isTouchDevice,\r\n isLimitedConnection,\r\n isRegistered: true,\r\n unregister: () => {},\r\n }\r\n }\r\n\r\n public unregister(element: ForesightElement, unregisterReason: ElementUnregisteredReason) {\r\n if (!this.elements.has(element)) {\r\n return\r\n }\r\n\r\n const elementData = this.elements.get(element)\r\n\r\n // Clear any pending trajectory expiration timeout\r\n if (elementData?.trajectoryHitData.trajectoryHitExpirationTimeoutId) {\r\n clearTimeout(elementData.trajectoryHitData.trajectoryHitExpirationTimeoutId)\r\n }\r\n\r\n this.positionObserver?.unobserve(element)\r\n this.elements.delete(element)\r\n\r\n const wasLastElement = this.elements.size === 0 && this.isSetup\r\n\r\n if (wasLastElement) {\r\n this.removeGlobalListeners()\r\n }\r\n\r\n if (elementData) {\r\n this.emit({\r\n type: \"elementUnregistered\",\r\n elementData: elementData,\r\n timestamp: Date.now(),\r\n unregisterReason: unregisterReason,\r\n wasLastElement: wasLastElement,\r\n })\r\n }\r\n }\r\n\r\n private updateNumericSettings(\r\n newValue: number | undefined,\r\n setting: NumericSettingKeys,\r\n min: number,\r\n max: number\r\n ) {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n\r\n this._globalSettings[setting] = clampNumber(newValue, min, max, setting)\r\n\r\n return true\r\n }\r\n\r\n private updateBooleanSetting(\r\n newValue: boolean | undefined,\r\n setting: ManagerBooleanSettingKeys\r\n ): boolean {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n this._globalSettings[setting] = newValue\r\n return true\r\n }\r\n\r\n public alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void {\r\n const changedSettings: UpdatedManagerSetting[] = []\r\n\r\n const oldTrajectoryPredictionTime = this._globalSettings.trajectoryPredictionTime\r\n const trajectoryPredictionTimeChanged = this.updateNumericSettings(\r\n props?.trajectoryPredictionTime,\r\n \"trajectoryPredictionTime\",\r\n MIN_TRAJECTORY_PREDICTION_TIME,\r\n MAX_TRAJECTORY_PREDICTION_TIME\r\n )\r\n if (trajectoryPredictionTimeChanged) {\r\n changedSettings.push({\r\n setting: \"trajectoryPredictionTime\",\r\n oldValue: oldTrajectoryPredictionTime,\r\n newValue: this._globalSettings.trajectoryPredictionTime,\r\n })\r\n }\r\n\r\n const oldPositionHistorySize = this._globalSettings.positionHistorySize\r\n const positionHistorySizeChanged = this.updateNumericSettings(\r\n props?.positionHistorySize,\r\n \"positionHistorySize\",\r\n MIN_POSITION_HISTORY_SIZE,\r\n MAX_POSITION_HISTORY_SIZE\r\n )\r\n if (positionHistorySizeChanged) {\r\n changedSettings.push({\r\n setting: \"positionHistorySize\",\r\n oldValue: oldPositionHistorySize,\r\n newValue: this._globalSettings.positionHistorySize,\r\n })\r\n this.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize)\r\n }\r\n\r\n const oldScrollMargin = this._globalSettings.scrollMargin\r\n const scrollMarginChanged = this.updateNumericSettings(\r\n props?.scrollMargin,\r\n \"scrollMargin\",\r\n MIN_SCROLL_MARGIN,\r\n MAX_SCROLL_MARGIN\r\n )\r\n if (scrollMarginChanged) {\r\n const newValue = this._globalSettings.scrollMargin\r\n if (this.scrollPredictor) {\r\n this.scrollPredictor.scrollMargin = newValue\r\n }\r\n changedSettings.push({\r\n setting: \"scrollMargin\",\r\n oldValue: oldScrollMargin,\r\n newValue: newValue,\r\n })\r\n }\r\n\r\n const oldTabOffset = this._globalSettings.tabOffset\r\n const tabOffsetChanged = this.updateNumericSettings(\r\n props?.tabOffset,\r\n \"tabOffset\",\r\n MIN_TAB_OFFSET,\r\n MAX_TAB_OFFSET\r\n )\r\n if (tabOffsetChanged) {\r\n if (this.tabPredictor) {\r\n this.tabPredictor.tabOffset = this._globalSettings.tabOffset\r\n }\r\n changedSettings.push({\r\n setting: \"tabOffset\",\r\n oldValue: oldTabOffset,\r\n newValue: this._globalSettings.tabOffset,\r\n })\r\n }\r\n\r\n const oldEnableMousePrediction = this._globalSettings.enableMousePrediction\r\n const enableMousePredictionChanged = this.updateBooleanSetting(\r\n props?.enableMousePrediction,\r\n \"enableMousePrediction\"\r\n )\r\n if (enableMousePredictionChanged) {\r\n changedSettings.push({\r\n setting: \"enableMousePrediction\",\r\n oldValue: oldEnableMousePrediction,\r\n newValue: this._globalSettings.enableMousePrediction,\r\n })\r\n }\r\n\r\n const oldEnableScrollPrediction = this._globalSettings.enableScrollPrediction\r\n const enableScrollPredictionChanged = this.updateBooleanSetting(\r\n props?.enableScrollPrediction,\r\n \"enableScrollPrediction\"\r\n )\r\n if (enableScrollPredictionChanged) {\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor = new ScrollPredictor({\r\n dependencies: {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n },\r\n settings: {\r\n scrollMargin: this._globalSettings.scrollMargin,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n } else {\r\n this.scrollPredictor?.cleanup()\r\n this.scrollPredictor = null\r\n }\r\n changedSettings.push({\r\n setting: \"enableScrollPrediction\",\r\n oldValue: oldEnableScrollPrediction,\r\n newValue: this._globalSettings.enableScrollPrediction,\r\n })\r\n }\r\n\r\n const oldEnableTabPrediction = this._globalSettings.enableTabPrediction\r\n const enableTabPredictionChanged = this.updateBooleanSetting(\r\n props?.enableTabPrediction,\r\n \"enableTabPrediction\"\r\n )\r\n if (enableTabPredictionChanged) {\r\n changedSettings.push({\r\n setting: \"enableTabPrediction\",\r\n oldValue: oldEnableTabPrediction,\r\n newValue: this._globalSettings.enableTabPrediction,\r\n })\r\n if (this._globalSettings.enableTabPrediction) {\r\n this.tabPredictor = new TabPredictor({\r\n dependencies: {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n },\r\n settings: {\r\n tabOffset: this._globalSettings.tabOffset,\r\n },\r\n })\r\n } else {\r\n this.tabPredictor?.cleanup()\r\n this.tabPredictor = null\r\n }\r\n }\r\n\r\n if (props?.defaultHitSlop !== undefined) {\r\n const oldHitSlop = this._globalSettings.defaultHitSlop\r\n const normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop)\r\n\r\n if (!areRectsEqual(oldHitSlop, normalizedNewHitSlop)) {\r\n this._globalSettings.defaultHitSlop = normalizedNewHitSlop\r\n changedSettings.push({\r\n setting: \"defaultHitSlop\",\r\n oldValue: oldHitSlop,\r\n newValue: normalizedNewHitSlop,\r\n })\r\n this.forceUpdateAllElementBounds()\r\n }\r\n }\r\n\r\n if (changedSettings.length > 0) {\r\n this.emit({\r\n type: \"managerSettingsChanged\",\r\n timestamp: Date.now(),\r\n managerData: this.getManagerData,\r\n updatedSettings: changedSettings,\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.\r\n *\r\n * @param mutationsList - Array of MutationRecord objects describing the DOM changes\r\n *\r\n */\r\n private handleDomMutations = (mutationsList: MutationRecord[]) => {\r\n // Invalidate tabbale elements cache\r\n if (mutationsList.length) {\r\n this.tabPredictor?.invalidateCache()\r\n }\r\n for (const mutation of mutationsList) {\r\n if (mutation.type === \"childList\" && mutation.removedNodes.length > 0) {\r\n for (const element of this.elements.keys()) {\r\n if (!element.isConnected) {\r\n this.unregister(element, \"disconnected\")\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n private updateHitCounters(callbackHitType: CallbackHitType) {\r\n switch (callbackHitType.kind) {\r\n case \"mouse\":\r\n this._globalCallbackHits.mouse[callbackHitType.subType]++\r\n break\r\n case \"tab\":\r\n this._globalCallbackHits.tab[callbackHitType.subType]++\r\n break\r\n case \"scroll\":\r\n this._globalCallbackHits.scroll[callbackHitType.subType]++\r\n break\r\n default:\r\n callbackHitType satisfies never\r\n }\r\n this._globalCallbackHits.total++\r\n }\r\n\r\n private makeElementActive(elementData: ForesightElementData) {\r\n // Only reactivate if callback is not currently running\r\n if (!elementData.callbackInfo.isRunningCallback) {\r\n elementData.callbackInfo.isCallbackActive = true\r\n this.positionObserver?.observe(elementData.element)\r\n this.emit({ type: \"elementReactivated\", elementData: elementData, timestamp: Date.now() })\r\n }\r\n }\r\n\r\n private makeElementUnactive(elementData: ForesightElementData) {\r\n elementData.callbackInfo.callbackFiredCount++\r\n elementData.callbackInfo.lastCallbackFiredAt = Date.now()\r\n elementData.callbackInfo.isRunningCallback = true\r\n elementData.callbackInfo.isCallbackActive = false\r\n if (elementData?.trajectoryHitData.trajectoryHitExpirationTimeoutId) {\r\n clearTimeout(elementData.trajectoryHitData.trajectoryHitExpirationTimeoutId)\r\n }\r\n this.positionObserver?.unobserve(elementData.element)\r\n\r\n // TODO Was last element check\r\n // TODO emit element unactive\r\n }\r\n\r\n private callCallback(elementData: ForesightElementData, callbackHitType: CallbackHitType) {\r\n if (elementData.callbackInfo.isRunningCallback || !elementData.callbackInfo.isCallbackActive) {\r\n return\r\n }\r\n\r\n this.makeElementUnactive(elementData)\r\n // We have this async wrapper so we can time exactly how long the callback takes\r\n const asyncCallbackWrapper = async () => {\r\n this.updateHitCounters(callbackHitType)\r\n this.emit({\r\n type: \"callbackInvoked\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n })\r\n const start = performance.now()\r\n try {\r\n await elementData.callback()\r\n elementData.callbackInfo.lastCallbackRuntime = performance.now() - start\r\n elementData.callbackInfo.lastCallbackStatus = \"success\"\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: performance.now() - start,\r\n status: \"success\",\r\n })\r\n } catch (error) {\r\n const errorMessage = error instanceof Error ? error.message : String(error)\r\n console.error(\r\n `Error in callback for element ${elementData.name} (${elementData.element.tagName}):`,\r\n error\r\n )\r\n elementData.callbackInfo.lastCallbackRuntime = performance.now() - start\r\n elementData.callbackInfo.lastCallbackStatus = \"error\"\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: elementData.callbackInfo.lastCallbackRuntime,\r\n status: \"error\",\r\n errorMessage,\r\n })\r\n }\r\n\r\n // Reset running state\r\n elementData.callbackInfo.isRunningCallback = false\r\n\r\n // Schedule element to become active again after reactivateAfter\r\n if (\r\n elementData.callbackInfo.reactivateAfter !== Infinity &&\r\n elementData.callbackInfo.reactivateAfter > 0\r\n ) {\r\n setTimeout(() => {\r\n this.makeElementActive(elementData)\r\n }, elementData.callbackInfo.reactivateAfter)\r\n }\r\n }\r\n asyncCallbackWrapper()\r\n }\r\n\r\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\r\n for (const entry of entries) {\r\n const elementData = this.elements.get(entry.target)\r\n if (!elementData) {\r\n continue\r\n }\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor?.handleScrollPrefetch(elementData, entry.boundingClientRect)\r\n } else {\r\n // If we dont check for scroll prediction, check if the user is hovering over the element during a scroll instead\r\n if (\r\n isPointInRectangle(\r\n this.trajectoryPositions.currentPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"mouse\",\r\n subType: \"hover\",\r\n })\r\n }\r\n }\r\n // Always call handlePositionChangeDataUpdates AFTER handleScrollPrefetch since handlePositionChangeDataUpdates alters the elementData\r\n this.handlePositionChangeDataUpdates(elementData, entry)\r\n }\r\n\r\n // End batch processing for scroll prediction\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor?.resetScrollProps()\r\n }\r\n }\r\n\r\n private handlePositionChangeDataUpdates = (\r\n elementData: ForesightElementData,\r\n entry: PositionObserverEntry\r\n ) => {\r\n const updatedProps: UpdatedDataPropertyNames[] = []\r\n const isNowIntersecting = entry.isIntersecting\r\n\r\n // Track visibility changes\r\n if (elementData.isIntersectingWithViewport !== isNowIntersecting) {\r\n updatedProps.push(\"visibility\")\r\n elementData.isIntersectingWithViewport = isNowIntersecting\r\n }\r\n\r\n // Handle bounds updates for intersecting elements\r\n if (isNowIntersecting) {\r\n updatedProps.push(\"bounds\")\r\n elementData.elementBounds = {\r\n hitSlop: elementData.elementBounds.hitSlop,\r\n originalRect: entry.boundingClientRect,\r\n expandedRect: getExpandedRect(entry.boundingClientRect, elementData.elementBounds.hitSlop),\r\n }\r\n }\r\n if (updatedProps.length) {\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: elementData,\r\n updatedProps,\r\n })\r\n }\r\n }\r\n\r\n private initializeGlobalListeners() {\r\n if (this.isSetup) {\r\n return\r\n }\r\n // To avoid setting up listeners while ssr\r\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\r\n return\r\n }\r\n\r\n const settings = this._globalSettings\r\n const dependencies: PredictorDependencies = {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n }\r\n\r\n if (settings.enableTabPrediction) {\r\n this.tabPredictor = new TabPredictor({\r\n dependencies,\r\n settings: {\r\n tabOffset: settings.tabOffset,\r\n },\r\n })\r\n }\r\n\r\n if (settings.enableScrollPrediction) {\r\n this.scrollPredictor = new ScrollPredictor({\r\n dependencies,\r\n settings: {\r\n scrollMargin: settings.scrollMargin,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n }\r\n\r\n // Always initialize mouse since if its not enabled we still check for hover and need it for scroll\r\n this.mousePredictor = new MousePredictor({\r\n dependencies,\r\n settings: {\r\n enableMousePrediction: settings.enableMousePrediction,\r\n trajectoryPredictionTime: settings.trajectoryPredictionTime,\r\n positionHistorySize: settings.positionHistorySize,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n\r\n //Mutation observer is to automatically unregister elements when they leave the DOM. Its a fail-safe for if the user forgets to do it.\r\n this.domObserver = new MutationObserver(this.handleDomMutations)\r\n this.domObserver.observe(document.documentElement, {\r\n childList: true,\r\n subtree: true,\r\n attributes: false,\r\n })\r\n\r\n // Handles all position based changes and update the rects of the elements. completely async to avoid dirtying the main thread.\r\n // Handles resize of elements\r\n // Handles resize of viewport\r\n // Handles scrolling\r\n this.positionObserver = new PositionObserver(this.handlePositionChange)\r\n\r\n this.isSetup = true\r\n }\r\n\r\n private removeGlobalListeners() {\r\n this.isSetup = false\r\n this.domObserver?.disconnect()\r\n this.domObserver = null\r\n this.positionObserver?.disconnect()\r\n this.positionObserver = null\r\n this.mousePredictor?.cleanup()\r\n this.tabPredictor?.cleanup()\r\n this.scrollPredictor?.cleanup()\r\n }\r\n\r\n private forceUpdateAllElementBounds() {\r\n for (const [, elementData] of this.elements) {\r\n if (elementData.isIntersectingWithViewport) {\r\n this.forceUpdateElementBounds(elementData)\r\n }\r\n }\r\n }\r\n /**\r\n * 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.\r\n * We need an observer for that\r\n */\r\n private forceUpdateElementBounds(elementData: ForesightElementData) {\r\n const newOriginalRect = elementData.element.getBoundingClientRect()\r\n const expandedRect = getExpandedRect(newOriginalRect, elementData.elementBounds.hitSlop)\r\n\r\n if (!areRectsEqual(expandedRect, elementData.elementBounds.expandedRect)) {\r\n const updatedElementData = {\r\n ...elementData,\r\n elementBounds: {\r\n ...elementData.elementBounds,\r\n originalRect: newOriginalRect,\r\n expandedRect,\r\n },\r\n }\r\n\r\n this.elements.set(elementData.element, updatedElementData)\r\n\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: updatedElementData,\r\n updatedProps: [\"bounds\"],\r\n })\r\n }\r\n }\r\n}\r\n","export class CircularBuffer<T> {\r\n private buffer: T[]\r\n private head: number = 0\r\n private count: number = 0\r\n private capacity: number\r\n\r\n constructor(capacity: number) {\r\n if (capacity <= 0) {\r\n throw new Error('CircularBuffer capacity must be greater than 0')\r\n }\r\n this.capacity = capacity\r\n this.buffer = new Array(capacity)\r\n }\r\n\r\n add(item: T): void {\r\n this.buffer[this.head] = item\r\n this.head = (this.head + 1) % this.capacity\r\n \r\n if (this.count < this.capacity) {\r\n this.count++\r\n }\r\n }\r\n\r\n getFirst(): T | undefined {\r\n if (this.count === 0) {\r\n return undefined\r\n }\r\n\r\n if (this.count < this.capacity) {\r\n return this.buffer[0]\r\n } else {\r\n return this.buffer[this.head]\r\n }\r\n }\r\n\r\n getLast(): T | undefined {\r\n if (this.count === 0) {\r\n return undefined\r\n }\r\n\r\n if (this.count < this.capacity) {\r\n return this.buffer[this.count - 1]\r\n } else {\r\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity\r\n return this.buffer[lastIndex]\r\n }\r\n }\r\n\r\n getFirstLast(): [T | undefined, T | undefined] {\r\n if (this.count === 0) {\r\n return [undefined, undefined]\r\n }\r\n\r\n if (this.count === 1) {\r\n const item = this.count < this.capacity ? this.buffer[0] : this.buffer[this.head]\r\n return [item, item]\r\n }\r\n\r\n const first = this.getFirst()\r\n const last = this.getLast()\r\n return [first, last]\r\n }\r\n\r\n resize(newCapacity: number): void {\r\n if (newCapacity <= 0) {\r\n throw new Error('CircularBuffer capacity must be greater than 0')\r\n }\r\n\r\n if (newCapacity === this.capacity) {\r\n return\r\n }\r\n\r\n const currentItems = this.getAllItems()\r\n this.capacity = newCapacity\r\n this.buffer = new Array(newCapacity)\r\n this.head = 0\r\n this.count = 0\r\n\r\n if (currentItems.length > newCapacity) {\r\n const itemsToKeep = currentItems.slice(-newCapacity)\r\n for (const item of itemsToKeep) {\r\n this.add(item)\r\n }\r\n } else {\r\n for (const item of currentItems) {\r\n this.add(item)\r\n }\r\n }\r\n }\r\n\r\n private getAllItems(): T[] {\r\n if (this.count === 0) {\r\n return []\r\n }\r\n\r\n const result: T[] = new Array(this.count)\r\n \r\n if (this.count < this.capacity) {\r\n for (let i = 0; i < this.count; i++) {\r\n result[i] = this.buffer[i]\r\n }\r\n } else {\r\n const startIndex = this.head\r\n for (let i = 0; i < this.capacity; i++) {\r\n const bufferIndex = (startIndex + i) % this.capacity\r\n result[i] = this.buffer[bufferIndex]\r\n }\r\n }\r\n \r\n return result\r\n }\r\n\r\n clear(): void {\r\n this.head = 0\r\n this.count = 0\r\n }\r\n\r\n get length(): number {\r\n return this.count\r\n }\r\n\r\n get size(): number {\r\n return this.capacity\r\n }\r\n\r\n get isFull(): boolean {\r\n return this.count === this.capacity\r\n }\r\n\r\n get isEmpty(): boolean {\r\n return this.count === 0\r\n }\r\n}","export function clampNumber(\n number: number,\n lowerBound: number,\n upperBound: number,\n settingName: string\n) {\n if (number < lowerBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is below minimum bound ${lowerBound}, clamping to ${lowerBound}`\n )\n } else if (number > upperBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is above maximum bound ${upperBound}, clamping to ${upperBound}`\n )\n }\n\n return Math.min(Math.max(number, lowerBound), upperBound)\n}\n","export function initialViewportState(rect: DOMRect) {\n const viewportWidth = window.innerWidth || document.documentElement.clientWidth\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight\n\n return rect.top < viewportHeight && rect.bottom > 0 && rect.left < viewportWidth && rect.right > 0\n}\n","import type { HitSlop, Point, Rect } from \"../types/types\"\nimport { MAX_HITSLOP, MIN_HITSLOP } from \"../constants\"\nimport { clampNumber } from \"./clampNumber\"\n\n/**\n * Normalizes a `hitSlop` value into a {@link Rect} object.\n * If `hitSlop` is a number, it's applied uniformly to all sides (top, left, right, bottom).\n * If `hitSlop` is already a `Rect` object, it's returned as is.\n *\n * @param hitSlop - A number for uniform slop, or a {@link Rect} object for specific slop per side.\n * @returns A {@link Rect} object with `top`, `left`, `right`, and `bottom` properties.\n */\nexport function normalizeHitSlop(hitSlop: HitSlop): Rect {\n if (typeof hitSlop === \"number\") {\n const clampedValue = clampNumber(hitSlop, MIN_HITSLOP, MAX_HITSLOP, \"hitslop\")\n return {\n top: clampedValue,\n left: clampedValue,\n right: clampedValue,\n bottom: clampedValue,\n }\n }\n\n return {\n top: clampNumber(hitSlop.top, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - top\"),\n left: clampNumber(hitSlop.left, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - left\"),\n right: clampNumber(hitSlop.right, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - right\"),\n bottom: clampNumber(hitSlop.bottom, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - bottom\"),\n }\n}\n\n/**\n * Calculates an expanded rectangle by applying a `hitSlop` to a base rectangle.\n * The `hitSlop` values define how much to extend each side of the `baseRect` outwards.\n *\n * @param baseRect - The original {@link Rect} or `DOMRect` to expand.\n * @param hitSlop - A {@link Rect} object defining how much to expand each side\n * (e.g., `hitSlop.left` expands the left boundary further to the left).\n * @returns A new {@link Rect} object representing the expanded area.\n */\nexport function getExpandedRect(baseRect: Rect | DOMRect, hitSlop: Rect): Rect {\n return {\n left: baseRect.left - hitSlop.left,\n right: baseRect.right + hitSlop.right,\n top: baseRect.top - hitSlop.top,\n bottom: baseRect.bottom + hitSlop.bottom,\n }\n}\n\n/**\n * Checks if two rectangle objects are equal by comparing their respective\n * `top`, `left`, `right`, and `bottom` properties.\n * Handles cases where one or both rects might be null or undefined.\n *\n * @param rect1 - The first {@link Rect} object to compare.\n * @param rect2 - The second {@link Rect} object to compare.\n * @returns `true` if the rectangles have identical dimensions or if both are null/undefined,\n * `false` otherwise.\n */\nexport function areRectsEqual(rect1: Rect, rect2: Rect): boolean {\n if (!rect1 || !rect2) return rect1 === rect2\n return (\n rect1.left === rect2.left &&\n rect1.right === rect2.right &&\n rect1.top === rect2.top &&\n rect1.bottom === rect2.bottom\n )\n}\n\nexport function isPointInRectangle(point: Point, rect: Rect): boolean {\n return (\n point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom\n )\n}\n","type ShouldRegister = {\n shouldRegister: boolean\n isTouchDevice: boolean\n isLimitedConnection: boolean\n}\n\nexport function evaluateRegistrationConditions(): ShouldRegister {\n const isTouchDevice = userUsesTouchDevice()\n const isLimitedConnection = hasConnectionLimitations()\n const shouldRegister = !isTouchDevice && !isLimitedConnection\n return { isTouchDevice, isLimitedConnection, shouldRegister }\n}\n\n/**\n * Detects if the current device is likely a touch-enabled device.\n * It checks for coarse pointer media query and the presence of touch points.\n *\n * @returns `true` if the device is likely touch-enabled, `false` otherwise.\n */\nfunction userUsesTouchDevice(): boolean {\n return window.matchMedia(\"(pointer: coarse)\").matches && navigator.maxTouchPoints > 0\n}\n\n/**\n * Checks if the user has connection limitations (slow network or data saver enabled).\n *\n * @returns {boolean} True if connection is limited, false if safe to prefetch\n * @example\n * if (!hasConnectionLimitations()) {\n * prefetchResource('/api/data');\n * }\n */\nfunction hasConnectionLimitations(): boolean {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const connection = (navigator as any).connection\n if (!connection) return false\n\n return /2g/.test(connection.effectiveType) || connection.saveData\n}\n","/**\n * Checks if a setting should be updated.\n * Returns true if the newValue is defined and different from the currentValue.\n * Uses a type predicate to narrow the type of newValue in the calling scope.\n *\n * @param newValue The potentially new value for the setting (can be undefined).\n * @param currentValue The current value of the setting.\n * @returns True if the setting should be updated, false otherwise.\n */\nexport function shouldUpdateSetting<T>(\n newValue: T | undefined,\n currentValue: T\n): newValue is NonNullable<T> {\n // NonNullable<T> ensures that if T itself could be undefined (e.g. T = number | undefined),\n // the predicate narrows to the non-undefined part (e.g. number).\n // If T is already non-nullable (e.g. T = number), it remains T (e.g. number).\n return newValue !== undefined && currentValue !== newValue\n}\n","import type { Point, Rect } from \"../types/types\"\n\n/**\n * Determines if a line segment intersects with a given rectangle.\n * This function implements the Liang-Barsky line clipping algorithm.\n *\n * @param p1 - The starting {@link Point} of the line segment.\n * @param p2 - The ending {@link Point} of the line segment.\n * @param rect - The {@link Rect} to check for intersection.\n * @returns `true` if the line segment intersects the rectangle, `false` otherwise.\n */\nexport function lineSegmentIntersectsRect(p1: Point, p2: Point, rect: Rect): boolean {\n let t0 = 0.0\n let t1 = 1.0\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n\n const clipTest = (p: number, q: number): boolean => {\n if (p === 0) {\n // Line is parallel to this clipping edge\n if (q < 0) return false // Line is outside the clipping edge\n } else {\n const r = q / p\n if (p < 0) {\n // Line proceeds from outside to inside\n if (r > t1) return false // Line segment ends before crossing edge\n if (r > t0) t0 = r // Update entry point\n } else {\n // Line proceeds from inside to outside\n if (r < t0) return false // Line segment starts after crossing edge\n if (r < t1) t1 = r // Update exit point\n }\n }\n return true\n }\n\n // Clip against all four edges of the rectangle\n if (!clipTest(-dx, p1.x - rect.left)) return false // Left edge\n if (!clipTest(dx, rect.right - p1.x)) return false // Right edge\n if (!clipTest(-dy, p1.y - rect.top)) return false // Top edge\n if (!clipTest(dy, rect.bottom - p1.y)) return false // Bottom edge\n\n // If t0 <= t1, the line segment intersects the rectangle (or lies within it)\n return t0 <= t1\n}\n","import type { MousePosition, Point } from \"../types/types\"\nimport type { CircularBuffer } from \"./CircularBuffer\"\n\n/**\n * Predicts the next mouse position based on a history of recent movements.\n * It calculates velocity from the historical data and extrapolates a future point.\n * The `buffer` is mutated by this function: the new `currentPoint` is added,\n * automatically overwriting the oldest entry when the buffer is full.\n *\n * @param currentPoint - The current actual mouse coordinates.\n * @param buffer - A circular buffer of previous mouse positions with timestamps.\n * This buffer will be modified by this function.\n * @param trajectoryPredictionTimeInMs - How far into the future (in milliseconds)\n * to predict the mouse position.\n * @returns The predicted {@link Point} (x, y coordinates). If history is insufficient\n * (less than 2 points) or time delta is zero, returns the `currentPoint`.\n */\nexport function predictNextMousePosition(\n currentPoint: Point,\n buffer: CircularBuffer<MousePosition>,\n trajectoryPredictionTimeInMs: number\n): Point {\n const now = performance.now()\n const currentPosition: MousePosition = { point: currentPoint, time: now }\n const { x, y } = currentPoint\n\n buffer.add(currentPosition)\n\n if (buffer.length < 2) {\n return { x, y }\n }\n\n const [first, last] = buffer.getFirstLast()\n if (!first || !last) {\n return { x, y }\n }\n const dt = (last.time - first.time) * 0.001\n\n if (dt === 0) {\n return { x, y }\n }\n\n const dx = last.point.x - first.point.x\n const dy = last.point.y - first.point.y\n const vx = dx / dt\n const vy = dy / dt\n\n const trajectoryPredictionTimeInSeconds = trajectoryPredictionTimeInMs * 0.001\n const predictedX = x + vx * trajectoryPredictionTimeInSeconds\n const predictedY = y + vy * trajectoryPredictionTimeInSeconds\n\n return { x: predictedX, y: predictedY }\n}\n","import type {\r\n CallbackHitType,\r\n ForesightElement,\r\n ForesightElementData,\r\n ForesightEvent,\r\n ForesightEventMap,\r\n} from \"../types/types\"\r\n\r\nexport type CallCallbackFunction = (\r\n elementData: ForesightElementData,\r\n callbackHitType: CallbackHitType\r\n) => void\r\n\r\nexport type EmitFunction = <K extends ForesightEvent>(event: ForesightEventMap[K]) => void\r\n\r\nexport type PredictorDependencies = {\r\n elements: ReadonlyMap<ForesightElement, ForesightElementData>\r\n callCallback: CallCallbackFunction\r\n emit: EmitFunction\r\n}\r\n\r\nexport interface BasePredictorConfig {\r\n dependencies: PredictorDependencies\r\n}\r\n\r\nexport type PredictorProps = PredictorDependencies\r\n\r\nexport abstract class BasePredictor {\r\n protected abortController: AbortController\r\n protected elements: ReadonlyMap<ForesightElement, ForesightElementData>\r\n protected callCallback: CallCallbackFunction\r\n protected emit: EmitFunction\r\n \r\n constructor(config: BasePredictorConfig) {\r\n this.elements = config.dependencies.elements\r\n this.callCallback = config.dependencies.callCallback\r\n this.emit = config.dependencies.emit\r\n this.abortController = new AbortController()\r\n }\r\n \r\n protected abort(): void {\r\n this.abortController.abort()\r\n }\r\n\r\n public abstract cleanup(): void\r\n protected abstract initializeListeners(): void\r\n \r\n protected handleError(error: unknown, context: string): void {\r\n console.error(`${this.constructor.name} error in ${context}:`, error)\r\n }\r\n}\r\n","import { lineSegmentIntersectsRect } from \"../helpers/lineSigmentIntersectsRect\"\r\nimport { predictNextMousePosition } from \"../helpers/predictNextMousePosition\"\r\nimport { isPointInRectangle } from \"../helpers/rectAndHitSlop\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\nimport type { TrajectoryPositions } from \"../types/types\"\r\n\r\nexport interface MousePredictorSettings {\r\n enableMousePrediction: boolean\r\n trajectoryPredictionTime: number\r\n positionHistorySize: number\r\n}\r\n\r\nexport interface MousePredictorConfig extends BasePredictorConfig {\r\n settings: MousePredictorSettings\r\n trajectoryPositions: TrajectoryPositions\r\n}\r\n\r\nexport class MousePredictor extends BasePredictor {\r\n private pendingMouseEvent: MouseEvent | null = null\r\n private rafId: number | null = null\r\n private enableMousePrediction: boolean\r\n public trajectoryPredictionTime: number\r\n public positionHistorySize: number\r\n private trajectoryPositions: TrajectoryPositions\r\n\r\n constructor(config: MousePredictorConfig) {\r\n super(config)\r\n this.enableMousePrediction = config.settings.enableMousePrediction\r\n this.trajectoryPredictionTime = config.settings.trajectoryPredictionTime\r\n this.positionHistorySize = config.settings.positionHistorySize\r\n this.trajectoryPositions = config.trajectoryPositions\r\n this.initializeListeners()\r\n }\r\n protected initializeListeners() {\r\n const { signal } = this.abortController\r\n document.addEventListener(\"mousemove\", this.handleMouseMove, { signal })\r\n }\r\n\r\n private handleMouseMove = (e: MouseEvent) => {\r\n this.pendingMouseEvent = e\r\n if (this.rafId) return\r\n\r\n this.rafId = requestAnimationFrame(() => {\r\n if (this.pendingMouseEvent) {\r\n this.processMouseMovement(this.pendingMouseEvent)\r\n }\r\n this.rafId = null\r\n })\r\n }\r\n private updatePointerState(e: MouseEvent): void {\r\n const currentPoint = { x: e.clientX, y: e.clientY }\r\n this.trajectoryPositions.currentPoint = currentPoint\r\n if (this.enableMousePrediction) {\r\n this.trajectoryPositions.predictedPoint = predictNextMousePosition(\r\n currentPoint,\r\n this.trajectoryPositions.positions,\r\n this.trajectoryPredictionTime\r\n )\r\n } else {\r\n this.trajectoryPositions.predictedPoint = currentPoint\r\n }\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n if (this.rafId) {\r\n cancelAnimationFrame(this.rafId)\r\n this.rafId = null\r\n }\r\n this.pendingMouseEvent = null\r\n }\r\n\r\n private processMouseMovement(e: MouseEvent): void {\r\n try {\r\n this.updatePointerState(e)\r\n\r\n // Use for...of instead of forEach for better performance in hot code path\r\n // Avoids function call overhead and iterator creation on every mouse move\r\n for (const currentData of this.elements.values()) {\r\n if (!currentData.isIntersectingWithViewport) {\r\n continue\r\n }\r\n const expandedRect = currentData.elementBounds.expandedRect\r\n\r\n if (!this.enableMousePrediction) {\r\n if (isPointInRectangle(this.trajectoryPositions.currentPoint, expandedRect)) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"hover\" })\r\n return\r\n }\r\n // when enable mouse prediction is off, we only check if the mouse is physically hovering over the element\r\n } else if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.trajectoryPositions.predictedPoint,\r\n expandedRect\r\n )\r\n ) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"trajectory\" })\r\n }\r\n }\r\n\r\n this.emit({\r\n type: \"mouseTrajectoryUpdate\",\r\n predictionEnabled: this.enableMousePrediction,\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"processMouseMovement\")\r\n }\r\n }\r\n}\r\n","import type { Rect, ScrollDirection } from \"../types/types\"\n\nexport function getScrollDirection(oldRect: Rect, newRect: Rect): ScrollDirection {\n const scrollThreshold = 1\n const deltaY = newRect.top - oldRect.top\n const deltaX = newRect.left - oldRect.left\n\n // Check vertical scroll first (most common)\n if (deltaY < -scrollThreshold) {\n return \"down\" // Element moved up in viewport = scrolled down\n } else if (deltaY > scrollThreshold) {\n return \"up\" // Element moved down in viewport = scrolled up\n }\n\n // Check horizontal scroll\n if (deltaX < -scrollThreshold) {\n return \"right\" // Element moved left in viewport = scrolled right\n } else if (deltaX > scrollThreshold) {\n return \"left\" // Element moved right in viewport = scrolled left\n }\n\n return \"none\" // No significant movement detected\n}\n","import type { Point, ScrollDirection } from \"../types/types\"\n\nexport function predictNextScrollPosition(\n currentPoint: Point,\n direction: ScrollDirection,\n scrollMargin: number\n) {\n const { x, y } = currentPoint\n const predictedPoint = { x, y }\n\n switch (direction) {\n case \"down\":\n predictedPoint.y += scrollMargin\n break\n case \"up\":\n predictedPoint.y -= scrollMargin\n break\n case \"left\":\n predictedPoint.x -= scrollMargin\n break\n case \"right\":\n predictedPoint.x += scrollMargin\n break\n case \"none\":\n break\n default:\n direction satisfies never\n }\n return predictedPoint\n}\n","import type {\r\n ForesightElementData,\r\n Point,\r\n ScrollDirection,\r\n TrajectoryPositions,\r\n} from \"../types/types\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\nimport { getScrollDirection } from \"../helpers/getScrollDirection\"\r\nimport { predictNextScrollPosition } from \"../helpers/predictNextScrollPosition\"\r\nimport { lineSegmentIntersectsRect } from \"../helpers/lineSigmentIntersectsRect\"\r\n\r\nexport interface ScrollPredictorSettings {\r\n scrollMargin: number\r\n}\r\n\r\nexport interface ScrollPredictorConfig extends BasePredictorConfig {\r\n settings: ScrollPredictorSettings\r\n trajectoryPositions: Readonly<TrajectoryPositions>\r\n}\r\n\r\nexport class ScrollPredictor extends BasePredictor {\r\n protected initializeListeners(): void {\r\n // ScrollPredictor doesn't need direct event listeners\r\n // as it's called by the ForesightManager during position changes\r\n }\r\n\r\n private predictedScrollPoint: Point | null = null\r\n private scrollDirection: ScrollDirection | null = null\r\n public scrollMargin: number\r\n private trajectoryPositions: Readonly<TrajectoryPositions>\r\n\r\n constructor(config: ScrollPredictorConfig) {\r\n super(config)\r\n this.scrollMargin = config.settings.scrollMargin\r\n this.trajectoryPositions = config.trajectoryPositions\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n this.resetScrollProps()\r\n }\r\n\r\n public resetScrollProps(): void {\r\n this.scrollDirection = null\r\n this.predictedScrollPoint = null\r\n }\r\n\r\n public handleScrollPrefetch(elementData: ForesightElementData, newRect: DOMRect): void {\r\n if (!elementData.isIntersectingWithViewport) {\r\n return\r\n }\r\n\r\n try {\r\n // ONCE per handlePositionChange batch we decide what the scroll direction is\r\n this.scrollDirection =\r\n this.scrollDirection ?? getScrollDirection(elementData.elementBounds.originalRect, newRect)\r\n\r\n if (this.scrollDirection === \"none\") {\r\n return\r\n }\r\n\r\n // ONCE per handlePositionChange batch we decide the predicted scroll point\r\n this.predictedScrollPoint =\r\n this.predictedScrollPoint ??\r\n predictNextScrollPosition(\r\n this.trajectoryPositions.currentPoint,\r\n this.scrollDirection,\r\n this.scrollMargin\r\n )\r\n\r\n // Check if the scroll is going to intersect with an registered element\r\n if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.predictedScrollPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"scroll\",\r\n subType: this.scrollDirection,\r\n })\r\n }\r\n\r\n this.emit({\r\n type: \"scrollTrajectoryUpdate\",\r\n currentPoint: this.trajectoryPositions.currentPoint,\r\n predictedPoint: this.predictedScrollPoint,\r\n scrollDirection: this.scrollDirection,\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"handleScrollPrefetch\")\r\n }\r\n }\r\n\r\n protected handleError(error: unknown, context: string): void {\r\n super.handleError(error, context)\r\n\r\n // Emit fallback event on error\r\n this.emit({\r\n type: \"scrollTrajectoryUpdate\",\r\n currentPoint: this.trajectoryPositions.currentPoint,\r\n predictedPoint: this.trajectoryPositions.currentPoint,\r\n scrollDirection: \"none\",\r\n })\r\n }\r\n}\r\n","import { tabbable, type FocusableElement } from \"tabbable\"\r\nimport { getFocusedElementIndex } from \"../helpers/getFocusedElementIndex\"\r\nimport type { ForesightElement } from \"../types/types\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\n\r\nexport interface TabPredictorSettings {\r\n tabOffset: number\r\n}\r\n\r\nexport interface TabPredictorConfig extends BasePredictorConfig {\r\n settings: TabPredictorSettings\r\n}\r\n\r\n/**\r\n * Manages the prediction of user intent based on Tab key navigation.\r\n *\r\n * This class is a specialist module controlled by the ForesightManager.\r\n * Its responsibilities are:\r\n * - Listening for `keydown` and `focusin` events to detect tabbing.\r\n * - Caching the list of tabbable elements on the page for performance.\r\n * - Invalidating the cache when the DOM changes.\r\n * - Predicting which registered elements the user is about to focus.\r\n * - Calling a provided callback when a prediction is made.\r\n */\r\nexport class TabPredictor extends BasePredictor {\r\n // Internal state for tab prediction\r\n private lastKeyDown: KeyboardEvent | null = null\r\n private tabbableElementsCache: FocusableElement[] = []\r\n private lastFocusedIndex: number | null = null\r\n public tabOffset: number\r\n\r\n constructor(config: TabPredictorConfig) {\r\n super(config)\r\n this.tabOffset = config.settings.tabOffset\r\n this.initializeListeners()\r\n }\r\n\r\n protected initializeListeners(): void {\r\n const { signal } = this.abortController\r\n document.addEventListener(\"keydown\", this.handleKeyDown, { signal })\r\n document.addEventListener(\"focusin\", this.handleFocusIn, { signal })\r\n }\r\n\r\n public invalidateCache() {\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n this.lastKeyDown = null\r\n }\r\n\r\n // We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.\r\n // We dont use handleKeyDown for the full event because of 2 main reasons:\r\n // 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)\r\n // 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)\r\n private handleKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Tab\") {\r\n this.lastKeyDown = e\r\n }\r\n }\r\n\r\n private handleFocusIn = (e: FocusEvent) => {\r\n try {\r\n if (!this.lastKeyDown) {\r\n return\r\n }\r\n const targetElement = e.target\r\n if (!(targetElement instanceof HTMLElement)) {\r\n return\r\n }\r\n\r\n // tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values\r\n if (!this.tabbableElementsCache.length || this.lastFocusedIndex === -1) {\r\n this.tabbableElementsCache = tabbable(document.documentElement)\r\n }\r\n\r\n const isReversed = this.lastKeyDown.shiftKey\r\n\r\n const currentIndex: number = getFocusedElementIndex(\r\n isReversed,\r\n this.lastFocusedIndex,\r\n this.tabbableElementsCache,\r\n targetElement\r\n )\r\n\r\n this.lastFocusedIndex = currentIndex\r\n\r\n this.lastKeyDown = null\r\n const elementsToPredict: ForesightElement[] = []\r\n for (let i = 0; i <= this.tabOffset; i++) {\r\n const elementIndex = isReversed ? currentIndex - i : currentIndex + i\r\n const element = this.tabbableElementsCache[elementIndex]\r\n\r\n // Type guard: ensure element exists and is a valid ForesightElement\r\n if (element && element instanceof Element && this.elements.has(element)) {\r\n elementsToPredict.push(element)\r\n }\r\n }\r\n\r\n elementsToPredict.forEach(element => {\r\n const elementData = this.elements.get(element)\r\n if (elementData) {\r\n this.callCallback(elementData, {\r\n kind: \"tab\",\r\n subType: isReversed ? \"reverse\" : \"forwards\",\r\n })\r\n }\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"handleFocusIn\")\r\n }\r\n }\r\n}\r\n","import type { FocusableElement } from \"tabbable\"\r\n\r\n/**\r\n * Finds the index of a focused element within a cache of tabbable elements.\r\n * It uses a predictive search for O(1) performance in the common case of\r\n * sequential tabbing, and falls back to a linear search O(n) if the\r\n * prediction fails.\r\n *\r\n * @param isReversed - True if the user is tabbing backward (Shift+Tab).\r\n * @param lastFocusedIndex - The index of the previously focused element, or null if none.\r\n * @param tabbableElementsCache - The array of all tabbable elements.\r\n * @param targetElement - The new HTML element that has received focus.\r\n * @returns The index of the targetElement in the cache, or -1 if not found.\r\n */\r\nexport function getFocusedElementIndex(\r\n isReversed: boolean,\r\n lastFocusedIndex: number | null,\r\n tabbableElementsCache: FocusableElement[],\r\n targetElement: HTMLElement\r\n): number {\r\n // First, try to predict the next index based on the last known position.\r\n if (lastFocusedIndex !== null && lastFocusedIndex > -1) {\r\n const predictedIndex = isReversed ? lastFocusedIndex - 1 : lastFocusedIndex + 1\r\n\r\n // Check if the prediction is valid and correct.\r\n if (\r\n predictedIndex >= 0 &&\r\n predictedIndex < tabbableElementsCache.length &&\r\n tabbableElementsCache[predictedIndex] === targetElement\r\n ) {\r\n return predictedIndex\r\n }\r\n }\r\n\r\n return tabbableElementsCache.findIndex(element => element === targetElement)\r\n}\r\n"],"mappings":"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","names":["PositionObserver","CircularBuffer","capacity","item","lastIndex","first","last","newCapacity","currentItems","itemsToKeep","result","i","startIndex","bufferIndex","clampNumber","number","lowerBound","upperBound","settingName","initialViewportState","rect","viewportWidth","viewportHeight","normalizeHitSlop","hitSlop","clampedValue","clampNumber","getExpandedRect","baseRect","areRectsEqual","rect1","rect2","isPointInRectangle","point","rect","evaluateRegistrationConditions","isTouchDevice","userUsesTouchDevice","isLimitedConnection","hasConnectionLimitations","connection","shouldUpdateSetting","newValue","currentValue","lineSegmentIntersectsRect","p1","p2","rect","t0","t1","dx","dy","clipTest","p","q","r","predictNextMousePosition","currentPoint","buffer","trajectoryPredictionTimeInMs","now","currentPosition","x","y","first","last","dt","dx","dy","vx","vy","trajectoryPredictionTimeInSeconds","predictedX","predictedY","BasePredictor","config","error","context","MousePredictor","BasePredictor","config","signal","currentPoint","predictNextMousePosition","currentData","expandedRect","lineSegmentIntersectsRect","isPointInRectangle","error","getScrollDirection","oldRect","newRect","deltaY","deltaX","predictNextScrollPosition","currentPoint","direction","scrollMargin","x","y","predictedPoint","ScrollPredictor","BasePredictor","config","elementData","newRect","getScrollDirection","predictNextScrollPosition","lineSegmentIntersectsRect","error","context","tabbable","getFocusedElementIndex","isReversed","lastFocusedIndex","tabbableElementsCache","targetElement","predictedIndex","element","TabPredictor","BasePredictor","config","targetElement","tabbable","isReversed","currentIndex","getFocusedElementIndex","elementsToPredict","i","elementIndex","element","elementData","error","signal","ForesightManager","_ForesightManager","CircularBuffer","mutationsList","mutation","element","entries","entry","elementData","isPointInRectangle","updatedProps","isNowIntersecting","getExpandedRect","props","eventType","listener","options","listeners","index","event","error","callback","hitSlop","name","meta","reactivateAfter","shouldRegister","isTouchDevice","isLimitedConnection","evaluateRegistrationConditions","initialRect","normalizedHitSlop","normalizeHitSlop","initialViewportState","unregisterReason","wasLastElement","newValue","setting","min","max","shouldUpdateSetting","clampNumber","changedSettings","oldTrajectoryPredictionTime","oldPositionHistorySize","oldScrollMargin","oldTabOffset","oldEnableMousePrediction","oldEnableScrollPrediction","ScrollPredictor","oldEnableTabPrediction","TabPredictor","oldHitSlop","normalizedNewHitSlop","areRectsEqual","callbackHitType","start","errorMessage","settings","dependencies","MousePredictor","PositionObserver","newOriginalRect","expandedRect","updatedElementData"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js.foresight",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.0-beta.1",
|
|
4
4
|
"description": "Predicts mouse trajectory to trigger actions as users approach elements, enabling anticipatory UI updates or pre-loading. Made with vanilla javascript and usable in every framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|