js.foresight 3.2.0-beta.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -82,17 +82,19 @@ type ForesightRegisterResult = {
82
82
  isLimitedConnection: boolean;
83
83
  /** Whether ForesightJS will actively track this element. False if touch device or limited connection, true otherwise */
84
84
  isRegistered: boolean;
85
- /** Function to unregister the element */
85
+ /** Function to unregister the element
86
+ * @deprecated no longer need to call this manually, you can call Foresightmanager.instance.unregister if needed
87
+ */
86
88
  unregister: () => void;
87
89
  };
88
90
  /**
89
91
  * Represents the data associated with a registered foresight element.
90
92
  */
91
- type ForesightElementData = Required<Pick<ForesightRegisterOptions, "callback" | "name">> & {
93
+ type ForesightElementData = Required<Pick<ForesightRegisterOptions, "callback" | "name" | "meta">> & {
94
+ /** Unique identifier assigned during registration */
95
+ id: string;
92
96
  /** The boundary information for the element. */
93
97
  elementBounds: ElementBounds;
94
- /** True if the mouse cursor is currently hovering over the element's expanded bounds. */
95
- isHovering: boolean;
96
98
  /**
97
99
  * Represents trajectory hit related data for a foresight element. Only used for the manager
98
100
  */
@@ -111,9 +113,8 @@ type ForesightElementData = Required<Pick<ForesightRegisterOptions, "callback" |
111
113
  */
112
114
  registerCount: number;
113
115
  /**
114
- * If set by user, stores additional information about the registered element
116
+ * Callbackinfo for debugging purposes
115
117
  */
116
- meta: Record<string, unknown>;
117
118
  callbackInfo: ElementCallbackInfo;
118
119
  };
119
120
  type ElementCallbackInfo = {
@@ -124,7 +125,11 @@ type ElementCallbackInfo = {
124
125
  /**
125
126
  * Timestamp when the callback was last fired
126
127
  */
127
- lastCallbackFiredAt: number | undefined;
128
+ lastCallbackInvokedAt: number | undefined;
129
+ /**
130
+ * Timestamp when the last callback was finished
131
+ */
132
+ lastCallbackCompletedAt: number | undefined;
128
133
  /**
129
134
  * Time in milliseconds it took for the last callback to go from invoked to complete.
130
135
  */
@@ -132,7 +137,11 @@ type ElementCallbackInfo = {
132
137
  /**
133
138
  * Status of the last ran callback
134
139
  */
135
- lastCallbackStatus: "error" | "success" | undefined;
140
+ lastCallbackStatus: callbackStatus;
141
+ /**
142
+ * Last callback error message
143
+ */
144
+ lastCallbackErrorMessage: string | undefined | null;
136
145
  /**
137
146
  * Time in milliseconds after which the callback can be fired again
138
147
  */
@@ -145,7 +154,12 @@ type ElementCallbackInfo = {
145
154
  * If the element is currently running its callback
146
155
  */
147
156
  isRunningCallback: boolean;
157
+ /**
158
+ * Timeout ID for the scheduled reactivation, if any
159
+ */
160
+ reactivateTimeoutId?: ReturnType<typeof setTimeout>;
148
161
  };
162
+ type callbackStatus = "error" | "success" | undefined;
149
163
  type MouseCallbackCounts = {
150
164
  hover: number;
151
165
  trajectory: number;
@@ -329,8 +343,10 @@ interface ElementUnregisteredEvent extends ForesightBaseEvent {
329
343
  * - `callbackHit`: The element was automatically unregistered after its callback fired.
330
344
  * - `disconnected`: The element was automatically unregistered because it was removed from the DOM.
331
345
  * - `apiCall`: The developer manually called the `unregister()` function for the element.
346
+ * - `devtools`: When clicking the trash icon in the devtools element tab
347
+ * - any other string
332
348
  */
333
- type ElementUnregisteredReason = "callbackHit" | "disconnected" | "apiCall";
349
+ type ElementUnregisteredReason = "callbackHit" | "disconnected" | "apiCall" | "devtools" | (string & {});
334
350
  interface ElementDataUpdatedEvent extends Omit<ForesightBaseEvent, "timestamp"> {
335
351
  type: "elementDataUpdated";
336
352
  elementData: ForesightElementData;
@@ -348,12 +364,10 @@ interface CallbackCompletedEventBase extends ForesightBaseEvent {
348
364
  hitType: CallbackHitType;
349
365
  elapsed: number;
350
366
  }
351
- type CallbackCompletedEvent = CallbackCompletedEventBase & ({
352
- status: "success";
353
- } | {
354
- status: "error";
355
- errorMessage: string;
356
- });
367
+ type CallbackCompletedEvent = CallbackCompletedEventBase & {
368
+ status: callbackStatus;
369
+ errorMessage: string | undefined | null;
370
+ };
357
371
  interface MouseTrajectoryUpdateEvent extends Omit<ForesightBaseEvent, "timestamp"> {
358
372
  type: "mouseTrajectoryUpdate";
359
373
  trajectoryPositions: TrajectoryPositions;
@@ -405,6 +419,7 @@ declare class ForesightManager {
405
419
  private elements;
406
420
  private trajectoryPositions;
407
421
  private isSetup;
422
+ private idCounter;
408
423
  private _globalCallbackHits;
409
424
  private _globalSettings;
410
425
  private domObserver;
@@ -414,6 +429,7 @@ declare class ForesightManager {
414
429
  private tabPredictor;
415
430
  private scrollPredictor;
416
431
  private constructor();
432
+ private generateId;
417
433
  static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager;
418
434
  addEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>, options?: {
419
435
  signal?: AbortSignal;
@@ -425,7 +441,7 @@ declare class ForesightManager {
425
441
  static get instance(): ForesightManager;
426
442
  get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData>;
427
443
  register({ element, callback, hitSlop, name, meta, reactivateAfter, }: ForesightRegisterOptions): ForesightRegisterResult;
428
- unregister(element: ForesightElement, unregisterReason: ElementUnregisteredReason): void;
444
+ unregister(element: ForesightElement, unregisterReason?: ElementUnregisteredReason): void;
429
445
  private updateNumericSettings;
430
446
  private updateBooleanSetting;
431
447
  alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
@@ -437,7 +453,7 @@ declare class ForesightManager {
437
453
  */
438
454
  private handleDomMutations;
439
455
  private updateHitCounters;
440
- private makeElementActive;
456
+ reactivate(element: ForesightElement): void;
441
457
  private makeElementUnactive;
442
458
  private callCallback;
443
459
  private handlePositionChange;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
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};
1
+ import{PositionObserver as st}from"position-observer";var v=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 o of i)this.add(o)}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 o=(e+i)%this.capacity;t[i]=this.buffer[o]}}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(n,t,e,i){return n<t?console.warn(`ForesightJS: "${i}" value ${n} is below minimum bound ${t}, clamping to ${t}`):n>e&&console.warn(`ForesightJS: "${i}" value ${n} is above maximum bound ${e}, clamping to ${e}`),Math.min(Math.max(n,t),e)}function D(n){let t=window.innerWidth||document.documentElement.clientWidth,e=window.innerHeight||document.documentElement.clientHeight;return n.top<e&&n.bottom>0&&n.left<t&&n.right>0}function k(n){if(typeof n=="number"){let t=u(n,0,2e3,"hitslop");return{top:t,left:t,right:t,bottom:t}}return{top:u(n.top,0,2e3,"hitslop - top"),left:u(n.left,0,2e3,"hitslop - left"),right:u(n.right,0,2e3,"hitslop - right"),bottom:u(n.bottom,0,2e3,"hitslop - bottom")}}function S(n,t){return{left:n.left-t.left,right:n.right+t.right,top:n.top-t.top,bottom:n.bottom+t.bottom}}function x(n,t){return!n||!t?n===t:n.left===t.left&&n.right===t.right&&n.top===t.top&&n.bottom===t.bottom}function T(n,t){return n.x>=t.left&&n.x<=t.right&&n.y>=t.top&&n.y<=t.bottom}function j(){let n=z(),t=K();return{isTouchDevice:n,isLimitedConnection:t,shouldRegister:!n&&!t}}function z(){return window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function K(){let n=navigator.connection;return n?/2g/.test(n.effectiveType)||n.saveData:!1}function O(n,t){return n!==void 0&&t!==n}function I(n,t,e){let i=0,o=1,r=t.x-n.x,c=t.y-n.y,s=(a,l)=>{if(a===0){if(l<0)return!1}else{let d=l/a;if(a<0){if(d>o)return!1;d>i&&(i=d)}else{if(d<i)return!1;d<o&&(o=d)}}return!0};return!s(-r,n.x-e.left)||!s(r,e.right-n.x)||!s(-c,n.y-e.top)||!s(c,e.bottom-n.y)?!1:i<=o}function w(n,t,e){let i=performance.now(),o={point:n,time:i},{x:r,y:c}=n;if(t.add(o),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,g=d/l,b=p/l,R=e*.001,_=r+g*R,A=c+b*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 C=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=w(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||!i.callbackInfo.isCallbackActive||i.callbackInfo.isRunningCallback)continue;let o=i.elementBounds.expandedRect;if(this.enableMousePrediction)I(this.trajectoryPositions.currentPoint,this.trajectoryPositions.predictedPoint,o)&&this.callCallback(i,{kind:"mouse",subType:"trajectory"});else if(T(this.trajectoryPositions.currentPoint,o)){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(n,t){let i=t.top-n.top,o=t.left-n.left;return i<-1?"down":i>1?"up":o<-1?"right":o>1?"left":"none"}function N(n,t,e){let{x:i,y:o}=n,r={x:i,y:o};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 y=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||e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive))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),I(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(o){this.handleError(o,"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(n,t,e,i){if(t!==null&&t>-1){let o=n?t-1:t+1;if(o>=0&&o<e.length&&e[o]===i)return o}return e.findIndex(o=>o===i)}var E=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 o=this.lastKeyDown.shiftKey,r=U(o,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let c=[];for(let s=0;s<=this.tabOffset;s++){let a=o?r-s:r+s,l=this.tabbableElementsCache[a];l&&l instanceof Element&&this.elements.has(l)&&c.push(l)}for(let s of c){let a=this.elements.get(s);a&&!a.callbackInfo.isRunningCallback&&a.callbackInfo.isCallbackActive&&this.callCallback(a,{kind:"tab",subType:o?"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 n{constructor(){this.elements=new Map;this.trajectoryPositions={positions:new v(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.isSetup=!1;this.idCounter=0;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=>{let e=this._globalSettings.enableScrollPrediction;for(let i of t){let o=this.elements.get(i.target);o&&(e?this.scrollPredictor?.handleScrollPrefetch(o,i.boundingClientRect):T(this.trajectoryPositions.currentPoint,o.elementBounds.expandedRect)&&this.callCallback(o,{kind:"mouse",subType:"hover"}),this.handlePositionChangeDataUpdates(o,i))}this._globalSettings.enableScrollPrediction&&this.scrollPredictor?.resetScrollProps()};this.handlePositionChangeDataUpdates=(t,e)=>{let i=[],o=e.isIntersecting;t.isIntersectingWithViewport!==o&&(i.push("visibility"),t.isIntersectingWithViewport=o),o&&(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})}}generateId(){return`foresight-${++this.idCounter}`}static initialize(t){return this.isInitiated||(n.manager=new n),t!==void 0&&n.manager.alterGlobalSettings(t),n.manager}addEventListener(t,e,i){if(i?.signal?.aborted)return()=>{};let o=this.eventListeners.get(t)??[];o.push(e),this.eventListeners.set(t,o),i?.signal?.addEventListener("abort",()=>this.removeEventListener(t,e))}removeEventListener(t,e){let i=this.eventListeners.get(t);if(!i)return;let o=i.indexOf(e);o>-1&&i.splice(o,1)}emit(t){let e=this.eventListeners.get(t.type);if(e)for(let i=0;i<e.length;i++)try{e[i](t)}catch(o){console.error(`Error in ForesightManager event listener ${i} for ${t.type}:`,o)}}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners}}static get isInitiated(){return!!n.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:t,callback:e,hitSlop:i,name:o,meta:r,reactivateAfter:c}){let{shouldRegister:s,isTouchDevice:a,isLimitedConnection:l}=j();if(!s)return{isLimitedConnection:l,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};let d=this.elements.get(t);if(d)return d.registerCount++,{isLimitedConnection:l,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let p=t.getBoundingClientRect(),g=i?k(i):this._globalSettings.defaultHitSlop,b={id:this.generateId(),element:t,callback:e,elementBounds:{originalRect:p,expandedRect:S(p,g),hitSlop:g},trajectoryHitData:{isTrajectoryHit:!1,trajectoryHitTime:0,trajectoryHitExpirationTimeoutId:void 0},name:o||t.id||"unnamed",isIntersectingWithViewport:D(p),registerCount:1,meta:r??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:c??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId: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){let i=this.elements.get(t);if(!i)return;i?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(i.trajectoryHitData.trajectoryHitExpirationTimeoutId),i?.callbackInfo.reactivateTimeoutId&&clearTimeout(i.callbackInfo.reactivateTimeoutId),this.positionObserver?.unobserve(t),this.elements.delete(t);let o=this.elements.size===0&&this.isSetup;o&&this.removeGlobalListeners(),i&&this.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:e??"by user",wasLastElement:o})}updateNumericSettings(t,e,i,o){return O(t,this._globalSettings[e])?(this._globalSettings[e]=u(t,i,o,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 f=this._globalSettings.scrollMargin;this.scrollPredictor&&(this.scrollPredictor.scrollMargin=f),e.push({setting:"scrollMargin",oldValue:s,newValue:f})}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 b=this._globalSettings.enableScrollPrediction;this.updateBooleanSetting(t?.enableScrollPrediction,"enableScrollPrediction")&&(this._globalSettings.enableScrollPrediction?this.scrollPredictor=new y({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:b,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 E({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 f=this._globalSettings.defaultHitSlop,F=k(t.defaultHitSlop);x(f,F)||(this._globalSettings.defaultHitSlop=F,e.push({setting:"defaultHitSlop",oldValue:f,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++}reactivate(t){let e=this.elements.get(t);e&&(e.callbackInfo.reactivateTimeoutId&&(clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0),e.callbackInfo.isRunningCallback||(e.callbackInfo.isCallbackActive=!0,this.positionObserver?.observe(e.element),this.emit({type:"elementReactivated",elementData:e,timestamp:Date.now()})))}makeElementUnactive(t){t.callbackInfo.callbackFiredCount++,t.callbackInfo.lastCallbackInvokedAt=Date.now(),t.callbackInfo.isRunningCallback=!0,t.callbackInfo.reactivateTimeoutId&&(clearTimeout(t.callbackInfo.reactivateTimeoutId),t.callbackInfo.reactivateTimeoutId=void 0),t?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(t.trajectoryHitData.trajectoryHitExpirationTimeoutId)}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 o=performance.now(),r,c=null;try{await t.callback(),r="success"}catch(s){c=s instanceof Error?s.message:String(s),r="error",console.error(`Error in callback for element ${t.name} (${t.element.tagName}):`,s)}t.callbackInfo.lastCallbackCompletedAt=Date.now(),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:t,hitType:e,elapsed:t.callbackInfo.lastCallbackRuntime=performance.now()-o,status:t.callbackInfo.lastCallbackStatus=r,errorMessage:t.callbackInfo.lastCallbackErrorMessage=c}),t.callbackInfo.isRunningCallback=!1,t.callbackInfo.isCallbackActive=!1,this.positionObserver?.unobserve(t.element),t.callbackInfo.reactivateAfter!==1/0&&t.callbackInfo.reactivateAfter>0&&(t.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(t.element)},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 E({dependencies:e,settings:{tabOffset:t.tabOffset}})),t.enableScrollPrediction&&(this.scrollPredictor=new y({dependencies:e,settings:{scrollMargin:t.scrollMargin},trajectoryPositions:this.trajectoryPositions})),this.mousePredictor=new C({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(!x(i,t.elementBounds.expandedRect)){let o={...t,elementBounds:{...t.elementBounds,originalRect:e,expandedRect:i}};this.elements.set(t.element,o),this.emit({type:"elementDataUpdated",elementData:o,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_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"]}
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 callbackStatus,\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 idCounter: number = 0\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 private generateId(): string {\r\n return `foresight-${++this.idCounter}`\r\n }\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 for (let i = 0; i < listeners.length; i++) {\r\n try {\r\n listeners[i](event)\r\n } catch (error) {\r\n console.error(`Error in ForesightManager event listener ${i} 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 const previousElementData = this.elements.get(element)\r\n if (previousElementData) {\r\n previousElementData.registerCount++\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 id: this.generateId(),\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 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: 1,\r\n meta: meta ?? {},\r\n callbackInfo: {\r\n callbackFiredCount: 0,\r\n lastCallbackInvokedAt: undefined,\r\n lastCallbackCompletedAt: undefined,\r\n lastCallbackRuntime: undefined,\r\n lastCallbackStatus: undefined,\r\n lastCallbackErrorMessage: undefined,\r\n reactivateAfter: reactivateAfter ?? DEFAULT_STALE_TIME,\r\n isCallbackActive: true,\r\n isRunningCallback: false,\r\n reactivateTimeoutId: 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 const elementData = this.elements.get(element)\r\n if (!elementData) {\r\n return\r\n }\r\n\r\n if (elementData?.trajectoryHitData.trajectoryHitExpirationTimeoutId) {\r\n clearTimeout(elementData.trajectoryHitData.trajectoryHitExpirationTimeoutId)\r\n }\r\n\r\n // Clear reactivation timeout if exists\r\n if (elementData?.callbackInfo.reactivateTimeoutId) {\r\n clearTimeout(elementData.callbackInfo.reactivateTimeoutId)\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 ?? \"by user\",\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 public reactivate(element: ForesightElement) {\r\n const elementData = this.elements.get(element)\r\n if (!elementData) {\r\n return\r\n }\r\n\r\n // Clear any existing reactivation timeout\r\n if (elementData.callbackInfo.reactivateTimeoutId) {\r\n clearTimeout(elementData.callbackInfo.reactivateTimeoutId)\r\n elementData.callbackInfo.reactivateTimeoutId = undefined\r\n }\r\n\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({\r\n type: \"elementReactivated\",\r\n elementData: elementData,\r\n timestamp: Date.now(),\r\n })\r\n }\r\n }\r\n\r\n private makeElementUnactive(elementData: ForesightElementData) {\r\n elementData.callbackInfo.callbackFiredCount++\r\n elementData.callbackInfo.lastCallbackInvokedAt = Date.now()\r\n elementData.callbackInfo.isRunningCallback = true\r\n // dont set isCallbackActive to false here, only do that after the callback is finished running\r\n\r\n // Clear any existing reactivation timeout\r\n if (elementData.callbackInfo.reactivateTimeoutId) {\r\n clearTimeout(elementData.callbackInfo.reactivateTimeoutId)\r\n elementData.callbackInfo.reactivateTimeoutId = undefined\r\n }\r\n\r\n if (elementData?.trajectoryHitData.trajectoryHitExpirationTimeoutId) {\r\n clearTimeout(elementData.trajectoryHitData.trajectoryHitExpirationTimeoutId)\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\r\n const start = performance.now()\r\n let status: callbackStatus = undefined\r\n let errorMessage = null\r\n try {\r\n await elementData.callback()\r\n status = \"success\"\r\n } catch (error) {\r\n errorMessage = error instanceof Error ? error.message : String(error)\r\n status = \"error\"\r\n console.error(\r\n `Error in callback for element ${elementData.name} (${elementData.element.tagName}):`,\r\n error\r\n )\r\n }\r\n\r\n elementData.callbackInfo.lastCallbackCompletedAt = Date.now()\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 = performance.now() - start),\r\n status: (elementData.callbackInfo.lastCallbackStatus = status),\r\n errorMessage: (elementData.callbackInfo.lastCallbackErrorMessage = errorMessage),\r\n })\r\n\r\n // Reset running state\r\n elementData.callbackInfo.isRunningCallback = false\r\n\r\n //Only make the callback unactive AFTER the callback is finished running, same for positionObserver as otherwise the animation wont run in debugger\r\n elementData.callbackInfo.isCallbackActive = false\r\n this.positionObserver?.unobserve(elementData.element)\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 elementData.callbackInfo.reactivateTimeoutId = setTimeout(() => {\r\n this.reactivate(elementData.element)\r\n }, elementData.callbackInfo.reactivateAfter)\r\n }\r\n }\r\n asyncCallbackWrapper()\r\n }\r\n\r\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\r\n const enableScrollPosition = this._globalSettings.enableScrollPrediction\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 (enableScrollPosition) {\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\" as const],\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 (\r\n !currentData.isIntersectingWithViewport ||\r\n !currentData.callbackInfo.isCallbackActive ||\r\n currentData.callbackInfo.isRunningCallback\r\n ) {\r\n continue\r\n }\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 (\r\n !elementData.isIntersectingWithViewport ||\r\n elementData.callbackInfo.isRunningCallback ||\r\n !elementData.callbackInfo.isCallbackActive\r\n ) {\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 for (const element of elementsToPredict) {\r\n const elementData = this.elements.get(element)\r\n if (\r\n elementData &&\r\n !elementData.callbackInfo.isRunningCallback &&\r\n elementData.callbackInfo.isCallbackActive\r\n ) {\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","enableScrollPosition","entry","elementData","isPointInRectangle","updatedProps","isNowIntersecting","getExpandedRect","props","eventType","listener","options","listeners","index","event","error","callback","hitSlop","name","meta","reactivateAfter","shouldRegister","isTouchDevice","isLimitedConnection","evaluateRegistrationConditions","previousElementData","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","status","errorMessage","settings","dependencies","MousePredictor","PositionObserver","newOriginalRect","expandedRect","updatedElementData"]}
package/package.json CHANGED
@@ -1,68 +1,68 @@
1
- {
2
- "name": "js.foresight",
3
- "version": "3.2.0-beta.1",
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
- "type": "module",
6
- "main": "./dist/index.js",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
- }
14
- },
15
- "homepage": "https://foresightjs.com/",
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/spaansba/ForesightJS"
19
- },
20
- "files": [
21
- "dist",
22
- "README.md",
23
- "LICENSE"
24
- ],
25
- "keywords": [
26
- "javascript-prefetch",
27
- "smart-prefetch",
28
- "fast-prefetch",
29
- "mouse-trajectory",
30
- "element-hitslop",
31
- "foresight",
32
- "interaction-prediction",
33
- "cursor-prediction",
34
- "vanilla-javascript",
35
- "prefetching",
36
- "keyboard-tracking",
37
- "keyboard-prefetching",
38
- "tab-prefetching"
39
- ],
40
- "author": "Bart Spaans",
41
- "license": "MIT",
42
- "devDependencies": {
43
- "@testing-library/dom": "^10.4.0",
44
- "@testing-library/jest-dom": "^6.6.3",
45
- "@types/node": "^22.15.30",
46
- "@vitest/coverage-v8": "3.2.4",
47
- "@vitest/ui": "^3.2.4",
48
- "happy-dom": "^18.0.1",
49
- "jsdom": "^26.1.0",
50
- "tslib": "^2.8.1",
51
- "tsup": "^8.5.0",
52
- "typescript": "^5.8.3",
53
- "vitest": "^3.2.4"
54
- },
55
- "dependencies": {
56
- "position-observer": "^1.0.0",
57
- "tabbable": "^6.2.0"
58
- },
59
- "scripts": {
60
- "build": "tsup",
61
- "dev": "tsup --watch",
62
- "test": "vitest",
63
- "test:watch": "vitest --watch",
64
- "test:ui": "vitest --ui",
65
- "test:coverage": "vitest --coverage",
66
- "test:run": "vitest run"
67
- }
68
- }
1
+ {
2
+ "name": "js.foresight",
3
+ "version": "3.2.0",
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
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsup",
8
+ "dev": "tsup --watch",
9
+ "test": "vitest",
10
+ "test:watch": "vitest --watch",
11
+ "test:ui": "vitest --ui",
12
+ "test:coverage": "vitest --coverage",
13
+ "test:run": "vitest run"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "homepage": "https://foresightjs.com/",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/spaansba/ForesightJS"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "keywords": [
35
+ "javascript-prefetch",
36
+ "smart-prefetch",
37
+ "fast-prefetch",
38
+ "mouse-trajectory",
39
+ "element-hitslop",
40
+ "foresight",
41
+ "interaction-prediction",
42
+ "cursor-prediction",
43
+ "vanilla-javascript",
44
+ "prefetching",
45
+ "keyboard-tracking",
46
+ "keyboard-prefetching",
47
+ "tab-prefetching"
48
+ ],
49
+ "author": "Bart Spaans",
50
+ "license": "MIT",
51
+ "devDependencies": {
52
+ "@testing-library/dom": "^10.4.0",
53
+ "@testing-library/jest-dom": "^6.6.3",
54
+ "@types/node": "^22.15.30",
55
+ "@vitest/coverage-v8": "3.2.4",
56
+ "@vitest/ui": "^3.2.4",
57
+ "happy-dom": "^18.0.1",
58
+ "jsdom": "^26.1.0",
59
+ "tslib": "^2.8.1",
60
+ "tsup": "^8.5.0",
61
+ "typescript": "^5.8.3",
62
+ "vitest": "^3.2.4"
63
+ },
64
+ "dependencies": {
65
+ "position-observer": "^1.0.0",
66
+ "tabbable": "^6.2.0"
67
+ }
68
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Bart Spaans
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.