js.foresight 3.2.0 → 3.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,7 +61,7 @@ ForesightManager.initialize({
61
61
  // Register an element to be tracked
62
62
  const myButton = document.getElementById("my-button")
63
63
 
64
- const { isTouchDevice, unregister } = ForesightManager.instance.register({
64
+ const { isTouchDevice } = ForesightManager.instance.register({
65
65
  element: myButton,
66
66
  callback: () => {
67
67
  // This is where your prefetching logic goes
@@ -69,9 +69,6 @@ const { isTouchDevice, unregister } = ForesightManager.instance.register({
69
69
  hitSlop: 20, // Optional: "hit slop" in pixels. Overwrites defaultHitSlop
70
70
  // other optional props (see configuration)
71
71
  })
72
-
73
- // Later, when done with this element:
74
- unregister()
75
72
  ```
76
73
 
77
74
  ## Integrations
package/dist/index.d.ts CHANGED
@@ -175,6 +175,8 @@ type CallbackHits = {
175
175
  mouse: MouseCallbackCounts;
176
176
  tab: TabCallbackCounts;
177
177
  scroll: ScrollCallbackCounts;
178
+ touch: number;
179
+ viewport: number;
178
180
  };
179
181
  type CallbackHitType = {
180
182
  kind: "mouse";
@@ -185,6 +187,12 @@ type CallbackHitType = {
185
187
  } | {
186
188
  kind: "scroll";
187
189
  subType: keyof ScrollCallbackCounts;
190
+ } | {
191
+ kind: "touch";
192
+ subType?: string;
193
+ } | {
194
+ kind: "viewport";
195
+ subType?: string;
188
196
  };
189
197
  /**
190
198
  * Snapshot of the current ForesightManager state
@@ -194,7 +202,9 @@ type ForesightManagerData = {
194
202
  globalSettings: Readonly<ForesightManagerSettings>;
195
203
  globalCallbackHits: Readonly<CallbackHits>;
196
204
  eventListeners: ReadonlyMap<keyof ForesightEventMap, ForesightEventListener[]>;
205
+ currentDeviceStrategy: CurrentDeviceStrategy;
197
206
  };
207
+ type TouchDeviceStrategy = "none" | "viewport" | "onTouchStart";
198
208
  type BaseForesightManagerSettings = {
199
209
  /**
200
210
  * Number of mouse positions to keep in history for trajectory calculation.
@@ -261,7 +271,16 @@ type BaseForesightManagerSettings = {
261
271
  * @default 2
262
272
  */
263
273
  tabOffset: number;
274
+ /**
275
+ * The prefetch strategy used for touch devices.
276
+ * - `none`: No prefetching is done on touch devices.
277
+ * - `viewport`: Prefetching is done based on the viewport, meaning elements in the viewport are preloaded.
278
+ * - `onTouchStart`: Prefetching is done when the user touches the element
279
+ * @default onTouchStart
280
+ */
281
+ touchDeviceStrategy: TouchDeviceStrategy;
264
282
  };
283
+ type CurrentDeviceStrategy = "mouse" | "touch" | "pen";
265
284
  /**
266
285
  * Configuration options for the ForesightManager
267
286
  * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
@@ -322,8 +341,14 @@ interface ForesightEventMap {
322
341
  mouseTrajectoryUpdate: MouseTrajectoryUpdateEvent;
323
342
  scrollTrajectoryUpdate: ScrollTrajectoryUpdateEvent;
324
343
  managerSettingsChanged: ManagerSettingsChangedEvent;
344
+ deviceStrategyChanged: DeviceStrategyChangedEvent;
345
+ }
346
+ type ForesightEvent = "elementRegistered" | "elementReactivated" | "elementUnregistered" | "elementDataUpdated" | "callbackInvoked" | "callbackCompleted" | "mouseTrajectoryUpdate" | "scrollTrajectoryUpdate" | "managerSettingsChanged" | "deviceStrategyChanged";
347
+ interface DeviceStrategyChangedEvent extends ForesightBaseEvent {
348
+ type: "deviceStrategyChanged";
349
+ newStrategy: CurrentDeviceStrategy;
350
+ oldStrategy: CurrentDeviceStrategy;
325
351
  }
326
- type ForesightEvent = "elementRegistered" | "elementReactivated" | "elementUnregistered" | "elementDataUpdated" | "callbackInvoked" | "callbackCompleted" | "mouseTrajectoryUpdate" | "scrollTrajectoryUpdate" | "managerSettingsChanged";
327
352
  interface ElementRegisteredEvent extends ForesightBaseEvent {
328
353
  type: "elementRegistered";
329
354
  elementData: ForesightElementData;
@@ -417,17 +442,18 @@ interface ForesightBaseEvent {
417
442
  declare class ForesightManager {
418
443
  private static manager;
419
444
  private elements;
420
- private trajectoryPositions;
445
+ private desktopHandler;
446
+ private touchDeviceHandler;
447
+ private handler;
421
448
  private isSetup;
422
449
  private idCounter;
423
450
  private _globalCallbackHits;
424
451
  private _globalSettings;
452
+ private pendingPointerEvent;
453
+ private rafId;
425
454
  private domObserver;
426
- private positionObserver;
427
455
  private eventListeners;
428
- private mousePredictor;
429
- private tabPredictor;
430
- private scrollPredictor;
456
+ private currentDeviceStrategy;
431
457
  private constructor();
432
458
  private generateId;
433
459
  static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager;
@@ -442,9 +468,14 @@ declare class ForesightManager {
442
468
  get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData>;
443
469
  register({ element, callback, hitSlop, name, meta, reactivateAfter, }: ForesightRegisterOptions): ForesightRegisterResult;
444
470
  unregister(element: ForesightElement, unregisterReason?: ElementUnregisteredReason): void;
445
- private updateNumericSettings;
446
- private updateBooleanSetting;
447
- alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
471
+ private updateHitCounters;
472
+ reactivate(element: ForesightElement): void;
473
+ private makeElementUnactive;
474
+ private callCallback;
475
+ private setDeviceStrategy;
476
+ private handlePointerMove;
477
+ private initializeGlobalListeners;
478
+ private removeGlobalListeners;
448
479
  /**
449
480
  * Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.
450
481
  *
@@ -452,20 +483,16 @@ declare class ForesightManager {
452
483
  *
453
484
  */
454
485
  private handleDomMutations;
455
- private updateHitCounters;
456
- reactivate(element: ForesightElement): void;
457
- private makeElementUnactive;
458
- private callCallback;
459
- private handlePositionChange;
460
- private handlePositionChangeDataUpdates;
461
- private initializeGlobalListeners;
462
- private removeGlobalListeners;
463
486
  private forceUpdateAllElementBounds;
464
487
  /**
465
488
  * 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.
466
489
  * We need an observer for that
467
490
  */
468
491
  private forceUpdateElementBounds;
492
+ private initializeManagerSettings;
493
+ private updateNumericSettings;
494
+ private updateBooleanSetting;
495
+ alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
469
496
  }
470
497
 
471
- export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type ElementCallbackInfo, type ElementDataUpdatedEvent, type ElementReactivatedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightElement, type ForesightElementData, type ForesightEvent, ForesightManager, type ForesightManagerSettings, type Rect as ForesightRect, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HitSlop, type ManagerSettingsChangedEvent, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
498
+ export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type DeviceStrategyChangedEvent, type ElementCallbackInfo, type ElementDataUpdatedEvent, type ElementReactivatedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightElement, type ForesightElementData, type ForesightEvent, ForesightManager, type ForesightManagerSettings, type Rect as ForesightRect, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HitSlop, type ManagerSettingsChangedEvent, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
package/dist/index.js CHANGED
@@ -1,2 +1 @@
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
- //# sourceMappingURL=index.js.map
1
+ function h(o,e,t,i){return o<e?console.warn(`ForesightJS: "${i}" value ${o} is below minimum bound ${e}, clamping to ${e}`):o>t&&console.warn(`ForesightJS: "${i}" value ${o} is above maximum bound ${t}, clamping to ${t}`),Math.min(Math.max(o,e),t)}function z(o){let e=window.innerWidth||document.documentElement.clientWidth,t=window.innerHeight||document.documentElement.clientHeight;return o.top<t&&o.bottom>0&&o.left<e&&o.right>0}function P(o){if(typeof o=="number"){let e=h(o,0,2e3,"hitslop");return{top:e,left:e,right:e,bottom:e}}return{top:h(o.top,0,2e3,"hitslop - top"),left:h(o.left,0,2e3,"hitslop - left"),right:h(o.right,0,2e3,"hitslop - right"),bottom:h(o.bottom,0,2e3,"hitslop - bottom")}}function S(o,e){return{left:o.left-e.left,right:o.right+e.right,top:o.top-e.top,bottom:o.bottom+e.bottom}}function H(o,e){return!o||!e?o===e:o.left===e.left&&o.right===e.right&&o.top===e.top&&o.bottom===e.bottom}function T(o,e){return o.x>=e.left&&o.x<=e.right&&o.y>=e.top&&o.y<=e.bottom}function K(){let o=N(),e=te();return{isTouchDevice:o,isLimitedConnection:e,shouldRegister:!o&&!e}}function N(){return window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function te(){let o=navigator.connection;return o?/2g/.test(o.effectiveType)||o.saveData:!1}function A(o,e){return o!==void 0&&e!==o}var d=class{constructor(e){this._isConnected=!1;this.elements=e.elements,this.callCallback=e.callCallback,this.emit=e.emit,this.settings=e.settings}get isConnected(){return this._isConnected}disconnect(){this.isConnected&&(process.env.NODE_ENV==="development"&&console.log(`\u{1F50C} ${this.moduleName} disconnecting...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){process.env.NODE_ENV==="development"&&console.log(`\u{1F50C} ${this.moduleName} connecting...`),this.onConnect(),this._isConnected=!0}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,process.env.NODE_ENV==="development"&&console.log(`\u{1F39B}\uFE0F ${this.moduleName} created new AbortController`))}handleError(e,t){console.error(`${this.moduleName} error in ${t}:`,e)}};function I(o,e,t){let i=0,n=1,r=e.x-o.x,l=e.y-o.y,s=(a,c)=>{if(a===0){if(c<0)return!1}else{let u=c/a;if(a<0){if(u>n)return!1;u>i&&(i=u)}else{if(u<i)return!1;u<n&&(n=u)}}return!0};return!s(-r,o.x-t.left)||!s(r,t.right-o.x)||!s(-l,o.y-t.top)||!s(l,t.bottom-o.y)?!1:i<=n}function $(o,e,t){let i=performance.now(),n={point:o,time:i},{x:r,y:l}=o;if(e.add(n),e.length<2)return{x:r,y:l};let[s,a]=e.getFirstLast();if(!s||!a)return{x:r,y:l};let c=(a.time-s.time)*.001;if(c===0)return{x:r,y:l};let u=a.point.x-s.point.x,p=a.point.y-s.point.y,v=u/c,x=p/c,O=t*.001,w=r+v*O,V=l+x*O;return{x:w,y:V}}var C=class extends d{constructor(t){super(t.dependencies);this.moduleName="MousePredictor";this.trajectoryPositions=t.trajectoryPositions}onDisconnect(){}onConnect(){}updatePointerState(t){let i={x:t.clientX,y:t.clientY};this.trajectoryPositions.currentPoint=i,this.settings.enableMousePrediction?this.trajectoryPositions.predictedPoint=$(i,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime):this.trajectoryPositions.predictedPoint=i}processMouseMovement(t){try{this.updatePointerState(t);for(let i of this.elements.values()){if(!i.isIntersectingWithViewport||!i.callbackInfo.isCallbackActive||i.callbackInfo.isRunningCallback)continue;let n=i.elementBounds.expandedRect;if(this.settings.enableMousePrediction)I(this.trajectoryPositions.currentPoint,this.trajectoryPositions.predictedPoint,n)&&this.callCallback(i,{kind:"mouse",subType:"trajectory"});else if(T(this.trajectoryPositions.currentPoint,n)){this.callCallback(i,{kind:"mouse",subType:"hover"});return}}this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:this.settings.enableMousePrediction,trajectoryPositions:this.trajectoryPositions})}catch(i){this.handleError(i,"processMouseMovement")}}};function Y(o,e){let i=e.top-o.top,n=e.left-o.left;return i<-1?"down":i>1?"up":n<-1?"right":n>1?"left":"none"}function X(o,e,t){let{x:i,y:n}=o,r={x:i,y:n};switch(e){case"down":r.y+=t;break;case"up":r.y-=t;break;case"left":r.x-=t;break;case"right":r.x+=t;break;case"none":break;default:}return r}var M=class extends d{constructor(t){super(t.dependencies);this.moduleName="ScrollPredictor";this.predictedScrollPoint=null;this.scrollDirection=null;this.trajectoryPositions=t.trajectoryPositions}onConnect(){}onDisconnect(){this.resetScrollProps()}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(t,i){if(!(!t.isIntersectingWithViewport||t.callbackInfo.isRunningCallback||!t.callbackInfo.isCallbackActive))try{if(this.scrollDirection=this.scrollDirection??Y(t.elementBounds.originalRect,i),this.scrollDirection==="none")return;this.predictedScrollPoint=this.predictedScrollPoint??X(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),I(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"scroll",subType:this.scrollDirection}),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})}catch(n){this.handleError(n,"handleScrollPrefetch")}}handleError(t,i){super.handleError(t,i),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.trajectoryPositions.currentPoint,scrollDirection:"none"})}};import{tabbable as ie}from"tabbable";function W(o,e,t,i){if(e!==null&&e>-1){let n=o?e-1:e+1;if(n>=0&&n<t.length&&t[n]===i)return n}return t.findIndex(n=>n===i)}var F=class extends d{constructor(t){super(t);this.moduleName="TabPredictor";this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=t=>{t.key==="Tab"&&(this.lastKeyDown=t)};this.handleFocusIn=t=>{try{if(!this.lastKeyDown)return;let i=t.target;if(!(i instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.tabbableElementsCache=ie(document.documentElement));let n=this.lastKeyDown.shiftKey,r=W(n,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let l=[];for(let s=0;s<=this.settings.tabOffset;s++){let a=n?r-s:r+s,c=this.tabbableElementsCache[a];c&&c instanceof Element&&this.elements.has(c)&&l.push(c)}for(let s of l){let a=this.elements.get(s);a&&!a.callbackInfo.isRunningCallback&&a.callbackInfo.isCallbackActive&&this.callCallback(a,{kind:"tab",subType:n?"reverse":"forwards"})}}catch(i){this.handleError(i,"handleFocusIn")}}}invalidateCache(){this.tabbableElementsCache=[],this.lastFocusedIndex=null}onConnect(){this.createAbortController(),document.addEventListener("keydown",this.handleKeyDown,{signal:this.abortController?.signal,passive:!0}),document.addEventListener("focusin",this.handleFocusIn,{signal:this.abortController?.signal,passive:!0})}onDisconnect(){this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}};import{PositionObserver as oe}from"position-observer";var D=class{constructor(e){this.head=0;this.count=0;if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=e,this.buffer=new Array(e)}add(e){this.buffer[this.head]=e,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let e=(this.head-1+this.capacity)%this.capacity;return this.buffer[e]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let i=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[i,i]}let e=this.getFirst(),t=this.getLast();return[e,t]}resize(e){if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(e===this.capacity)return;let t=this.getAllItems();if(this.capacity=e,this.buffer=new Array(e),this.head=0,this.count=0,t.length>e){let i=t.slice(-e);for(let n of i)this.add(n)}else for(let i of t)this.add(i)}getAllItems(){if(this.count===0)return[];let e=new Array(this.count);if(this.count<this.capacity)for(let t=0;t<this.count;t++)e[t]=this.buffer[t];else{let t=this.head;for(let i=0;i<this.capacity;i++){let n=(t+i)%this.capacity;e[i]=this.buffer[n]}}return e}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}};var m=class extends d{constructor(t){super(t);this.moduleName="DesktopHandler";this.positionObserver=null;this.trajectoryPositions={positions:new D(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.handlePositionChange=t=>{let i=this.settings.enableScrollPrediction;for(let n of t){let r=this.elements.get(n.target);r&&(i?this.scrollPredictor?.handleScrollPrefetch(r,n.boundingClientRect):T(this.trajectoryPositions.currentPoint,r.elementBounds.expandedRect)&&this.callCallback(r,{kind:"mouse",subType:"hover"}),this.handlePositionChangeDataUpdates(r,n))}i&&this.scrollPredictor?.resetScrollProps()};this.handlePositionChangeDataUpdates=(t,i)=>{let n=[],r=i.isIntersecting;t.isIntersectingWithViewport!==r&&(n.push("visibility"),t.isIntersectingWithViewport=r),r&&(n.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:i.boundingClientRect,expandedRect:S(i.boundingClientRect,t.elementBounds.hitSlop)}),n.length&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:n})};this.tabPredictor=new F(t),this.scrollPredictor=new M({dependencies:t,trajectoryPositions:this.trajectoryPositions}),this.mousePredictor=new C({dependencies:t,trajectoryPositions:this.trajectoryPositions})}invalidateTabCache(){this.tabPredictor?.invalidateCache()}processMouseMovement(t){this.mousePredictor.processMouseMovement(t)}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new oe(this.handlePositionChange);for(let t of this.elements.keys())this.positionObserver.observe(t)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}observeElement(t){this.positionObserver?.observe(t)}unobserveElement(t){this.positionObserver?.unobserve(t)}connectTabPredictor(){this.tabPredictor.connect()}connectScrollPredictor(){this.scrollPredictor.connect()}connectMousePredictor(){this.mousePredictor.connect()}disconnectTabPredictor(){this.tabPredictor.disconnect()}disconnectScrollPredictor(){this.scrollPredictor.disconnect()}disconnectMousePredictor(){this.mousePredictor.disconnect()}};var _=class extends d{constructor(t){super(t);this.moduleName="ViewportPredictor";this.intersectionObserver=null}onConnect(){this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter.bind(this))}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}observeElement(t){this.intersectionObserver?.observe(t)}unobserveElement(t){this.intersectionObserver?.unobserve(t)}handleViewportEnter(t){t.forEach(i=>{if(i.isIntersecting){let n=this.elements.get(i.target);n&&(this.callCallback(n,{kind:"viewport"}),this.unobserveElement(i.target))}})}};var k=class extends d{constructor(t){super(t);this.moduleName="TouchStartPredictor";this.handleTouchStart=t=>{let i=t.target,n=this.elements.get(i);n&&(this.callCallback(n,{kind:"touch"}),this.unobserveElement(i))}}onConnect(){this.createAbortController()}onDisconnect(){}observeElement(t){t instanceof HTMLElement&&t.addEventListener("touchstart",this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(t){t instanceof HTMLElement&&t.removeEventListener("touchstart",this.handleTouchStart)}};var f=class extends d{constructor(t){super(t);this.moduleName="TouchDeviceHandler";this.viewportPredictor=new _(t),this.touchStartPredictor=new k(t),this.predictor=this.viewportPredictor}setTouchPredictor(){this.predictor.disconnect(),this.settings.touchDeviceStrategy==="viewport"?this.predictor=this.viewportPredictor:this.settings.touchDeviceStrategy==="onTouchStart"&&(this.predictor=this.touchStartPredictor),this.predictor.connect();for(let t of this.elements.keys())this.predictor.observeElement(t)}onDisconnect(){this.predictor.disconnect()}onConnect(){this.setTouchPredictor()}observeElement(t){this.predictor.observeElement(t)}unobserveElement(t){this.predictor.unobserveElement(t)}};var B=class o{constructor(e){this.elements=new Map;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},touch:0,viewport: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,touchDeviceStrategy:"viewport"};this.pendingPointerEvent=null;this.rafId=null;this.domObserver=null;this.eventListeners=new Map;this.currentDeviceStrategy=N()?"touch":"mouse";this.handlePointerMove=e=>{e.pointerType!=this.currentDeviceStrategy&&(this.emit({type:"deviceStrategyChanged",timestamp:Date.now(),newStrategy:e.pointerType,oldStrategy:this.currentDeviceStrategy}),this.setDeviceStrategy(this.currentDeviceStrategy=e.pointerType)),this.pendingPointerEvent=e,!this.rafId&&(this.rafId=requestAnimationFrame(()=>{if(this.handler instanceof f){this.rafId=null;return}this.pendingPointerEvent&&this.handler.processMouseMovement(this.pendingPointerEvent),this.rafId=null}))};this.handleDomMutations=e=>{e.length&&this.desktopHandler?.invalidateTabCache();for(let t of e)if(t.type==="childList"&&t.removedNodes.length>0)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};e!==void 0&&this.initializeManagerSettings(e);let t={elements:this.elements,callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),settings:this._globalSettings};this.desktopHandler=new m(t),this.touchDeviceHandler=new f(t),this.handler=this.currentDeviceStrategy==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.initializeGlobalListeners()}generateId(){return`foresight-${++this.idCounter}`}static initialize(e){return this.isInitiated||(o.manager=new o(e)),o.manager}addEventListener(e,t,i){if(i?.signal?.aborted)return()=>{};let n=this.eventListeners.get(e)??[];n.push(t),this.eventListeners.set(e,n),i?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let i=this.eventListeners.get(e);if(!i)return;let n=i.indexOf(t);n>-1&&i.splice(n,1)}emit(e){let t=this.eventListeners.get(e.type)?.slice();if(t)for(let i=0;i<t.length;i++)try{t[i](e)}catch(n){console.error(`Error in ForesightManager event listener ${i} for ${e.type}:`,n)}}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners,currentDeviceStrategy:this.currentDeviceStrategy}}static get isInitiated(){return!!o.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:e,callback:t,hitSlop:i,name:n,meta:r,reactivateAfter:l}){let{isTouchDevice:s,isLimitedConnection:a}=K(),c=this.elements.get(e);if(c)return c.registerCount++,{isLimitedConnection:a,isTouchDevice:s,isRegistered:!1,unregister:()=>{}};let u=e.getBoundingClientRect(),p=i?P(i):this._globalSettings.defaultHitSlop,v={id:this.generateId(),element:e,callback:t,elementBounds:{originalRect:u,expandedRect:S(u,p),hitSlop:p},trajectoryHitData:{isTrajectoryHit:!1,trajectoryHitTime:0,trajectoryHitExpirationTimeoutId:void 0},name:n||e.id||"unnamed",isIntersectingWithViewport:z(u),registerCount:1,meta:r??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:l??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}};return this.elements.set(e,v),this.handler.observeElement(e),this.emit({type:"elementRegistered",timestamp:Date.now(),elementData:v}),{isTouchDevice:s,isLimitedConnection:a,isRegistered:!0,unregister:()=>{}}}unregister(e,t){let i=this.elements.get(e);if(!i)return;i?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(i.trajectoryHitData.trajectoryHitExpirationTimeoutId),i?.callbackInfo.reactivateTimeoutId&&clearTimeout(i.callbackInfo.reactivateTimeoutId),this.handler.unobserveElement(e),this.elements.delete(e);let n=this.elements.size===0&&this.isSetup;i&&this.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:t??"by user",wasLastElement:n})}updateHitCounters(e){switch(e.kind){case"mouse":this._globalCallbackHits.mouse[e.subType]++;break;case"tab":this._globalCallbackHits.tab[e.subType]++;break;case"scroll":this._globalCallbackHits.scroll[e.subType]++;break;case"touch":this._globalCallbackHits.touch++;break;case"viewport":this._globalCallbackHits.viewport++;break;default:}this._globalCallbackHits.total++}reactivate(e){let t=this.elements.get(e);t&&(t.callbackInfo.reactivateTimeoutId&&(clearTimeout(t.callbackInfo.reactivateTimeoutId),t.callbackInfo.reactivateTimeoutId=void 0),t.callbackInfo.isRunningCallback||(t.callbackInfo.isCallbackActive=!0,this.handler.observeElement(e),this.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()})))}makeElementUnactive(e){e.callbackInfo.callbackFiredCount++,e.callbackInfo.lastCallbackInvokedAt=Date.now(),e.callbackInfo.isRunningCallback=!0,e.callbackInfo.reactivateTimeoutId&&(clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0),e?.trajectoryHitData.trajectoryHitExpirationTimeoutId&&clearTimeout(e.trajectoryHitData.trajectoryHitExpirationTimeoutId)}callCallback(e,t){if(e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive)return;this.makeElementUnactive(e),(async()=>{this.updateHitCounters(t),this.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let n=performance.now(),r,l=null;try{await e.callback(),r="success"}catch(s){l=s instanceof Error?s.message:String(s),r="error",console.error(`Error in callback for element ${e.name} (${e.element.tagName}):`,s)}e.callbackInfo.lastCallbackCompletedAt=Date.now(),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:e.callbackInfo.lastCallbackRuntime=performance.now()-n,status:e.callbackInfo.lastCallbackStatus=r,errorMessage:e.callbackInfo.lastCallbackErrorMessage=l}),e.callbackInfo.isRunningCallback=!1,e.callbackInfo.isCallbackActive=!1,this.handler.unobserveElement(e.element),e.callbackInfo.reactivateAfter!==1/0&&e.callbackInfo.reactivateAfter>0&&(e.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.callbackInfo.reactivateAfter))})()}setDeviceStrategy(e){this.handler.disconnect(),this.handler=e==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.handler.connect()}initializeGlobalListeners(){this.isSetup||(this.setDeviceStrategy(this.currentDeviceStrategy),document.addEventListener("pointermove",this.handlePointerMove),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.isSetup=!0)}removeGlobalListeners(){this.isSetup=!1,this.domObserver?.disconnect(),this.domObserver=null,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingPointerEvent=null}forceUpdateAllElementBounds(){for(let[,e]of this.elements)e.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),i=S(t,e.elementBounds.hitSlop);if(!H(i,e.elementBounds.expandedRect)){let n={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:i}};this.elements.set(e.element,n),this.emit({type:"elementDataUpdated",elementData:n,updatedProps:["bounds"]})}}initializeManagerSettings(e){this.updateNumericSettings(e.trajectoryPredictionTime,"trajectoryPredictionTime",10,200),this.updateNumericSettings(e.positionHistorySize,"positionHistorySize",2,30),this.updateNumericSettings(e.scrollMargin,"scrollMargin",30,300),this.updateNumericSettings(e.tabOffset,"tabOffset",0,20),this.updateBooleanSetting(e.enableMousePrediction,"enableMousePrediction"),this.updateBooleanSetting(e.enableScrollPrediction,"enableScrollPrediction"),this.updateBooleanSetting(e.enableTabPrediction,"enableTabPrediction"),e.defaultHitSlop!==void 0&&(this._globalSettings.defaultHitSlop=P(e.defaultHitSlop)),e.touchDeviceStrategy!==void 0&&(this._globalSettings.touchDeviceStrategy=e.touchDeviceStrategy),e.debug!==void 0&&(this._globalSettings.debug=e.debug),console.log(this._globalSettings)}updateNumericSettings(e,t,i,n){return A(e,this._globalSettings[t])?(this._globalSettings[t]=h(e,i,n,t),!0):!1}updateBooleanSetting(e,t){return A(e,this._globalSettings[t])?(this._globalSettings[t]=e,!0):!1}alterGlobalSettings(e){let t=[],i=this._globalSettings.trajectoryPredictionTime;this.updateNumericSettings(e?.trajectoryPredictionTime,"trajectoryPredictionTime",10,200)&&t.push({setting:"trajectoryPredictionTime",oldValue:i,newValue:this._globalSettings.trajectoryPredictionTime});let r=this._globalSettings.positionHistorySize;this.updateNumericSettings(e?.positionHistorySize,"positionHistorySize",2,30)&&(t.push({setting:"positionHistorySize",oldValue:r,newValue:this._globalSettings.positionHistorySize}),this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize));let s=this._globalSettings.scrollMargin;if(this.updateNumericSettings(e?.scrollMargin,"scrollMargin",30,300)){let g=this._globalSettings.scrollMargin;t.push({setting:"scrollMargin",oldValue:s,newValue:g})}let c=this._globalSettings.tabOffset;this.updateNumericSettings(e?.tabOffset,"tabOffset",0,20)&&(this._globalSettings.tabOffset=h(e.tabOffset,0,20,"tabOffset"),t.push({setting:"tabOffset",oldValue:c,newValue:this._globalSettings.tabOffset}));let p=this._globalSettings.enableMousePrediction;this.updateBooleanSetting(e?.enableMousePrediction,"enableMousePrediction")&&t.push({setting:"enableMousePrediction",oldValue:p,newValue:this._globalSettings.enableMousePrediction});let x=this._globalSettings.enableScrollPrediction;this.updateBooleanSetting(e?.enableScrollPrediction,"enableScrollPrediction")&&(this.handler instanceof m&&(this._globalSettings.enableScrollPrediction?this.handler.connectScrollPredictor():this.handler.disconnectScrollPredictor()),t.push({setting:"enableScrollPrediction",oldValue:x,newValue:this._globalSettings.enableScrollPrediction}));let w=this._globalSettings.enableTabPrediction;if(this.updateBooleanSetting(e?.enableTabPrediction,"enableTabPrediction")&&(this.handler instanceof m&&(this._globalSettings.enableTabPrediction?this.handler.connectTabPredictor():this.handler.disconnectTabPredictor()),t.push({setting:"enableTabPrediction",oldValue:w,newValue:this._globalSettings.enableTabPrediction})),e?.defaultHitSlop!==void 0){let g=this._globalSettings.defaultHitSlop,b=P(e.defaultHitSlop);H(g,b)||(this._globalSettings.defaultHitSlop=b,t.push({setting:"defaultHitSlop",oldValue:g,newValue:b}),this.forceUpdateAllElementBounds())}if(e?.touchDeviceStrategy!==void 0){let g=this._globalSettings.touchDeviceStrategy,b=e.touchDeviceStrategy;this._globalSettings.touchDeviceStrategy=b,t.push({setting:"touchDeviceStrategy",oldValue:g,newValue:b}),this.handler instanceof f&&this.handler.setTouchPredictor()}t.length>0&&this.emit({type:"managerSettingsChanged",timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:t})}};export{B as ForesightManager};
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "js.foresight",
3
- "version": "3.2.0",
3
+ "version": "3.3.0-beta.1",
4
4
  "description": "Predicts mouse trajectory to trigger actions as users approach elements, enabling anticipatory UI updates or pre-loading. Made with vanilla javascript and usable in every framework.",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "build": "tsup",
8
- "dev": "tsup --watch",
7
+ "build": "tsup --sourcemap",
8
+ "build:prod": "tsup",
9
+ "dev": "tsup --sourcemap --watch",
9
10
  "test": "vitest",
10
11
  "test:watch": "vitest --watch",
11
12
  "test:ui": "vitest --ui",
12
13
  "test:coverage": "vitest --coverage",
13
- "test:run": "vitest run"
14
+ "test:run": "vitest run",
15
+ "prepublishOnly": "pnpm test:run && pnpm build:prod"
14
16
  },
15
17
  "main": "./dist/index.js",
16
18
  "module": "./dist/index.js",
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
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"]}