js.foresight 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/BaseForesightModule-CJzWVlF2.mjs +2 -0
  2. package/dist/BaseForesightModule-CJzWVlF2.mjs.map +1 -0
  3. package/dist/DesktopHandler-CR9nLuzu.mjs +2 -0
  4. package/dist/DesktopHandler-CR9nLuzu.mjs.map +1 -0
  5. package/dist/ElementObservingModule-QE8ZBcnz.mjs +2 -0
  6. package/dist/{ElementObservingModule-OqTcnagr.mjs.map → ElementObservingModule-QE8ZBcnz.mjs.map} +1 -1
  7. package/dist/ScrollPredictor-ZHRqX0lh.mjs +2 -0
  8. package/dist/{ScrollPredictor-r4YnqcfQ.mjs.map → ScrollPredictor-ZHRqX0lh.mjs.map} +1 -1
  9. package/dist/{TabPredictor-CohoaRM5.mjs → TabPredictor-C2zyaEQZ.mjs} +2 -2
  10. package/dist/{TabPredictor-CohoaRM5.mjs.map → TabPredictor-C2zyaEQZ.mjs.map} +1 -1
  11. package/dist/{TouchDeviceHandler-CKhsCqLJ.mjs → TouchDeviceHandler-BPzPdr-z.mjs} +2 -2
  12. package/dist/{TouchDeviceHandler-CKhsCqLJ.mjs.map → TouchDeviceHandler-BPzPdr-z.mjs.map} +1 -1
  13. package/dist/{TouchStartPredictor-Dqk28kjI.mjs → TouchStartPredictor-r7R_E74t.mjs} +2 -2
  14. package/dist/{TouchStartPredictor-Dqk28kjI.mjs.map → TouchStartPredictor-r7R_E74t.mjs.map} +1 -1
  15. package/dist/{ViewportPredictor-BOtudr8f.mjs → ViewportPredictor-aTE2EmU2.mjs} +2 -2
  16. package/dist/{ViewportPredictor-BOtudr8f.mjs.map → ViewportPredictor-aTE2EmU2.mjs.map} +1 -1
  17. package/dist/index.d.mts +50 -12
  18. package/dist/index.d.mts.map +1 -1
  19. package/dist/index.mjs +1 -1
  20. package/dist/index.mjs.map +1 -1
  21. package/package.json +1 -1
  22. package/dist/BaseForesightModule-BxWJ6mzK.mjs +0 -2
  23. package/dist/BaseForesightModule-BxWJ6mzK.mjs.map +0 -1
  24. package/dist/DesktopHandler-BgCQieJ9.mjs +0 -2
  25. package/dist/DesktopHandler-BgCQieJ9.mjs.map +0 -1
  26. package/dist/ElementObservingModule-OqTcnagr.mjs +0 -2
  27. package/dist/ScrollPredictor-r4YnqcfQ.mjs +0 -2
@@ -0,0 +1,2 @@
1
+ var e=class{get isConnected(){return this._isConnected}constructor(e){this._isConnected=!1,this._cachedLogStyle=null,this.elements=e.elements,this.callCallback=e.callCallback,this.emit=e.emit,this.hasListeners=e.hasListeners,this.updateElementState=e.updateElementState,this.updateElementBounds=e.updateElementBounds,this.settings=e.settings}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.isConnected||(this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0)}devLog(e){this.settings.enableManagerLogging&&(this._cachedLogStyle===null&&(this._cachedLogStyle=`color: ${this.moduleName.includes(`Predictor`)?`#ea580c`:`#2563eb`}; font-weight: bold;`),console.log(`%c${this.moduleName}: ${e}`,this._cachedLogStyle))}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};export{e as t};
2
+ //# sourceMappingURL=BaseForesightModule-CJzWVlF2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseForesightModule-CJzWVlF2.mjs","names":[],"sources":["../src/core/BaseForesightModule.ts"],"sourcesContent":["import type {\n CallbackHitType,\n ElementBounds,\n ForesightElement,\n ForesightElementInternal,\n ForesightElementState,\n ForesightEvent,\n ForesightEventMap,\n ForesightManagerSettings,\n} from \"../types/types\"\n\ntype CallCallbackFunction = (\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType\n) => void\n\ntype EmitFunction = <K extends ForesightEvent>(event: ForesightEventMap[K]) => void\n\nexport type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean\n\ntype UpdateElementStateFunction = (\n entry: ForesightElementInternal,\n patch: Partial<ForesightElementState>\n) => ForesightElementState\n\ntype UpdateElementBoundsFunction = (\n entry: ForesightElementInternal,\n next: ElementBounds\n) => ElementBounds\n\nexport type ForesightModuleDependencies = {\n elements: ReadonlyMap<ForesightElement, ForesightElementInternal>\n callCallback: CallCallbackFunction\n emit: EmitFunction\n hasListeners: HasListenersFunction\n updateElementState: UpdateElementStateFunction\n updateElementBounds: UpdateElementBoundsFunction\n settings: ForesightManagerSettings\n}\n\nexport abstract class BaseForesightModule {\n protected abortController?: AbortController\n protected elements: ReadonlyMap<ForesightElement, ForesightElementInternal>\n protected callCallback: CallCallbackFunction\n protected emit: EmitFunction\n protected hasListeners: HasListenersFunction\n protected updateElementState: UpdateElementStateFunction\n protected updateElementBounds: UpdateElementBoundsFunction\n protected settings: ForesightManagerSettings\n private _isConnected = false\n private _cachedLogStyle: string | null = null\n\n public get isConnected(): boolean {\n return this._isConnected\n }\n\n protected abstract readonly moduleName: string // Name of the implementor class for debugging purposes\n\n constructor(dependencies: ForesightModuleDependencies) {\n this.elements = dependencies.elements\n this.callCallback = dependencies.callCallback\n this.emit = dependencies.emit\n this.hasListeners = dependencies.hasListeners\n this.updateElementState = dependencies.updateElementState\n this.updateElementBounds = dependencies.updateElementBounds\n this.settings = dependencies.settings\n }\n\n public disconnect(): void {\n if (!this.isConnected) {\n return\n }\n\n this.devLog(`Disconnecting ${this.moduleName}...`)\n this.abortController?.abort(`${this.moduleName} module disconnected`)\n this.onDisconnect()\n this._isConnected = false\n }\n\n public connect(): void {\n if (this.isConnected) {\n return\n }\n\n this.devLog(`Connecting ${this.moduleName}...`)\n this.onConnect()\n this._isConnected = true\n }\n\n public devLog(message: string): void {\n if (!this.settings.enableManagerLogging) {\n return\n }\n\n // Cache the log style on first use to avoid repeated string operations\n if (this._cachedLogStyle === null) {\n const color = this.moduleName.includes(\"Predictor\") ? \"#ea580c\" : \"#2563eb\"\n this._cachedLogStyle = `color: ${color}; font-weight: bold;`\n }\n\n console.log(`%c${this.moduleName}: ${message}`, this._cachedLogStyle)\n }\n\n protected abstract onConnect(): void\n protected abstract onDisconnect(): void\n\n protected createAbortController(): void {\n if (this.abortController && !this.abortController.signal.aborted) {\n return\n }\n\n this.abortController = new AbortController()\n\n this.devLog(`Created new AbortController for ${this.moduleName}`)\n }\n}\n"],"mappings":"AAwCA,IAAsB,EAAtB,KAA0C,CAYxC,IAAW,aAAuB,CAChC,OAAO,KAAK,aAKd,YAAY,EAA2C,mBAThC,wBACkB,KASvC,KAAK,SAAW,EAAa,SAC7B,KAAK,aAAe,EAAa,aACjC,KAAK,KAAO,EAAa,KACzB,KAAK,aAAe,EAAa,aACjC,KAAK,mBAAqB,EAAa,mBACvC,KAAK,oBAAsB,EAAa,oBACxC,KAAK,SAAW,EAAa,SAG/B,YAA0B,CACnB,KAAK,cAIV,KAAK,OAAO,iBAAiB,KAAK,WAAW,KAAK,CAClD,KAAK,iBAAiB,MAAM,GAAG,KAAK,WAAW,sBAAsB,CACrE,KAAK,cAAc,CACnB,KAAK,aAAe,IAGtB,SAAuB,CACjB,KAAK,cAIT,KAAK,OAAO,cAAc,KAAK,WAAW,KAAK,CAC/C,KAAK,WAAW,CAChB,KAAK,aAAe,IAGtB,OAAc,EAAuB,CAC9B,KAAK,SAAS,uBAKf,KAAK,kBAAoB,OAE3B,KAAK,gBAAkB,UADT,KAAK,WAAW,SAAS,YAAY,CAAG,UAAY,UAC3B,uBAGzC,QAAQ,IAAI,KAAK,KAAK,WAAW,IAAI,IAAW,KAAK,gBAAgB,EAMvE,uBAAwC,CAClC,KAAK,iBAAmB,CAAC,KAAK,gBAAgB,OAAO,UAIzD,KAAK,gBAAkB,IAAI,gBAE3B,KAAK,OAAO,mCAAmC,KAAK,aAAa"}
@@ -0,0 +1,2 @@
1
+ import{n as e,r as t,t as n}from"./rectAndHitSlop-T7Z3PZlb.mjs";import{t as r}from"./BaseForesightModule-CJzWVlF2.mjs";import{t as i}from"./ElementObservingModule-QE8ZBcnz.mjs";import{t as a}from"./lineSegmentIntersectsRect-x9nicliA.mjs";import{PositionObserver as o}from"position-observer";const s=(e,t,n,r)=>{let i=performance.now();t.add({point:{x:e.x,y:e.y},time:i});let{x:a,y:o}=e;if(t.length<2){r.x=a,r.y=o;return}let[s,c]=t.getFirstLast();if(!s||!c){r.x=a,r.y=o;return}let l=(c.time-s.time)*.001;if(l===0){r.x=a,r.y=o;return}let u=c.point.x-s.point.x,d=c.point.y-s.point.y,f=u/l,p=d/l,m=n*.001;r.x=a+f*m,r.y=o+p*m};var c=class extends r{constructor(e){super(e.dependencies),this.moduleName=`MousePredictor`,this.trajectoryPositions=e.trajectoryPositions,this.mouseTrajectoryEvent={type:`mouseTrajectoryUpdate`,predictionEnabled:!1,trajectoryPositions:this.trajectoryPositions}}updatePointerState(e){let t=this.trajectoryPositions.currentPoint;t.x=e.clientX,t.y=e.clientY,this.settings.enableMousePrediction?s(t,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime,this.trajectoryPositions.predictedPoint):(this.trajectoryPositions.predictedPoint.x=t.x,this.trajectoryPositions.predictedPoint.y=t.y)}processMouseMovement(e){this.updatePointerState(e);let n=this.settings.enableMousePrediction,r=this.trajectoryPositions.currentPoint;for(let e of this.elements.values()){let i=e.state;if(!i.isIntersectingWithViewport||!i.isActive||i.isPredicted)continue;let o=e.bounds.expandedRect;if(n)a(r,this.trajectoryPositions.predictedPoint,o)&&this.callCallback(e,{kind:`mouse`,subType:`trajectory`});else if(t(r,o)){this.callCallback(e,{kind:`mouse`,subType:`hover`});continue}}this.hasListeners(`mouseTrajectoryUpdate`)&&(this.mouseTrajectoryEvent.predictionEnabled=n,this.emit(this.mouseTrajectoryEvent))}onDisconnect(){}onConnect(){}},l=class{constructor(e){if(this.head=0,this.count=0,e<=0)throw Error(`CircularBuffer capacity must be greater than 0`);this.capacity=e,this.buffer=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 e=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[e,e]}return[this.getFirst(),this.getLast()]}resize(e){if(e<=0)throw Error(`CircularBuffer capacity must be greater than 0`);if(e===this.capacity)return;let t=this.getAllItems();if(this.capacity=e,this.buffer=Array(e),this.head=0,this.count=0,t.length>e){let n=t.slice(-e);for(let e of n)this.add(e)}else for(let e of t)this.add(e)}getAllItems(){if(this.count===0)return[];let e=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 n=0;n<this.capacity;n++){let r=(t+n)%this.capacity;e[n]=this.buffer[r]}}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}},u=class extends i{constructor(r){super(r),this.moduleName=`DesktopHandler`,this.tabPredictor=null,this.scrollPredictor=null,this.positionObserver=null,this.trajectoryPositions={positions:new l(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}},this.handlePositionChange=e=>{let t=this.settings.enableScrollPrediction;for(let n of e){let e=this.elements.get(n.target);e&&(t?this.scrollPredictor?.handleScrollPrefetch(e,n.boundingClientRect):this.checkForMouseHover(e),this.handlePositionChangeDataUpdates(e,n))}t&&(this.scrollPredictor?.emitTrajectoryUpdate(),this.scrollPredictor?.resetScrollProps())},this.checkForMouseHover=e=>{t(this.trajectoryPositions.currentPoint,e.bounds.expandedRect)&&this.callCallback(e,{kind:`mouse`,subType:`hover`})},this.handlePositionChangeDataUpdates=(t,r)=>{let i=r.isIntersecting;i&&!n(r.boundingClientRect,t.bounds.originalRect)&&this.updateElementBounds(t,{originalRect:r.boundingClientRect,expandedRect:e(r.boundingClientRect,t.state.hitSlop)}),t.state.isIntersectingWithViewport!==i&&this.updateElementState(t,{isIntersectingWithViewport:i})},this.processMouseMovement=e=>this.mousePredictor.processMouseMovement(e),this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache(),this.connectTabPredictor=async()=>{if(!this.tabPredictor){let{TabPredictor:e}=await import(`./TabPredictor-C2zyaEQZ.mjs`);this.tabPredictor=new e(this.storedDependencies),this.devLog(`TabPredictor lazy loaded`)}this.tabPredictor.connect()},this.connectScrollPredictor=async()=>{if(!this.scrollPredictor){let{ScrollPredictor:e}=await import(`./ScrollPredictor-ZHRqX0lh.mjs`);this.scrollPredictor=new e({dependencies:this.storedDependencies,trajectoryPositions:this.trajectoryPositions}),this.devLog(`ScrollPredictor lazy loaded`)}this.scrollPredictor.connect()},this.connectMousePredictor=()=>this.mousePredictor.connect(),this.disconnectTabPredictor=()=>this.tabPredictor?.disconnect(),this.disconnectScrollPredictor=()=>this.scrollPredictor?.disconnect(),this.disconnectMousePredictor=()=>this.mousePredictor.disconnect(),this.storedDependencies=r,this.mousePredictor=new c({dependencies:r,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new o(this.handlePositionChange);let e=[`mouse`];this.settings.enableTabPrediction&&e.push(`tab (loading...)`),this.settings.enableScrollPrediction&&e.push(`scroll (loading...)`),this.devLog(`Connected predictors: [${e.join(`, `)}] and PositionObserver`);for(let e of this.elements.keys())this.positionObserver.observe(e)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}observeElement(e){this.positionObserver?.observe(e)}unobserveElement(e){this.positionObserver?.unobserve(e)}get loadedPredictors(){return{mouse:this.mousePredictor!==null,tab:this.tabPredictor!==null,scroll:this.scrollPredictor!==null}}};export{u as DesktopHandler};
2
+ //# sourceMappingURL=DesktopHandler-CR9nLuzu.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DesktopHandler-CR9nLuzu.mjs","names":[],"sources":["../src/helpers/predictNextMousePosition.ts","../src/predictors/MousePredictor.ts","../src/helpers/CircularBuffer.ts","../src/managers/DesktopHandler.ts"],"sourcesContent":["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 * @param out - Output point to mutate with the predicted position.\n */\nexport const predictNextMousePosition = (\n currentPoint: Point,\n buffer: CircularBuffer<MousePosition>,\n trajectoryPredictionTimeInMs: number,\n out: Point\n): void => {\n const now = performance.now()\n // Create a copy of currentPoint for buffer storage (buffer needs independent copies)\n buffer.add({ point: { x: currentPoint.x, y: currentPoint.y }, time: now })\n\n const { x, y } = currentPoint\n\n if (buffer.length < 2) {\n out.x = x\n out.y = y\n\n return\n }\n\n const [first, last] = buffer.getFirstLast()\n if (!first || !last) {\n out.x = x\n out.y = y\n\n return\n }\n\n const dt = (last.time - first.time) * 0.001\n if (dt === 0) {\n out.x = x\n out.y = y\n\n return\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 out.x = x + vx * trajectoryPredictionTimeInSeconds\n out.y = y + vy * trajectoryPredictionTimeInSeconds\n}\n","import { lineSegmentIntersectsRect } from \"../helpers/lineSegmentIntersectsRect\"\nimport { predictNextMousePosition } from \"../helpers/predictNextMousePosition\"\nimport { isPointInRectangle } from \"../helpers/rectAndHitSlop\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport type { MouseTrajectoryUpdateEvent, TrajectoryPositions } from \"../types/types\"\n\ninterface MousePredictorConfig {\n dependencies: ForesightModuleDependencies\n trajectoryPositions: TrajectoryPositions\n}\n\nexport class MousePredictor extends BaseForesightModule {\n protected readonly moduleName = \"MousePredictor\"\n\n private trajectoryPositions: TrajectoryPositions\n\n // Pre-allocated event object to avoid creating a new object every frame (~60/sec)\n private readonly mouseTrajectoryEvent: MouseTrajectoryUpdateEvent\n\n constructor(config: MousePredictorConfig) {\n super(config.dependencies)\n\n this.trajectoryPositions = config.trajectoryPositions\n this.mouseTrajectoryEvent = {\n type: \"mouseTrajectoryUpdate\",\n predictionEnabled: false,\n trajectoryPositions: this.trajectoryPositions,\n }\n }\n\n private updatePointerState(e: MouseEvent): void {\n // Mutate existing point objects instead of creating new ones (perf optimization)\n const currentPoint = this.trajectoryPositions.currentPoint\n currentPoint.x = e.clientX\n currentPoint.y = e.clientY\n\n if (this.settings.enableMousePrediction) {\n predictNextMousePosition(\n currentPoint,\n this.trajectoryPositions.positions,\n this.settings.trajectoryPredictionTime,\n this.trajectoryPositions.predictedPoint // reuse existing object\n )\n } else {\n this.trajectoryPositions.predictedPoint.x = currentPoint.x\n this.trajectoryPositions.predictedPoint.y = currentPoint.y\n }\n }\n\n public processMouseMovement(e: MouseEvent): void {\n this.updatePointerState(e)\n const enablePrediction = this.settings.enableMousePrediction\n const currentPoint = this.trajectoryPositions.currentPoint\n\n for (const internal of this.elements.values()) {\n const state = internal.state\n if (!state.isIntersectingWithViewport || !state.isActive || state.isPredicted) {\n continue\n }\n\n const expandedRect = internal.bounds.expandedRect\n\n if (!enablePrediction) {\n if (isPointInRectangle(currentPoint, expandedRect)) {\n this.callCallback(internal, { kind: \"mouse\", subType: \"hover\" })\n\n continue\n }\n\n // when enable mouse prediction is off, we only check if the mouse is physically hovering over the element\n } else if (\n lineSegmentIntersectsRect(\n currentPoint,\n this.trajectoryPositions.predictedPoint,\n expandedRect\n )\n ) {\n this.callCallback(internal, { kind: \"mouse\", subType: \"trajectory\" })\n }\n }\n\n if (this.hasListeners(\"mouseTrajectoryUpdate\")) {\n this.mouseTrajectoryEvent.predictionEnabled = enablePrediction\n this.emit(this.mouseTrajectoryEvent)\n }\n }\n\n protected onDisconnect(): void {}\n protected onConnect(): void {}\n}\n","export class CircularBuffer<T> {\n private buffer: T[]\n private head: number = 0\n private count: number = 0\n private capacity: number\n\n constructor(capacity: number) {\n if (capacity <= 0) {\n throw new Error(\"CircularBuffer capacity must be greater than 0\")\n }\n\n this.capacity = capacity\n this.buffer = new Array(capacity)\n }\n\n add(item: T): void {\n this.buffer[this.head] = item\n this.head = (this.head + 1) % this.capacity\n\n if (this.count < this.capacity) {\n this.count++\n }\n }\n\n getFirst(): T | undefined {\n if (this.count === 0) {\n return undefined\n }\n\n if (this.count < this.capacity) {\n return this.buffer[0]\n } else {\n return this.buffer[this.head]\n }\n }\n\n getLast(): T | undefined {\n if (this.count === 0) {\n return undefined\n }\n\n if (this.count < this.capacity) {\n return this.buffer[this.count - 1]\n } else {\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity\n\n return this.buffer[lastIndex]\n }\n }\n\n getFirstLast(): [T | undefined, T | undefined] {\n if (this.count === 0) {\n return [undefined, undefined]\n }\n\n if (this.count === 1) {\n const item = this.count < this.capacity ? this.buffer[0] : this.buffer[this.head]\n\n return [item, item]\n }\n\n const first = this.getFirst()\n const last = this.getLast()\n\n return [first, last]\n }\n\n resize(newCapacity: number): void {\n if (newCapacity <= 0) {\n throw new Error(\"CircularBuffer capacity must be greater than 0\")\n }\n\n if (newCapacity === this.capacity) {\n return\n }\n\n const currentItems = this.getAllItems()\n this.capacity = newCapacity\n this.buffer = new Array(newCapacity)\n this.head = 0\n this.count = 0\n\n if (currentItems.length > newCapacity) {\n const itemsToKeep = currentItems.slice(-newCapacity)\n for (const item of itemsToKeep) {\n this.add(item)\n }\n } else {\n for (const item of currentItems) {\n this.add(item)\n }\n }\n }\n\n private getAllItems(): T[] {\n if (this.count === 0) {\n return []\n }\n\n const result: T[] = new Array(this.count)\n\n if (this.count < this.capacity) {\n for (let i = 0; i < this.count; i++) {\n result[i] = this.buffer[i]\n }\n } else {\n const startIndex = this.head\n for (let i = 0; i < this.capacity; i++) {\n const bufferIndex = (startIndex + i) % this.capacity\n result[i] = this.buffer[bufferIndex]\n }\n }\n\n return result\n }\n\n clear(): void {\n this.head = 0\n this.count = 0\n }\n\n get length(): number {\n return this.count\n }\n\n get size(): number {\n return this.capacity\n }\n\n get isFull(): boolean {\n return this.count === this.capacity\n }\n\n get isEmpty(): boolean {\n return this.count === 0\n }\n}\n","import type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport { MousePredictor } from \"../predictors/MousePredictor\"\nimport type { ScrollPredictor } from \"../predictors/ScrollPredictor\"\nimport type { TabPredictor } from \"../predictors/TabPredictor\"\nimport { PositionObserver, PositionObserverEntry } from \"position-observer\"\nimport type {\n ForesightElement,\n ForesightElementInternal,\n TrajectoryPositions,\n} from \"../types/types\"\nimport { CircularBuffer } from \"../helpers/CircularBuffer\"\nimport { DEFAULT_POSITION_HISTORY_SIZE } from \"../constants\"\nimport { areRectsEqual, getExpandedRect, isPointInRectangle } from \"../helpers/rectAndHitSlop\"\n\nexport class DesktopHandler extends ElementObservingModule {\n protected readonly moduleName = \"DesktopHandler\"\n\n private mousePredictor: MousePredictor\n private tabPredictor: TabPredictor | null = null\n private scrollPredictor: ScrollPredictor | null = null\n private positionObserver: PositionObserver | null = null\n private storedDependencies: ForesightModuleDependencies\n\n public trajectoryPositions: TrajectoryPositions = {\n positions: new CircularBuffer(DEFAULT_POSITION_HISTORY_SIZE),\n currentPoint: { x: 0, y: 0 },\n predictedPoint: { x: 0, y: 0 },\n }\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n this.storedDependencies = dependencies\n\n // Only MousePredictor is instantiated immediately - Tab and Scroll are lazy-loaded\n this.mousePredictor = new MousePredictor({\n dependencies,\n trajectoryPositions: this.trajectoryPositions,\n })\n }\n\n protected onConnect(): void {\n if (this.settings.enableTabPrediction) {\n this.connectTabPredictor()\n }\n\n if (this.settings.enableScrollPrediction) {\n this.connectScrollPredictor()\n }\n\n this.connectMousePredictor() // We always connect the mouse predictor\n this.positionObserver = new PositionObserver(this.handlePositionChange)\n\n const enabledPredictors = [\"mouse\"]\n\n if (this.settings.enableTabPrediction) {\n enabledPredictors.push(\"tab (loading...)\")\n }\n\n if (this.settings.enableScrollPrediction) {\n enabledPredictors.push(\"scroll (loading...)\")\n }\n\n this.devLog(`Connected predictors: [${enabledPredictors.join(\", \")}] and PositionObserver`)\n\n for (const element of this.elements.keys()) {\n this.positionObserver.observe(element)\n }\n }\n\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\n const enableScrollPosition = this.settings.enableScrollPrediction\n\n for (const positionEntry of entries) {\n const entry = this.elements.get(positionEntry.target)\n\n if (!entry) {\n continue\n }\n\n if (enableScrollPosition) {\n this.scrollPredictor?.handleScrollPrefetch(entry, positionEntry.boundingClientRect)\n } else {\n // If we dont check for scroll prediction, check if the user is hovering over the element during a scroll instead\n this.checkForMouseHover(entry)\n }\n\n // Must run AFTER handleScrollPrefetch/checkForMouseHover - scroll\n // direction is derived from the difference between the old and new\n // originalRect, so entry.bounds must not be updated before they read it.\n this.handlePositionChangeDataUpdates(entry, positionEntry)\n }\n\n if (enableScrollPosition) {\n this.scrollPredictor?.emitTrajectoryUpdate()\n this.scrollPredictor?.resetScrollProps()\n }\n }\n\n private checkForMouseHover = (entry: ForesightElementInternal) => {\n if (isPointInRectangle(this.trajectoryPositions.currentPoint, entry.bounds.expandedRect)) {\n this.callCallback(entry, {\n kind: \"mouse\",\n subType: \"hover\",\n })\n }\n }\n\n private handlePositionChangeDataUpdates = (\n entry: ForesightElementInternal,\n positionEntry: PositionObserverEntry\n ) => {\n const isNowIntersecting = positionEntry.isIntersecting\n\n // Bounds are updated BEFORE the state patch so state subscribers always\n // read fresh geometry.\n if (\n isNowIntersecting &&\n !areRectsEqual(positionEntry.boundingClientRect, entry.bounds.originalRect)\n ) {\n this.updateElementBounds(entry, {\n originalRect: positionEntry.boundingClientRect,\n expandedRect: getExpandedRect(positionEntry.boundingClientRect, entry.state.hitSlop),\n })\n }\n\n if (entry.state.isIntersectingWithViewport !== isNowIntersecting) {\n this.updateElementState(entry, { isIntersectingWithViewport: isNowIntersecting })\n }\n }\n\n protected onDisconnect(): void {\n this.disconnectMousePredictor()\n this.disconnectTabPredictor()\n this.disconnectScrollPredictor()\n this.positionObserver?.disconnect()\n this.positionObserver = null\n }\n\n public processMouseMovement = (event: PointerEvent) =>\n this.mousePredictor.processMouseMovement(event)\n\n public invalidateTabCache = () => this.tabPredictor?.invalidateCache()\n\n public observeElement(element: ForesightElement): void {\n this.positionObserver?.observe(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.positionObserver?.unobserve(element)\n }\n\n public connectTabPredictor = async () => {\n if (!this.tabPredictor) {\n const { TabPredictor } = await import(\"../predictors/TabPredictor\")\n this.tabPredictor = new TabPredictor(this.storedDependencies)\n this.devLog(\"TabPredictor lazy loaded\")\n }\n\n this.tabPredictor.connect()\n }\n\n public connectScrollPredictor = async () => {\n if (!this.scrollPredictor) {\n const { ScrollPredictor } = await import(\"../predictors/ScrollPredictor\")\n this.scrollPredictor = new ScrollPredictor({\n dependencies: this.storedDependencies,\n trajectoryPositions: this.trajectoryPositions,\n })\n\n this.devLog(\"ScrollPredictor lazy loaded\")\n }\n\n this.scrollPredictor.connect()\n }\n\n public connectMousePredictor = () => this.mousePredictor.connect()\n\n public disconnectTabPredictor = () => this.tabPredictor?.disconnect()\n public disconnectScrollPredictor = () => this.scrollPredictor?.disconnect()\n public disconnectMousePredictor = () => this.mousePredictor.disconnect()\n\n /** For debugging: returns which predictors have been lazy loaded */\n public get loadedPredictors() {\n return {\n mouse: this.mousePredictor !== null,\n tab: this.tabPredictor !== null,\n scroll: this.scrollPredictor !== null,\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./BaseForesightModule-CJzWVlF2.mjs";var t=class extends e{};export{t};
2
+ //# sourceMappingURL=ElementObservingModule-QE8ZBcnz.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ElementObservingModule-OqTcnagr.mjs","names":[],"sources":["../src/core/ElementObservingModule.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport { BaseForesightModule } from \"./BaseForesightModule\"\n\n/**\n * Abstract base for modules that observe and unobserve individual elements.\n *\n * Declaring these methods on a shared base lets the manager call them through\n * a single type (e.g. `currentlyActiveHandler.observeElement(el)`) while still\n * keeping each subclass responsible for its own observation strategy\n * (PositionObserver, IntersectionObserver, pointerdown listeners, etc.).\n */\nexport abstract class ElementObservingModule extends BaseForesightModule {\n public abstract observeElement(element: ForesightElement): void\n public abstract unobserveElement(element: ForesightElement): void\n}\n"],"mappings":"uDAWA,IAAsB,EAAtB,cAAqD,CAAoB"}
1
+ {"version":3,"file":"ElementObservingModule-QE8ZBcnz.mjs","names":[],"sources":["../src/core/ElementObservingModule.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport { BaseForesightModule } from \"./BaseForesightModule\"\n\n/**\n * Abstract base for modules that observe and unobserve individual elements.\n *\n * Declaring these methods on a shared base lets the manager call them through\n * a single type (e.g. `currentlyActiveHandler.observeElement(el)`) while still\n * keeping each subclass responsible for its own observation strategy\n * (PositionObserver, IntersectionObserver, pointerdown listeners, etc.).\n */\nexport abstract class ElementObservingModule extends BaseForesightModule {\n public abstract observeElement(element: ForesightElement): void\n public abstract unobserveElement(element: ForesightElement): void\n}\n"],"mappings":"uDAWA,IAAsB,EAAtB,cAAqD,CAAoB"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./BaseForesightModule-CJzWVlF2.mjs";import{t}from"./lineSegmentIntersectsRect-x9nicliA.mjs";const n=(e,t)=>{let n=t.top-e.top,r=t.left-e.left;return n<-1?`down`:n>1?`up`:r<-1?`right`:r>1?`left`:`none`},r=(e,t,n)=>{let{x:r,y:i}=e,a={x:r,y:i};switch(t){case`down`:a.y+=n;break;case`up`:a.y-=n;break;case`left`:a.x-=n;break;case`right`:a.x+=n;break;case`none`:break;default:}return a};var i=class extends e{constructor(e){super(e.dependencies),this.moduleName=`ScrollPredictor`,this.predictedScrollPoint=null,this.scrollDirection=null,this.onDisconnect=()=>this.resetScrollProps(),this.trajectoryPositions=e.trajectoryPositions,this.scrollTrajectoryEvent={type:`scrollTrajectoryUpdate`,currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:{x:0,y:0},scrollDirection:`none`}}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(e,i){let a=e.state;!a.isIntersectingWithViewport||a.isPredicted||!a.isActive||(this.scrollDirection=this.scrollDirection??n(e.bounds.originalRect,i),this.scrollDirection!==`none`&&(this.predictedScrollPoint=this.predictedScrollPoint??r(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),t(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,e.bounds.expandedRect)&&this.callCallback(e,{kind:`scroll`,subType:this.scrollDirection})))}emitTrajectoryUpdate(){!this.scrollDirection||this.scrollDirection===`none`||!this.predictedScrollPoint||!this.hasListeners(`scrollTrajectoryUpdate`)||(this.scrollTrajectoryEvent.predictedPoint=this.predictedScrollPoint,this.scrollTrajectoryEvent.scrollDirection=this.scrollDirection,this.emit(this.scrollTrajectoryEvent))}onConnect(){}};export{i as ScrollPredictor};
2
+ //# sourceMappingURL=ScrollPredictor-ZHRqX0lh.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollPredictor-r4YnqcfQ.mjs","names":[],"sources":["../src/helpers/getScrollDirection.ts","../src/helpers/predictNextScrollPosition.ts","../src/predictors/ScrollPredictor.ts"],"sourcesContent":["import type { Rect, ScrollDirection } from \"../types/types\"\n\nexport const 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 const 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\n return predictedPoint\n}\n","import { getScrollDirection } from \"../helpers/getScrollDirection\"\nimport { lineSegmentIntersectsRect } from \"../helpers/lineSegmentIntersectsRect\"\nimport { predictNextScrollPosition } from \"../helpers/predictNextScrollPosition\"\nimport type {\n ForesightElementInternal,\n Point,\n ScrollDirection,\n ScrollTrajectoryUpdateEvent,\n TrajectoryPositions,\n} from \"../types/types\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\n\ninterface ScrollPredictorConfig {\n dependencies: ForesightModuleDependencies\n trajectoryPositions: Readonly<TrajectoryPositions>\n}\n\nexport class ScrollPredictor extends BaseForesightModule {\n protected readonly moduleName = \"ScrollPredictor\"\n\n private predictedScrollPoint: Point | null = null\n private scrollDirection: ScrollDirection | null = null\n private trajectoryPositions: Readonly<TrajectoryPositions>\n\n // Pre-allocated event object to avoid creating a new object every scroll frame\n private readonly scrollTrajectoryEvent: ScrollTrajectoryUpdateEvent\n\n constructor(config: ScrollPredictorConfig) {\n super(config.dependencies)\n this.trajectoryPositions = config.trajectoryPositions\n this.scrollTrajectoryEvent = {\n type: \"scrollTrajectoryUpdate\",\n currentPoint: this.trajectoryPositions.currentPoint,\n predictedPoint: { x: 0, y: 0 },\n scrollDirection: \"none\",\n }\n }\n\n public resetScrollProps(): void {\n this.scrollDirection = null\n this.predictedScrollPoint = null\n }\n\n public handleScrollPrefetch(entry: ForesightElementInternal, newRect: DOMRect): void {\n const state = entry.state\n if (!state.isIntersectingWithViewport || state.isPredicted || !state.isActive) {\n return\n }\n\n // ONCE per handlePositionChange batch we decide what the scroll direction is\n this.scrollDirection =\n this.scrollDirection ?? getScrollDirection(state.elementBounds.originalRect, newRect)\n\n if (this.scrollDirection === \"none\") {\n return\n }\n\n // ONCE per handlePositionChange batch we decide the predicted scroll point\n this.predictedScrollPoint =\n this.predictedScrollPoint ??\n predictNextScrollPosition(\n this.trajectoryPositions.currentPoint,\n this.scrollDirection,\n this.settings.scrollMargin\n )\n\n // Check if the scroll is going to intersect with an registered element\n if (\n lineSegmentIntersectsRect(\n this.trajectoryPositions.currentPoint,\n this.predictedScrollPoint,\n state.elementBounds.expandedRect\n )\n ) {\n this.callCallback(entry, {\n kind: \"scroll\",\n subType: this.scrollDirection,\n })\n }\n\n if (this.hasListeners(\"scrollTrajectoryUpdate\")) {\n this.scrollTrajectoryEvent.predictedPoint = this.predictedScrollPoint\n this.scrollTrajectoryEvent.scrollDirection = this.scrollDirection\n this.emit(this.scrollTrajectoryEvent)\n }\n }\n\n protected onConnect() {}\n protected onDisconnect = () => this.resetScrollProps()\n}\n"],"mappings":"+GAEA,MAAa,GAAsB,EAAe,IAAmC,CACnF,IACM,EAAS,EAAQ,IAAM,EAAQ,IAC/B,EAAS,EAAQ,KAAO,EAAQ,KAgBtC,OAbI,EAAS,GACJ,OACE,EAAS,EACX,KAIL,EAAS,GACJ,QACE,EAAS,EACX,OAGF,QCnBI,GACX,EACA,EACA,IACG,CACH,GAAM,CAAE,IAAG,KAAM,EACX,EAAiB,CAAE,IAAG,IAAG,CAE/B,OAAQ,EAAR,CACE,IAAK,OACH,EAAe,GAAK,EACpB,MACF,IAAK,KACH,EAAe,GAAK,EACpB,MACF,IAAK,OACH,EAAe,GAAK,EACpB,MACF,IAAK,QACH,EAAe,GAAK,EACpB,MACF,IAAK,OACH,MACF,SAIF,OAAO,GCZT,IAAa,EAAb,cAAqC,CAAoB,CAUvD,YAAY,EAA+B,CACzC,MAAM,EAAO,aAAa,iBAVI,4CAEa,0BACK,2BAmEnB,KAAK,kBAAkB,CA3DpD,KAAK,oBAAsB,EAAO,oBAClC,KAAK,sBAAwB,CAC3B,KAAM,yBACN,aAAc,KAAK,oBAAoB,aACvC,eAAgB,CAAE,EAAG,EAAG,EAAG,EAAG,CAC9B,gBAAiB,OAClB,CAGH,kBAAgC,CAC9B,KAAK,gBAAkB,KACvB,KAAK,qBAAuB,KAG9B,qBAA4B,EAAiC,EAAwB,CACnF,IAAM,EAAQ,EAAM,MAChB,CAAC,EAAM,4BAA8B,EAAM,aAAe,CAAC,EAAM,WAKrE,KAAK,gBACH,KAAK,iBAAmB,EAAmB,EAAM,cAAc,aAAc,EAAQ,CAEnF,KAAK,kBAAoB,SAK7B,KAAK,qBACH,KAAK,sBACL,EACE,KAAK,oBAAoB,aACzB,KAAK,gBACL,KAAK,SAAS,aACf,CAID,EACE,KAAK,oBAAoB,aACzB,KAAK,qBACL,EAAM,cAAc,aACrB,EAED,KAAK,aAAa,EAAO,CACvB,KAAM,SACN,QAAS,KAAK,gBACf,CAAC,CAGA,KAAK,aAAa,yBAAyB,GAC7C,KAAK,sBAAsB,eAAiB,KAAK,qBACjD,KAAK,sBAAsB,gBAAkB,KAAK,gBAClD,KAAK,KAAK,KAAK,sBAAsB,IAIzC,WAAsB"}
1
+ {"version":3,"file":"ScrollPredictor-ZHRqX0lh.mjs","names":[],"sources":["../src/helpers/getScrollDirection.ts","../src/helpers/predictNextScrollPosition.ts","../src/predictors/ScrollPredictor.ts"],"sourcesContent":["import type { Rect, ScrollDirection } from \"../types/types\"\n\nexport const 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 const 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\n return predictedPoint\n}\n","import { getScrollDirection } from \"../helpers/getScrollDirection\"\nimport { lineSegmentIntersectsRect } from \"../helpers/lineSegmentIntersectsRect\"\nimport { predictNextScrollPosition } from \"../helpers/predictNextScrollPosition\"\nimport type {\n ForesightElementInternal,\n Point,\n ScrollDirection,\n ScrollTrajectoryUpdateEvent,\n TrajectoryPositions,\n} from \"../types/types\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\n\ninterface ScrollPredictorConfig {\n dependencies: ForesightModuleDependencies\n trajectoryPositions: Readonly<TrajectoryPositions>\n}\n\nexport class ScrollPredictor extends BaseForesightModule {\n protected readonly moduleName = \"ScrollPredictor\"\n\n private predictedScrollPoint: Point | null = null\n private scrollDirection: ScrollDirection | null = null\n private trajectoryPositions: Readonly<TrajectoryPositions>\n\n // Pre-allocated event object to avoid creating a new object every scroll frame\n private readonly scrollTrajectoryEvent: ScrollTrajectoryUpdateEvent\n\n constructor(config: ScrollPredictorConfig) {\n super(config.dependencies)\n this.trajectoryPositions = config.trajectoryPositions\n this.scrollTrajectoryEvent = {\n type: \"scrollTrajectoryUpdate\",\n currentPoint: this.trajectoryPositions.currentPoint,\n predictedPoint: { x: 0, y: 0 },\n scrollDirection: \"none\",\n }\n }\n\n public resetScrollProps(): void {\n this.scrollDirection = null\n this.predictedScrollPoint = null\n }\n\n public handleScrollPrefetch(entry: ForesightElementInternal, newRect: DOMRect): void {\n const state = entry.state\n if (!state.isIntersectingWithViewport || state.isPredicted || !state.isActive) {\n return\n }\n\n // ONCE per handlePositionChange batch we decide what the scroll direction is.\n // entry.bounds still holds the PRE-scroll rect here - the DesktopHandler only\n // updates bounds after this runs (see handlePositionChange ordering).\n this.scrollDirection =\n this.scrollDirection ?? getScrollDirection(entry.bounds.originalRect, newRect)\n\n if (this.scrollDirection === \"none\") {\n return\n }\n\n // ONCE per handlePositionChange batch we decide the predicted scroll point\n this.predictedScrollPoint =\n this.predictedScrollPoint ??\n predictNextScrollPosition(\n this.trajectoryPositions.currentPoint,\n this.scrollDirection,\n this.settings.scrollMargin\n )\n\n // Check if the scroll is going to intersect with an registered element\n if (\n lineSegmentIntersectsRect(\n this.trajectoryPositions.currentPoint,\n this.predictedScrollPoint,\n entry.bounds.expandedRect\n )\n ) {\n this.callCallback(entry, {\n kind: \"scroll\",\n subType: this.scrollDirection,\n })\n }\n }\n\n /**\n * Emits a single trajectory update for the whole position-change batch;\n * direction and predicted point are identical for every element in it.\n * Called by the DesktopHandler after the batch, before resetScrollProps.\n */\n public emitTrajectoryUpdate(): void {\n if (\n !this.scrollDirection ||\n this.scrollDirection === \"none\" ||\n !this.predictedScrollPoint ||\n !this.hasListeners(\"scrollTrajectoryUpdate\")\n ) {\n return\n }\n\n this.scrollTrajectoryEvent.predictedPoint = this.predictedScrollPoint\n this.scrollTrajectoryEvent.scrollDirection = this.scrollDirection\n this.emit(this.scrollTrajectoryEvent)\n }\n\n protected onConnect() {}\n protected onDisconnect = () => this.resetScrollProps()\n}\n"],"mappings":"+GAEA,MAAa,GAAsB,EAAe,IAAmC,CACnF,IACM,EAAS,EAAQ,IAAM,EAAQ,IAC/B,EAAS,EAAQ,KAAO,EAAQ,KAgBtC,OAbI,EAAS,GACJ,OACE,EAAS,EACX,KAIL,EAAS,GACJ,QACE,EAAS,EACX,OAGF,QCnBI,GACX,EACA,EACA,IACG,CACH,GAAM,CAAE,IAAG,KAAM,EACX,EAAiB,CAAE,IAAG,IAAG,CAE/B,OAAQ,EAAR,CACE,IAAK,OACH,EAAe,GAAK,EACpB,MACF,IAAK,KACH,EAAe,GAAK,EACpB,MACF,IAAK,OACH,EAAe,GAAK,EACpB,MACF,IAAK,QACH,EAAe,GAAK,EACpB,MACF,IAAK,OACH,MACF,SAIF,OAAO,GCZT,IAAa,EAAb,cAAqC,CAAoB,CAUvD,YAAY,EAA+B,CACzC,MAAM,EAAO,aAAa,iBAVI,4CAEa,0BACK,2BAmFnB,KAAK,kBAAkB,CA3EpD,KAAK,oBAAsB,EAAO,oBAClC,KAAK,sBAAwB,CAC3B,KAAM,yBACN,aAAc,KAAK,oBAAoB,aACvC,eAAgB,CAAE,EAAG,EAAG,EAAG,EAAG,CAC9B,gBAAiB,OAClB,CAGH,kBAAgC,CAC9B,KAAK,gBAAkB,KACvB,KAAK,qBAAuB,KAG9B,qBAA4B,EAAiC,EAAwB,CACnF,IAAM,EAAQ,EAAM,MAChB,CAAC,EAAM,4BAA8B,EAAM,aAAe,CAAC,EAAM,WAOrE,KAAK,gBACH,KAAK,iBAAmB,EAAmB,EAAM,OAAO,aAAc,EAAQ,CAE5E,KAAK,kBAAoB,SAK7B,KAAK,qBACH,KAAK,sBACL,EACE,KAAK,oBAAoB,aACzB,KAAK,gBACL,KAAK,SAAS,aACf,CAID,EACE,KAAK,oBAAoB,aACzB,KAAK,qBACL,EAAM,OAAO,aACd,EAED,KAAK,aAAa,EAAO,CACvB,KAAM,SACN,QAAS,KAAK,gBACf,CAAC,GASN,sBAAoC,CAEhC,CAAC,KAAK,iBACN,KAAK,kBAAoB,QACzB,CAAC,KAAK,sBACN,CAAC,KAAK,aAAa,yBAAyB,GAK9C,KAAK,sBAAsB,eAAiB,KAAK,qBACjD,KAAK,sBAAsB,gBAAkB,KAAK,gBAClD,KAAK,KAAK,KAAK,sBAAsB,EAGvC,WAAsB"}
@@ -1,2 +1,2 @@
1
- import{t as e}from"./BaseForesightModule-BxWJ6mzK.mjs";import{tabbable as t}from"tabbable";const n=(e,t,n,r)=>{if(t!==null&&t>-1){let i=e?t-1:t+1;if(i>=0&&i<n.length&&n[i]===r)return i}return n.findIndex(e=>e===r)};var r=class extends e{constructor(e){super(e),this.moduleName=`TabPredictor`,this.lastKeyDown=null,this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.handleKeyDown=e=>{e.key===`Tab`&&(this.lastKeyDown=e)},this.handleFocusIn=e=>{if(!this.lastKeyDown||!(e.target instanceof HTMLElement))return;let t=this.lastKeyDown.shiftKey;this.lastKeyDown=null,this.ensureTabbableCache();let r=n(t,this.lastFocusedIndex,this.tabbableElementsCache,e.target);this.lastFocusedIndex=r,this.predictUpcomingTabs(r,t)}}invalidateCache(){this.tabbableElementsCache.length&&this.devLog(`Invalidating tabbable elements cache`),this.tabbableElementsCache=[],this.lastFocusedIndex=null}onConnect(){typeof document>`u`||(this.createAbortController(),document.addEventListener(`keydown`,this.handleKeyDown,{signal:this.abortController?.signal,passive:!0}),document.addEventListener(`focusin`,this.handleFocusIn,{signal:this.abortController?.signal,passive:!0}))}onDisconnect(){this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}ensureTabbableCache(){this.tabbableElementsCache.length&&this.lastFocusedIndex!==-1||(this.devLog(`Caching tabbable elements`),this.tabbableElementsCache=t(document.documentElement))}predictUpcomingTabs(e,t){let n=this.settings.tabOffset,r=t?`reverse`:`forwards`;for(let i=0;i<=n;i++){let n=t?e-i:e+i,a=this.tabbableElementsCache[n];if(!(a instanceof Element))continue;let o=this.elements.get(a);o&&!o.state.isPredicted&&o.state.isActive&&this.callCallback(o,{kind:`tab`,subType:r})}}};export{r as TabPredictor};
2
- //# sourceMappingURL=TabPredictor-CohoaRM5.mjs.map
1
+ import{t as e}from"./BaseForesightModule-CJzWVlF2.mjs";import{tabbable as t}from"tabbable";const n=(e,t,n,r)=>{if(t!==null&&t>-1){let i=e?t-1:t+1;if(i>=0&&i<n.length&&n[i]===r)return i}return n.findIndex(e=>e===r)};var r=class extends e{constructor(e){super(e),this.moduleName=`TabPredictor`,this.lastKeyDown=null,this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.handleKeyDown=e=>{e.key===`Tab`&&(this.lastKeyDown=e)},this.handleFocusIn=e=>{if(!this.lastKeyDown||!(e.target instanceof HTMLElement))return;let t=this.lastKeyDown.shiftKey;this.lastKeyDown=null,this.ensureTabbableCache();let r=n(t,this.lastFocusedIndex,this.tabbableElementsCache,e.target);this.lastFocusedIndex=r,this.predictUpcomingTabs(r,t)}}invalidateCache(){this.tabbableElementsCache.length&&this.devLog(`Invalidating tabbable elements cache`),this.tabbableElementsCache=[],this.lastFocusedIndex=null}onConnect(){typeof document>`u`||(this.createAbortController(),document.addEventListener(`keydown`,this.handleKeyDown,{signal:this.abortController?.signal,passive:!0}),document.addEventListener(`focusin`,this.handleFocusIn,{signal:this.abortController?.signal,passive:!0}))}onDisconnect(){this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}ensureTabbableCache(){this.tabbableElementsCache.length&&this.lastFocusedIndex!==-1||(this.devLog(`Caching tabbable elements`),this.tabbableElementsCache=t(document.documentElement))}predictUpcomingTabs(e,t){let n=this.settings.tabOffset,r=t?`reverse`:`forwards`;for(let i=0;i<=n;i++){let n=t?e-i:e+i,a=this.tabbableElementsCache[n];if(!(a instanceof Element))continue;let o=this.elements.get(a);o&&!o.state.isPredicted&&o.state.isActive&&this.callCallback(o,{kind:`tab`,subType:r})}}};export{r as TabPredictor};
2
+ //# sourceMappingURL=TabPredictor-C2zyaEQZ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TabPredictor-CohoaRM5.mjs","names":[],"sources":["../src/helpers/getFocusedElementIndex.ts","../src/predictors/TabPredictor.ts"],"sourcesContent":["import type { FocusableElement } from \"tabbable\"\n\n/**\n * Finds the index of a focused element within a cache of tabbable elements.\n * It uses a predictive search for O(1) performance in the common case of\n * sequential tabbing, and falls back to a linear search O(n) if the\n * prediction fails.\n *\n * @param isReversed - True if the user is tabbing backward (Shift+Tab).\n * @param lastFocusedIndex - The index of the previously focused element, or null if none.\n * @param tabbableElementsCache - The array of all tabbable elements.\n * @param targetElement - The new HTML element that has received focus.\n * @returns The index of the targetElement in the cache, or -1 if not found.\n */\nexport const getFocusedElementIndex = (\n isReversed: boolean,\n lastFocusedIndex: number | null,\n tabbableElementsCache: FocusableElement[],\n targetElement: HTMLElement\n): number => {\n // First, try to predict the next index based on the last known position.\n if (lastFocusedIndex !== null && lastFocusedIndex > -1) {\n const predictedIndex = isReversed ? lastFocusedIndex - 1 : lastFocusedIndex + 1\n\n // Check if the prediction is valid and correct.\n if (\n predictedIndex >= 0 &&\n predictedIndex < tabbableElementsCache.length &&\n tabbableElementsCache[predictedIndex] === targetElement\n ) {\n return predictedIndex\n }\n }\n\n return tabbableElementsCache.findIndex(element => element === targetElement)\n}\n","import { tabbable, type FocusableElement } from \"tabbable\"\nimport { getFocusedElementIndex } from \"../helpers/getFocusedElementIndex\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\n\n/**\n * Manages the prediction of user intent based on Tab key navigation.\n *\n * This class is a specialist module controlled by the ForesightManager.\n * Its responsibilities are:\n * - Listening for `keydown` and `focusin` events to detect tabbing.\n * - Caching the list of tabbable elements on the page for performance.\n * - Invalidating the cache when the DOM changes.\n * - Predicting which registered elements the user is about to focus.\n * - Calling a provided callback when a prediction is made.\n */\nexport class TabPredictor extends BaseForesightModule {\n protected readonly moduleName = \"TabPredictor\"\n\n // Internal state for tab prediction\n private lastKeyDown: KeyboardEvent | null = null\n private tabbableElementsCache: FocusableElement[] = []\n private lastFocusedIndex: number | null = null\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n public invalidateCache() {\n if (this.tabbableElementsCache.length) {\n this.devLog(\"Invalidating tabbable elements cache\")\n }\n\n this.tabbableElementsCache = []\n this.lastFocusedIndex = null\n }\n\n protected onConnect(): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n this.createAbortController()\n\n document.addEventListener(\"keydown\", this.handleKeyDown, {\n signal: this.abortController?.signal,\n passive: true,\n })\n\n document.addEventListener(\"focusin\", this.handleFocusIn, {\n signal: this.abortController?.signal,\n passive: true,\n })\n }\n\n protected onDisconnect(): void {\n this.tabbableElementsCache = []\n this.lastFocusedIndex = null\n this.lastKeyDown = null\n }\n\n // We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.\n // We dont use handleKeyDown for the full event because of 2 main reasons:\n // 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)\n // 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)\n private handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Tab\") {\n this.lastKeyDown = e\n }\n }\n\n private handleFocusIn = (e: FocusEvent) => {\n if (!this.lastKeyDown || !(e.target instanceof HTMLElement)) {\n return\n }\n\n const isReversed = this.lastKeyDown.shiftKey\n this.lastKeyDown = null\n\n this.ensureTabbableCache()\n\n const currentIndex = getFocusedElementIndex(\n isReversed,\n this.lastFocusedIndex,\n this.tabbableElementsCache,\n e.target\n )\n this.lastFocusedIndex = currentIndex\n\n this.predictUpcomingTabs(currentIndex, isReversed)\n }\n\n private ensureTabbableCache(): void {\n // tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values\n if (this.tabbableElementsCache.length && this.lastFocusedIndex !== -1) {\n return\n }\n\n this.devLog(\"Caching tabbable elements\")\n this.tabbableElementsCache = tabbable(document.documentElement)\n }\n\n private predictUpcomingTabs(currentIndex: number, isReversed: boolean): void {\n const tabOffset = this.settings.tabOffset\n const subType = isReversed ? \"reverse\" : \"forwards\"\n\n for (let i = 0; i <= tabOffset; i++) {\n const elementIndex = isReversed ? currentIndex - i : currentIndex + i\n const element = this.tabbableElementsCache[elementIndex]\n\n if (!(element instanceof Element)) {\n continue\n }\n\n const entry = this.elements.get(element)\n if (entry && !entry.state.isPredicted && entry.state.isActive) {\n this.callCallback(entry, { kind: \"tab\", subType })\n }\n }\n }\n}\n"],"mappings":"2FAcA,MAAa,GACX,EACA,EACA,EACA,IACW,CAEX,GAAI,IAAqB,MAAQ,EAAmB,GAAI,CACtD,IAAM,EAAiB,EAAa,EAAmB,EAAI,EAAmB,EAG9E,GACE,GAAkB,GAClB,EAAiB,EAAsB,QACvC,EAAsB,KAAoB,EAE1C,OAAO,EAIX,OAAO,EAAsB,UAAU,GAAW,IAAY,EAAc,ECnB9E,IAAa,EAAb,cAAkC,CAAoB,CAQpD,YAAY,EAA2C,CACrD,MAAM,EAAa,iBARW,gCAGY,gCACQ,EAAE,uBACZ,wBA2CjB,GAAqB,CACxC,EAAE,MAAQ,QACZ,KAAK,YAAc,uBAIE,GAAkB,CACzC,GAAI,CAAC,KAAK,aAAe,EAAE,EAAE,kBAAkB,aAC7C,OAGF,IAAM,EAAa,KAAK,YAAY,SACpC,KAAK,YAAc,KAEnB,KAAK,qBAAqB,CAE1B,IAAM,EAAe,EACnB,EACA,KAAK,iBACL,KAAK,sBACL,EAAE,OACH,CACD,KAAK,iBAAmB,EAExB,KAAK,oBAAoB,EAAc,EAAW,EA7DpD,iBAAyB,CACnB,KAAK,sBAAsB,QAC7B,KAAK,OAAO,uCAAuC,CAGrD,KAAK,sBAAwB,EAAE,CAC/B,KAAK,iBAAmB,KAG1B,WAA4B,CACtB,OAAO,SAAa,MAIxB,KAAK,uBAAuB,CAE5B,SAAS,iBAAiB,UAAW,KAAK,cAAe,CACvD,OAAQ,KAAK,iBAAiB,OAC9B,QAAS,GACV,CAAC,CAEF,SAAS,iBAAiB,UAAW,KAAK,cAAe,CACvD,OAAQ,KAAK,iBAAiB,OAC9B,QAAS,GACV,CAAC,EAGJ,cAA+B,CAC7B,KAAK,sBAAwB,EAAE,CAC/B,KAAK,iBAAmB,KACxB,KAAK,YAAc,KAkCrB,qBAAoC,CAE9B,KAAK,sBAAsB,QAAU,KAAK,mBAAqB,KAInE,KAAK,OAAO,4BAA4B,CACxC,KAAK,sBAAwB,EAAS,SAAS,gBAAgB,EAGjE,oBAA4B,EAAsB,EAA2B,CAC3E,IAAM,EAAY,KAAK,SAAS,UAC1B,EAAU,EAAa,UAAY,WAEzC,IAAK,IAAI,EAAI,EAAG,GAAK,EAAW,IAAK,CACnC,IAAM,EAAe,EAAa,EAAe,EAAI,EAAe,EAC9D,EAAU,KAAK,sBAAsB,GAE3C,GAAI,EAAE,aAAmB,SACvB,SAGF,IAAM,EAAQ,KAAK,SAAS,IAAI,EAAQ,CACpC,GAAS,CAAC,EAAM,MAAM,aAAe,EAAM,MAAM,UACnD,KAAK,aAAa,EAAO,CAAE,KAAM,MAAO,UAAS,CAAC"}
1
+ {"version":3,"file":"TabPredictor-C2zyaEQZ.mjs","names":[],"sources":["../src/helpers/getFocusedElementIndex.ts","../src/predictors/TabPredictor.ts"],"sourcesContent":["import type { FocusableElement } from \"tabbable\"\n\n/**\n * Finds the index of a focused element within a cache of tabbable elements.\n * It uses a predictive search for O(1) performance in the common case of\n * sequential tabbing, and falls back to a linear search O(n) if the\n * prediction fails.\n *\n * @param isReversed - True if the user is tabbing backward (Shift+Tab).\n * @param lastFocusedIndex - The index of the previously focused element, or null if none.\n * @param tabbableElementsCache - The array of all tabbable elements.\n * @param targetElement - The new HTML element that has received focus.\n * @returns The index of the targetElement in the cache, or -1 if not found.\n */\nexport const getFocusedElementIndex = (\n isReversed: boolean,\n lastFocusedIndex: number | null,\n tabbableElementsCache: FocusableElement[],\n targetElement: HTMLElement\n): number => {\n // First, try to predict the next index based on the last known position.\n if (lastFocusedIndex !== null && lastFocusedIndex > -1) {\n const predictedIndex = isReversed ? lastFocusedIndex - 1 : lastFocusedIndex + 1\n\n // Check if the prediction is valid and correct.\n if (\n predictedIndex >= 0 &&\n predictedIndex < tabbableElementsCache.length &&\n tabbableElementsCache[predictedIndex] === targetElement\n ) {\n return predictedIndex\n }\n }\n\n return tabbableElementsCache.findIndex(element => element === targetElement)\n}\n","import { tabbable, type FocusableElement } from \"tabbable\"\nimport { getFocusedElementIndex } from \"../helpers/getFocusedElementIndex\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\n\n/**\n * Manages the prediction of user intent based on Tab key navigation.\n *\n * This class is a specialist module controlled by the ForesightManager.\n * Its responsibilities are:\n * - Listening for `keydown` and `focusin` events to detect tabbing.\n * - Caching the list of tabbable elements on the page for performance.\n * - Invalidating the cache when the DOM changes.\n * - Predicting which registered elements the user is about to focus.\n * - Calling a provided callback when a prediction is made.\n */\nexport class TabPredictor extends BaseForesightModule {\n protected readonly moduleName = \"TabPredictor\"\n\n // Internal state for tab prediction\n private lastKeyDown: KeyboardEvent | null = null\n private tabbableElementsCache: FocusableElement[] = []\n private lastFocusedIndex: number | null = null\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n public invalidateCache() {\n if (this.tabbableElementsCache.length) {\n this.devLog(\"Invalidating tabbable elements cache\")\n }\n\n this.tabbableElementsCache = []\n this.lastFocusedIndex = null\n }\n\n protected onConnect(): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n this.createAbortController()\n\n document.addEventListener(\"keydown\", this.handleKeyDown, {\n signal: this.abortController?.signal,\n passive: true,\n })\n\n document.addEventListener(\"focusin\", this.handleFocusIn, {\n signal: this.abortController?.signal,\n passive: true,\n })\n }\n\n protected onDisconnect(): void {\n this.tabbableElementsCache = []\n this.lastFocusedIndex = null\n this.lastKeyDown = null\n }\n\n // We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.\n // We dont use handleKeyDown for the full event because of 2 main reasons:\n // 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)\n // 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)\n private handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Tab\") {\n this.lastKeyDown = e\n }\n }\n\n private handleFocusIn = (e: FocusEvent) => {\n if (!this.lastKeyDown || !(e.target instanceof HTMLElement)) {\n return\n }\n\n const isReversed = this.lastKeyDown.shiftKey\n this.lastKeyDown = null\n\n this.ensureTabbableCache()\n\n const currentIndex = getFocusedElementIndex(\n isReversed,\n this.lastFocusedIndex,\n this.tabbableElementsCache,\n e.target\n )\n this.lastFocusedIndex = currentIndex\n\n this.predictUpcomingTabs(currentIndex, isReversed)\n }\n\n private ensureTabbableCache(): void {\n // tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values\n if (this.tabbableElementsCache.length && this.lastFocusedIndex !== -1) {\n return\n }\n\n this.devLog(\"Caching tabbable elements\")\n this.tabbableElementsCache = tabbable(document.documentElement)\n }\n\n private predictUpcomingTabs(currentIndex: number, isReversed: boolean): void {\n const tabOffset = this.settings.tabOffset\n const subType = isReversed ? \"reverse\" : \"forwards\"\n\n for (let i = 0; i <= tabOffset; i++) {\n const elementIndex = isReversed ? currentIndex - i : currentIndex + i\n const element = this.tabbableElementsCache[elementIndex]\n\n if (!(element instanceof Element)) {\n continue\n }\n\n const entry = this.elements.get(element)\n if (entry && !entry.state.isPredicted && entry.state.isActive) {\n this.callCallback(entry, { kind: \"tab\", subType })\n }\n }\n }\n}\n"],"mappings":"2FAcA,MAAa,GACX,EACA,EACA,EACA,IACW,CAEX,GAAI,IAAqB,MAAQ,EAAmB,GAAI,CACtD,IAAM,EAAiB,EAAa,EAAmB,EAAI,EAAmB,EAG9E,GACE,GAAkB,GAClB,EAAiB,EAAsB,QACvC,EAAsB,KAAoB,EAE1C,OAAO,EAIX,OAAO,EAAsB,UAAU,GAAW,IAAY,EAAc,ECnB9E,IAAa,EAAb,cAAkC,CAAoB,CAQpD,YAAY,EAA2C,CACrD,MAAM,EAAa,iBARW,gCAGY,gCACQ,EAAE,uBACZ,wBA2CjB,GAAqB,CACxC,EAAE,MAAQ,QACZ,KAAK,YAAc,uBAIE,GAAkB,CACzC,GAAI,CAAC,KAAK,aAAe,EAAE,EAAE,kBAAkB,aAC7C,OAGF,IAAM,EAAa,KAAK,YAAY,SACpC,KAAK,YAAc,KAEnB,KAAK,qBAAqB,CAE1B,IAAM,EAAe,EACnB,EACA,KAAK,iBACL,KAAK,sBACL,EAAE,OACH,CACD,KAAK,iBAAmB,EAExB,KAAK,oBAAoB,EAAc,EAAW,EA7DpD,iBAAyB,CACnB,KAAK,sBAAsB,QAC7B,KAAK,OAAO,uCAAuC,CAGrD,KAAK,sBAAwB,EAAE,CAC/B,KAAK,iBAAmB,KAG1B,WAA4B,CACtB,OAAO,SAAa,MAIxB,KAAK,uBAAuB,CAE5B,SAAS,iBAAiB,UAAW,KAAK,cAAe,CACvD,OAAQ,KAAK,iBAAiB,OAC9B,QAAS,GACV,CAAC,CAEF,SAAS,iBAAiB,UAAW,KAAK,cAAe,CACvD,OAAQ,KAAK,iBAAiB,OAC9B,QAAS,GACV,CAAC,EAGJ,cAA+B,CAC7B,KAAK,sBAAwB,EAAE,CAC/B,KAAK,iBAAmB,KACxB,KAAK,YAAc,KAkCrB,qBAAoC,CAE9B,KAAK,sBAAsB,QAAU,KAAK,mBAAqB,KAInE,KAAK,OAAO,4BAA4B,CACxC,KAAK,sBAAwB,EAAS,SAAS,gBAAgB,EAGjE,oBAA4B,EAAsB,EAA2B,CAC3E,IAAM,EAAY,KAAK,SAAS,UAC1B,EAAU,EAAa,UAAY,WAEzC,IAAK,IAAI,EAAI,EAAG,GAAK,EAAW,IAAK,CACnC,IAAM,EAAe,EAAa,EAAe,EAAI,EAAe,EAC9D,EAAU,KAAK,sBAAsB,GAE3C,GAAI,EAAE,aAAmB,SACvB,SAGF,IAAM,EAAQ,KAAK,SAAS,IAAI,EAAQ,CACpC,GAAS,CAAC,EAAM,MAAM,aAAe,EAAM,MAAM,UACnD,KAAK,aAAa,EAAO,CAAE,KAAM,MAAO,UAAS,CAAC"}
@@ -1,2 +1,2 @@
1
- import{t as e}from"./ElementObservingModule-OqTcnagr.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`TouchDeviceHandler`,this.viewportPredictor=null,this.touchStartPredictor=null,this.predictor=null,this.onDisconnect=()=>{this.devLog(`Disconnecting touch predictor`),this.predictor?.disconnect()},this.onConnect=()=>this.setTouchPredictor(),this.storedDependencies=e}async getOrCreateViewportPredictor(){if(!this.viewportPredictor){let{ViewportPredictor:e}=await import(`./ViewportPredictor-BOtudr8f.mjs`);this.viewportPredictor=new e(this.storedDependencies),this.devLog(`ViewportPredictor lazy loaded`)}return this.viewportPredictor}async getOrCreateTouchStartPredictor(){if(!this.touchStartPredictor){let{TouchStartPredictor:e}=await import(`./TouchStartPredictor-Dqk28kjI.mjs`);this.touchStartPredictor=new e(this.storedDependencies),this.devLog(`TouchStartPredictor lazy loaded`)}return this.touchStartPredictor}async setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case`viewport`:this.predictor=await this.getOrCreateViewportPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`);break;case`onTouchStart`:this.predictor=await this.getOrCreateTouchStartPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`);break;case`none`:this.predictor=null,this.devLog(`Touch strategy set to "none" - no predictor connected`);return;default:this.settings.touchDeviceStrategy}this.predictor?.connect();for(let e of this.elements.keys())this.predictor?.observeElement(e)}observeElement(e){this.predictor?.observeElement(e)}unobserveElement(e){this.predictor?.unobserveElement(e)}get loadedPredictors(){return{viewport:this.viewportPredictor!==null,touchStart:this.touchStartPredictor!==null}}};export{t as TouchDeviceHandler};
2
- //# sourceMappingURL=TouchDeviceHandler-CKhsCqLJ.mjs.map
1
+ import{t as e}from"./ElementObservingModule-QE8ZBcnz.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`TouchDeviceHandler`,this.viewportPredictor=null,this.touchStartPredictor=null,this.predictor=null,this.onDisconnect=()=>{this.devLog(`Disconnecting touch predictor`),this.predictor?.disconnect()},this.onConnect=()=>this.setTouchPredictor(),this.storedDependencies=e}async getOrCreateViewportPredictor(){if(!this.viewportPredictor){let{ViewportPredictor:e}=await import(`./ViewportPredictor-aTE2EmU2.mjs`);this.viewportPredictor=new e(this.storedDependencies),this.devLog(`ViewportPredictor lazy loaded`)}return this.viewportPredictor}async getOrCreateTouchStartPredictor(){if(!this.touchStartPredictor){let{TouchStartPredictor:e}=await import(`./TouchStartPredictor-r7R_E74t.mjs`);this.touchStartPredictor=new e(this.storedDependencies),this.devLog(`TouchStartPredictor lazy loaded`)}return this.touchStartPredictor}async setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case`viewport`:this.predictor=await this.getOrCreateViewportPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`);break;case`onTouchStart`:this.predictor=await this.getOrCreateTouchStartPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`);break;case`none`:this.predictor=null,this.devLog(`Touch strategy set to "none" - no predictor connected`);return;default:this.settings.touchDeviceStrategy}this.predictor?.connect();for(let e of this.elements.keys())this.predictor?.observeElement(e)}observeElement(e){this.predictor?.observeElement(e)}unobserveElement(e){this.predictor?.unobserveElement(e)}get loadedPredictors(){return{viewport:this.viewportPredictor!==null,touchStart:this.touchStartPredictor!==null}}};export{t as TouchDeviceHandler};
2
+ //# sourceMappingURL=TouchDeviceHandler-BPzPdr-z.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TouchDeviceHandler-CKhsCqLJ.mjs","names":[],"sources":["../src/managers/TouchDeviceHandler.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport type { ViewportPredictor } from \"../predictors/ViewportPredictor\"\nimport type { TouchStartPredictor } from \"../predictors/TouchStartPredictor\"\n\nexport class TouchDeviceHandler extends ElementObservingModule {\n protected readonly moduleName = \"TouchDeviceHandler\"\n\n private viewportPredictor: ViewportPredictor | null = null\n private touchStartPredictor: TouchStartPredictor | null = null\n private predictor: ElementObservingModule | null = null\n private storedDependencies: ForesightModuleDependencies\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n this.storedDependencies = dependencies\n }\n\n private async getOrCreateViewportPredictor(): Promise<ViewportPredictor> {\n if (!this.viewportPredictor) {\n const { ViewportPredictor } = await import(\"../predictors/ViewportPredictor\")\n this.viewportPredictor = new ViewportPredictor(this.storedDependencies)\n this.devLog(\"ViewportPredictor lazy loaded\")\n }\n\n return this.viewportPredictor\n }\n\n private async getOrCreateTouchStartPredictor(): Promise<TouchStartPredictor> {\n if (!this.touchStartPredictor) {\n const { TouchStartPredictor } = await import(\"../predictors/TouchStartPredictor\")\n this.touchStartPredictor = new TouchStartPredictor(this.storedDependencies)\n this.devLog(\"TouchStartPredictor lazy loaded\")\n }\n\n return this.touchStartPredictor\n }\n\n public async setTouchPredictor() {\n this.predictor?.disconnect()\n\n switch (this.settings.touchDeviceStrategy) {\n case \"viewport\":\n this.predictor = await this.getOrCreateViewportPredictor()\n this.devLog(\n `Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`\n )\n break\n case \"onTouchStart\":\n this.predictor = await this.getOrCreateTouchStartPredictor()\n this.devLog(\n `Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`\n )\n break\n case \"none\":\n this.predictor = null\n this.devLog(`Touch strategy set to \"none\" - no predictor connected`)\n\n return\n default:\n this.settings.touchDeviceStrategy satisfies never\n }\n\n this.predictor?.connect()\n\n for (const element of this.elements.keys()) {\n this.predictor?.observeElement(element)\n }\n }\n\n protected onDisconnect = () => {\n this.devLog(\"Disconnecting touch predictor\")\n this.predictor?.disconnect()\n }\n protected onConnect = () => this.setTouchPredictor()\n\n public observeElement(element: ForesightElement): void {\n this.predictor?.observeElement(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.predictor?.unobserveElement(element)\n }\n\n /** For debugging: returns which predictors have been lazy loaded */\n public get loadedPredictors() {\n return {\n viewport: this.viewportPredictor !== null,\n touchStart: this.touchStartPredictor !== null,\n }\n }\n}\n"],"mappings":"0DAMA,IAAa,EAAb,cAAwC,CAAuB,CAQ7D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBARW,4CAEsB,8BACI,oBACP,2BA4DpB,CAC7B,KAAK,OAAO,gCAAgC,CAC5C,KAAK,WAAW,YAAY,qBAEF,KAAK,mBAAmB,CA3DlD,KAAK,mBAAqB,EAG5B,MAAc,8BAA2D,CACvE,GAAI,CAAC,KAAK,kBAAmB,CAC3B,GAAM,CAAE,qBAAsB,MAAM,OAAO,oCAC3C,KAAK,kBAAoB,IAAI,EAAkB,KAAK,mBAAmB,CACvE,KAAK,OAAO,gCAAgC,CAG9C,OAAO,KAAK,kBAGd,MAAc,gCAA+D,CAC3E,GAAI,CAAC,KAAK,oBAAqB,CAC7B,GAAM,CAAE,uBAAwB,MAAM,OAAO,sCAC7C,KAAK,oBAAsB,IAAI,EAAoB,KAAK,mBAAmB,CAC3E,KAAK,OAAO,kCAAkC,CAGhD,OAAO,KAAK,oBAGd,MAAa,mBAAoB,CAG/B,OAFA,KAAK,WAAW,YAAY,CAEpB,KAAK,SAAS,oBAAtB,CACE,IAAK,WACH,KAAK,UAAY,MAAM,KAAK,8BAA8B,CAC1D,KAAK,OACH,6BAA6B,KAAK,SAAS,oBAAoB,sBAChE,CACD,MACF,IAAK,eACH,KAAK,UAAY,MAAM,KAAK,gCAAgC,CAC5D,KAAK,OACH,6BAA6B,KAAK,SAAS,oBAAoB,wBAChE,CACD,MACF,IAAK,OACH,KAAK,UAAY,KACjB,KAAK,OAAO,wDAAwD,CAEpE,OACF,QACE,KAAK,SAAS,oBAGlB,KAAK,WAAW,SAAS,CAEzB,IAAK,IAAM,KAAW,KAAK,SAAS,MAAM,CACxC,KAAK,WAAW,eAAe,EAAQ,CAU3C,eAAsB,EAAiC,CACrD,KAAK,WAAW,eAAe,EAAQ,CAGzC,iBAAwB,EAAiC,CACvD,KAAK,WAAW,iBAAiB,EAAQ,CAI3C,IAAW,kBAAmB,CAC5B,MAAO,CACL,SAAU,KAAK,oBAAsB,KACrC,WAAY,KAAK,sBAAwB,KAC1C"}
1
+ {"version":3,"file":"TouchDeviceHandler-BPzPdr-z.mjs","names":[],"sources":["../src/managers/TouchDeviceHandler.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport type { ViewportPredictor } from \"../predictors/ViewportPredictor\"\nimport type { TouchStartPredictor } from \"../predictors/TouchStartPredictor\"\n\nexport class TouchDeviceHandler extends ElementObservingModule {\n protected readonly moduleName = \"TouchDeviceHandler\"\n\n private viewportPredictor: ViewportPredictor | null = null\n private touchStartPredictor: TouchStartPredictor | null = null\n private predictor: ElementObservingModule | null = null\n private storedDependencies: ForesightModuleDependencies\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n this.storedDependencies = dependencies\n }\n\n private async getOrCreateViewportPredictor(): Promise<ViewportPredictor> {\n if (!this.viewportPredictor) {\n const { ViewportPredictor } = await import(\"../predictors/ViewportPredictor\")\n this.viewportPredictor = new ViewportPredictor(this.storedDependencies)\n this.devLog(\"ViewportPredictor lazy loaded\")\n }\n\n return this.viewportPredictor\n }\n\n private async getOrCreateTouchStartPredictor(): Promise<TouchStartPredictor> {\n if (!this.touchStartPredictor) {\n const { TouchStartPredictor } = await import(\"../predictors/TouchStartPredictor\")\n this.touchStartPredictor = new TouchStartPredictor(this.storedDependencies)\n this.devLog(\"TouchStartPredictor lazy loaded\")\n }\n\n return this.touchStartPredictor\n }\n\n public async setTouchPredictor() {\n this.predictor?.disconnect()\n\n switch (this.settings.touchDeviceStrategy) {\n case \"viewport\":\n this.predictor = await this.getOrCreateViewportPredictor()\n this.devLog(\n `Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`\n )\n break\n case \"onTouchStart\":\n this.predictor = await this.getOrCreateTouchStartPredictor()\n this.devLog(\n `Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`\n )\n break\n case \"none\":\n this.predictor = null\n this.devLog(`Touch strategy set to \"none\" - no predictor connected`)\n\n return\n default:\n this.settings.touchDeviceStrategy satisfies never\n }\n\n this.predictor?.connect()\n\n for (const element of this.elements.keys()) {\n this.predictor?.observeElement(element)\n }\n }\n\n protected onDisconnect = () => {\n this.devLog(\"Disconnecting touch predictor\")\n this.predictor?.disconnect()\n }\n protected onConnect = () => this.setTouchPredictor()\n\n public observeElement(element: ForesightElement): void {\n this.predictor?.observeElement(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.predictor?.unobserveElement(element)\n }\n\n /** For debugging: returns which predictors have been lazy loaded */\n public get loadedPredictors() {\n return {\n viewport: this.viewportPredictor !== null,\n touchStart: this.touchStartPredictor !== null,\n }\n }\n}\n"],"mappings":"0DAMA,IAAa,EAAb,cAAwC,CAAuB,CAQ7D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBARW,4CAEsB,8BACI,oBACP,2BA4DpB,CAC7B,KAAK,OAAO,gCAAgC,CAC5C,KAAK,WAAW,YAAY,qBAEF,KAAK,mBAAmB,CA3DlD,KAAK,mBAAqB,EAG5B,MAAc,8BAA2D,CACvE,GAAI,CAAC,KAAK,kBAAmB,CAC3B,GAAM,CAAE,qBAAsB,MAAM,OAAO,oCAC3C,KAAK,kBAAoB,IAAI,EAAkB,KAAK,mBAAmB,CACvE,KAAK,OAAO,gCAAgC,CAG9C,OAAO,KAAK,kBAGd,MAAc,gCAA+D,CAC3E,GAAI,CAAC,KAAK,oBAAqB,CAC7B,GAAM,CAAE,uBAAwB,MAAM,OAAO,sCAC7C,KAAK,oBAAsB,IAAI,EAAoB,KAAK,mBAAmB,CAC3E,KAAK,OAAO,kCAAkC,CAGhD,OAAO,KAAK,oBAGd,MAAa,mBAAoB,CAG/B,OAFA,KAAK,WAAW,YAAY,CAEpB,KAAK,SAAS,oBAAtB,CACE,IAAK,WACH,KAAK,UAAY,MAAM,KAAK,8BAA8B,CAC1D,KAAK,OACH,6BAA6B,KAAK,SAAS,oBAAoB,sBAChE,CACD,MACF,IAAK,eACH,KAAK,UAAY,MAAM,KAAK,gCAAgC,CAC5D,KAAK,OACH,6BAA6B,KAAK,SAAS,oBAAoB,wBAChE,CACD,MACF,IAAK,OACH,KAAK,UAAY,KACjB,KAAK,OAAO,wDAAwD,CAEpE,OACF,QACE,KAAK,SAAS,oBAGlB,KAAK,WAAW,SAAS,CAEzB,IAAK,IAAM,KAAW,KAAK,SAAS,MAAM,CACxC,KAAK,WAAW,eAAe,EAAQ,CAU3C,eAAsB,EAAiC,CACrD,KAAK,WAAW,eAAe,EAAQ,CAGzC,iBAAwB,EAAiC,CACvD,KAAK,WAAW,iBAAiB,EAAQ,CAI3C,IAAW,kBAAmB,CAC5B,MAAO,CACL,SAAU,KAAK,oBAAsB,KACrC,WAAY,KAAK,sBAAwB,KAC1C"}
@@ -1,2 +1,2 @@
1
- import{t as e}from"./ElementObservingModule-OqTcnagr.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`TouchStartPredictor`,this.onConnect=()=>this.createAbortController(),this.onDisconnect=()=>{},this.handleTouchStart=e=>{let t=e.currentTarget,n=this.elements.get(t);n&&(this.callCallback(n,{kind:`touch`}),this.unobserveElement(t))}}observeElement(e){e instanceof HTMLElement&&e.addEventListener(`pointerdown`,this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(e){e instanceof HTMLElement&&e.removeEventListener(`pointerdown`,this.handleTouchStart)}};export{t as TouchStartPredictor};
2
- //# sourceMappingURL=TouchStartPredictor-Dqk28kjI.mjs.map
1
+ import{t as e}from"./ElementObservingModule-QE8ZBcnz.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`TouchStartPredictor`,this.onConnect=()=>this.createAbortController(),this.onDisconnect=()=>{},this.handleTouchStart=e=>{let t=e.currentTarget,n=this.elements.get(t);n&&(this.callCallback(n,{kind:`touch`}),this.unobserveElement(t))}}observeElement(e){e instanceof HTMLElement&&e.addEventListener(`pointerdown`,this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(e){e instanceof HTMLElement&&e.removeEventListener(`pointerdown`,this.handleTouchStart)}};export{t as TouchStartPredictor};
2
+ //# sourceMappingURL=TouchStartPredictor-r7R_E74t.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TouchStartPredictor-Dqk28kjI.mjs","names":[],"sources":["../src/predictors/TouchStartPredictor.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\n\nexport class TouchStartPredictor extends ElementObservingModule {\n protected readonly moduleName = \"TouchStartPredictor\"\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n protected onConnect = () => this.createAbortController()\n protected onDisconnect = () => {}\n\n // Change to touchstart ones it is baseline https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event\n public observeElement(element: ForesightElement): void {\n if (element instanceof HTMLElement) {\n element.addEventListener(\"pointerdown\", this.handleTouchStart, {\n signal: this.abortController?.signal,\n })\n }\n }\n\n // Change to touchstart ones it is baseline https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event\n public unobserveElement(element: ForesightElement): void {\n if (element instanceof HTMLElement) {\n element.removeEventListener(\"pointerdown\", this.handleTouchStart)\n }\n }\n\n protected handleTouchStart = (e: PointerEvent): void => {\n const currentTarget = e.currentTarget as ForesightElement\n const data = this.elements.get(currentTarget)\n\n if (data) {\n this.callCallback(data, {\n kind: \"touch\",\n })\n this.unobserveElement(currentTarget)\n }\n }\n}\n"],"mappings":"0DAIA,IAAa,EAAb,cAAyC,CAAuB,CAG9D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBAHW,yCAMJ,KAAK,uBAAuB,uBACzB,yBAkBD,GAA0B,CACtD,IAAM,EAAgB,EAAE,cAClB,EAAO,KAAK,SAAS,IAAI,EAAc,CAEzC,IACF,KAAK,aAAa,EAAM,CACtB,KAAM,QACP,CAAC,CACF,KAAK,iBAAiB,EAAc,GAvBxC,eAAsB,EAAiC,CACjD,aAAmB,aACrB,EAAQ,iBAAiB,cAAe,KAAK,iBAAkB,CAC7D,OAAQ,KAAK,iBAAiB,OAC/B,CAAC,CAKN,iBAAwB,EAAiC,CACnD,aAAmB,aACrB,EAAQ,oBAAoB,cAAe,KAAK,iBAAiB"}
1
+ {"version":3,"file":"TouchStartPredictor-r7R_E74t.mjs","names":[],"sources":["../src/predictors/TouchStartPredictor.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\n\nexport class TouchStartPredictor extends ElementObservingModule {\n protected readonly moduleName = \"TouchStartPredictor\"\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n protected onConnect = () => this.createAbortController()\n protected onDisconnect = () => {}\n\n // Change to touchstart ones it is baseline https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event\n public observeElement(element: ForesightElement): void {\n if (element instanceof HTMLElement) {\n element.addEventListener(\"pointerdown\", this.handleTouchStart, {\n signal: this.abortController?.signal,\n })\n }\n }\n\n // Change to touchstart ones it is baseline https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event\n public unobserveElement(element: ForesightElement): void {\n if (element instanceof HTMLElement) {\n element.removeEventListener(\"pointerdown\", this.handleTouchStart)\n }\n }\n\n protected handleTouchStart = (e: PointerEvent): void => {\n const currentTarget = e.currentTarget as ForesightElement\n const data = this.elements.get(currentTarget)\n\n if (data) {\n this.callCallback(data, {\n kind: \"touch\",\n })\n this.unobserveElement(currentTarget)\n }\n }\n}\n"],"mappings":"0DAIA,IAAa,EAAb,cAAyC,CAAuB,CAG9D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBAHW,yCAMJ,KAAK,uBAAuB,uBACzB,yBAkBD,GAA0B,CACtD,IAAM,EAAgB,EAAE,cAClB,EAAO,KAAK,SAAS,IAAI,EAAc,CAEzC,IACF,KAAK,aAAa,EAAM,CACtB,KAAM,QACP,CAAC,CACF,KAAK,iBAAiB,EAAc,GAvBxC,eAAsB,EAAiC,CACjD,aAAmB,aACrB,EAAQ,iBAAiB,cAAe,KAAK,iBAAkB,CAC7D,OAAQ,KAAK,iBAAiB,OAC/B,CAAC,CAKN,iBAAwB,EAAiC,CACnD,aAAmB,aACrB,EAAQ,oBAAoB,cAAe,KAAK,iBAAiB"}
@@ -1,2 +1,2 @@
1
- import{t as e}from"./ElementObservingModule-OqTcnagr.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`ViewportPredictor`,this.intersectionObserver=null,this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter),this.handleViewportEnter=e=>{for(let t of e){if(!t.isIntersecting)continue;let e=this.elements.get(t.target);e&&(this.callCallback(e,{kind:`viewport`}),this.unobserveElement(t.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}observeElement(e){this.intersectionObserver?.observe(e)}unobserveElement(e){this.intersectionObserver?.unobserve(e)}};export{t as ViewportPredictor};
2
- //# sourceMappingURL=ViewportPredictor-BOtudr8f.mjs.map
1
+ import{t as e}from"./ElementObservingModule-QE8ZBcnz.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`ViewportPredictor`,this.intersectionObserver=null,this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter),this.handleViewportEnter=e=>{for(let t of e){if(!t.isIntersecting)continue;let e=this.elements.get(t.target);e&&(this.callCallback(e,{kind:`viewport`}),this.unobserveElement(t.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}observeElement(e){this.intersectionObserver?.observe(e)}unobserveElement(e){this.intersectionObserver?.unobserve(e)}};export{t as ViewportPredictor};
2
+ //# sourceMappingURL=ViewportPredictor-aTE2EmU2.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ViewportPredictor-BOtudr8f.mjs","names":[],"sources":["../src/predictors/ViewportPredictor.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\n\nexport class ViewportPredictor extends ElementObservingModule {\n protected readonly moduleName = \"ViewportPredictor\"\n\n private intersectionObserver: IntersectionObserver | null = null\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n protected onConnect = () =>\n (this.intersectionObserver = new IntersectionObserver(this.handleViewportEnter))\n\n protected onDisconnect() {\n this.intersectionObserver?.disconnect()\n this.intersectionObserver = null\n }\n\n public observeElement(element: ForesightElement): void {\n this.intersectionObserver?.observe(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.intersectionObserver?.unobserve(element)\n }\n\n protected handleViewportEnter = (entries: IntersectionObserverEntry[]) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n continue\n }\n\n const data = this.elements.get(entry.target as ForesightElement)\n\n if (!data) {\n continue\n }\n\n this.callCallback(data, {\n kind: \"viewport\",\n })\n\n this.unobserveElement(entry.target as ForesightElement)\n }\n }\n}\n"],"mappings":"0DAIA,IAAa,EAAb,cAAuC,CAAuB,CAK5D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBALW,8CAE4B,wBAOzD,KAAK,qBAAuB,IAAI,qBAAqB,KAAK,oBAAoB,0BAehD,GAAyC,CACxE,IAAK,IAAM,KAAS,EAAS,CAC3B,GAAI,CAAC,EAAM,eACT,SAGF,IAAM,EAAO,KAAK,SAAS,IAAI,EAAM,OAA2B,CAE3D,IAIL,KAAK,aAAa,EAAM,CACtB,KAAM,WACP,CAAC,CAEF,KAAK,iBAAiB,EAAM,OAA2B,IA7B3D,cAAyB,CACvB,KAAK,sBAAsB,YAAY,CACvC,KAAK,qBAAuB,KAG9B,eAAsB,EAAiC,CACrD,KAAK,sBAAsB,QAAQ,EAAQ,CAG7C,iBAAwB,EAAiC,CACvD,KAAK,sBAAsB,UAAU,EAAQ"}
1
+ {"version":3,"file":"ViewportPredictor-aTE2EmU2.mjs","names":[],"sources":["../src/predictors/ViewportPredictor.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\n\nexport class ViewportPredictor extends ElementObservingModule {\n protected readonly moduleName = \"ViewportPredictor\"\n\n private intersectionObserver: IntersectionObserver | null = null\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n protected onConnect = () =>\n (this.intersectionObserver = new IntersectionObserver(this.handleViewportEnter))\n\n protected onDisconnect() {\n this.intersectionObserver?.disconnect()\n this.intersectionObserver = null\n }\n\n public observeElement(element: ForesightElement): void {\n this.intersectionObserver?.observe(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.intersectionObserver?.unobserve(element)\n }\n\n protected handleViewportEnter = (entries: IntersectionObserverEntry[]) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n continue\n }\n\n const data = this.elements.get(entry.target as ForesightElement)\n\n if (!data) {\n continue\n }\n\n this.callCallback(data, {\n kind: \"viewport\",\n })\n\n this.unobserveElement(entry.target as ForesightElement)\n }\n }\n}\n"],"mappings":"0DAIA,IAAa,EAAb,cAAuC,CAAuB,CAK5D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBALW,8CAE4B,wBAOzD,KAAK,qBAAuB,IAAI,qBAAqB,KAAK,oBAAoB,0BAehD,GAAyC,CACxE,IAAK,IAAM,KAAS,EAAS,CAC3B,GAAI,CAAC,EAAM,eACT,SAGF,IAAM,EAAO,KAAK,SAAS,IAAI,EAAM,OAA2B,CAE3D,IAIL,KAAK,aAAa,EAAM,CACtB,KAAM,WACP,CAAC,CAEF,KAAK,iBAAiB,EAAM,OAA2B,IA7B3D,cAAyB,CACvB,KAAK,sBAAsB,YAAY,CACvC,KAAK,qBAAuB,KAG9B,eAAsB,EAAiC,CACrD,KAAK,sBAAsB,QAAQ,EAAQ,CAG7C,iBAAwB,EAAiC,CACvD,KAAK,sBAAsB,UAAU,EAAQ"}
package/dist/index.d.mts CHANGED
@@ -53,13 +53,13 @@ type TrajectoryPositions = {
53
53
  predictedPoint: Point;
54
54
  };
55
55
  /**
56
- * Internal type representing the calculated boundaries of a foresight element,
57
- * including its original dimensions and the expanded hit area.
56
+ * Immutable geometry snapshot for a registered element. Replaced (never mutated)
57
+ * whenever the element's position or size changes, which happens on every
58
+ * scroll/resize tick for visible elements.
58
59
  */
59
60
  type ElementBounds = {
60
61
  /** The expanded rectangle, including hitSlop, used for interaction detection. */expandedRect: Readonly<Rect>; /** The original bounding rectangle of the element, as returned by `getBoundingClientRect()`. */
61
- originalRect: DOMRectReadOnly; /** The hit slop values applied to this element. */
62
- hitSlop: Exclude<HitSlop, number>;
62
+ originalRect: DOMRectReadOnly;
63
63
  };
64
64
  /**
65
65
  * Immutable, flat state snapshot for a registered element.
@@ -69,8 +69,10 @@ type ElementBounds = {
69
69
  type ForesightElementState = {
70
70
  /** Unique identifier assigned during registration. */id: string; /** Human-readable name for debugging. */
71
71
  name: string; /** Arbitrary user-supplied metadata. */
72
- meta: Record<string, unknown>; /** The boundary information for the element. */
73
- elementBounds: ElementBounds; /** Whether the user has connection limitations (network slower than minimum connection type (default: 3g) or data saver enabled) that prevent prefetching. */
72
+ meta: Record<string, unknown>;
73
+ /** The normalized hit slop applied to this element. The element's rects live in
74
+ * {@link ElementBounds} (see `getBounds`/`subscribeToBounds`), not in this snapshot. */
75
+ hitSlop: Exclude<HitSlop, number>; /** Whether the user has connection limitations (network slower than minimum connection type (default: 3g) or data saver enabled) that prevent prefetching. */
74
76
  isLimitedConnection: boolean; /** Whether the element is currently intersecting the viewport. */
75
77
  isIntersectingWithViewport: boolean;
76
78
  /** Whether the element is currently tracked by the manager. Stays `true` from
@@ -104,13 +106,25 @@ type ForesightRegisterResult = ForesightElementState & {
104
106
  */
105
107
  unregister: () => void;
106
108
  /**
107
- * Subscribe to state changes for this element. Returns an unsubscribe function.
109
+ * Subscribe to logical state changes for this element. Never fires for
110
+ * geometry-only changes (scroll/resize), use `subscribeToBounds` for those.
111
+ * Returns an unsubscribe function.
108
112
  */
109
113
  subscribe: (listener: () => void) => () => void;
110
114
  /**
111
115
  * Returns the current immutable state snapshot for this element.
116
+ * The reference only changes when logical state changes - never on scroll.
112
117
  */
113
118
  getSnapshot: () => ForesightElementState;
119
+ /**
120
+ * Subscribe to geometry changes for this element (position/size, fired on
121
+ * every scroll/resize tick while visible). Returns an unsubscribe function.
122
+ */
123
+ subscribeToBounds: (listener: () => void) => () => void;
124
+ /**
125
+ * Returns the current immutable geometry snapshot for this element.
126
+ */
127
+ getBounds: () => ElementBounds;
114
128
  };
115
129
  type callbackStatus = "error" | "success" | undefined;
116
130
  type MouseCallbackCounts = {
@@ -452,14 +466,28 @@ declare class ForesightManager {
452
466
  removeEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>): void;
453
467
  hasListeners<K extends ForesightEvent>(eventType: K): boolean;
454
468
  /**
455
- * Subscribe to state changes for a specific element.
469
+ * Subscribe to logical state changes for a specific element.
456
470
  * The listener is called (with no arguments) whenever the element's
457
- * immutable state snapshot is replaced. Use {@link registeredElements}
458
- * to read the latest state inside the listener.
471
+ * immutable state snapshot is replaced. Never fires for geometry-only
472
+ * changes (scroll/resize) - see {@link subscribeToElementBounds}.
473
+ * Use {@link registeredElements} to read the latest state inside the listener.
459
474
  *
460
475
  * @returns An unsubscribe function, or `undefined` if the element is not registered.
461
476
  */
462
477
  subscribeToElement(element: ForesightElement, listener: () => void): (() => void) | undefined;
478
+ /**
479
+ * Subscribe to geometry changes for a specific element (position/size, fired
480
+ * on every scroll/resize tick while visible). Use {@link getElementBounds}
481
+ * to read the latest geometry inside the listener.
482
+ *
483
+ * @returns An unsubscribe function, or `undefined` if the element is not registered.
484
+ */
485
+ subscribeToElementBounds(element: ForesightElement, listener: () => void): (() => void) | undefined;
486
+ /**
487
+ * Returns the current immutable geometry snapshot for a registered element,
488
+ * or `undefined` if the element is not registered.
489
+ */
490
+ getElementBounds(element: ForesightElement): ElementBounds | undefined;
463
491
  get getManagerData(): Readonly<ForesightManagerData>;
464
492
  private getLoadedModulesSnapshot;
465
493
  register(options: ForesightRegisterNodeListOptions): ForesightRegisterResult[];
@@ -474,7 +502,7 @@ declare class ForesightManager {
474
502
  */
475
503
  updateElementOptions(element: ForesightElement, options: Partial<ForesightRegisterOptionsWithoutElement>): ForesightElementState;
476
504
  /**
477
- * Create a subscribe function for an element.
505
+ * Create a subscribe function for a listener set (state or bounds subscribers).
478
506
  * Returns an unsubscribe callback when called.
479
507
  */
480
508
  private makeSubscribe;
@@ -484,6 +512,16 @@ declare class ForesightManager {
484
512
  * stable-reference contract relied on by useSyncExternalStore and shallowRef.
485
513
  */
486
514
  private updateElementState;
515
+ /**
516
+ * Replace the immutable geometry ref for an element and notify bounds
517
+ * subscribers. No-op when both rects are content-equal. Preserves the
518
+ * stable-reference contract, mirroring {@link updateElementState}.
519
+ *
520
+ * When a single trigger changes both geometry and logical state (position
521
+ * change, hitSlop update), bounds must be updated BEFORE the state patch so
522
+ * state subscribers always read fresh geometry.
523
+ */
524
+ private updateElementBounds;
487
525
  unregister(element: ForesightElement | NodeListOf<ForesightElement>, unregisterReason?: ElementUnregisteredReason): void;
488
526
  private unregisterElement;
489
527
  reactivate(element: ForesightElement | NodeListOf<ForesightElement>): void;
@@ -552,5 +590,5 @@ declare const createUnregisteredSnapshot: (isLimitedConnection: boolean) => Fore
552
590
  //#region src/core/BaseForesightModule.d.ts
553
591
  type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean;
554
592
  //#endregion
555
- export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type DeviceStrategyChangedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightCallback, type ForesightElement, type ForesightElementState, type ForesightEvent, type ForesightEventMap, ForesightManager, type ForesightManagerData, type ForesightManagerSettings, type Point as ForesightPoint, type Rect as ForesightRect, type ForesightRegisterNodeListOptions, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HasListenersFunction, type HitSlop, type ManagerSettingsChangedEvent, type MinimumConnectionType, type MouseTrajectoryUpdateEvent, type ScrollDirection, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting, createUnregisteredSnapshot };
593
+ export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type DeviceStrategyChangedEvent, type ElementBounds, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightCallback, type ForesightElement, type ForesightElementState, type ForesightEvent, type ForesightEventMap, ForesightManager, type ForesightManagerData, type ForesightManagerSettings, type Point as ForesightPoint, type Rect as ForesightRect, type ForesightRegisterNodeListOptions, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HasListenersFunction, type HitSlop, type ManagerSettingsChangedEvent, type MinimumConnectionType, type MouseTrajectoryUpdateEvent, type ScrollDirection, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting, createUnregisteredSnapshot };
556
594
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/helpers/CircularBuffer.ts","../src/types/types.ts","../src/managers/ForesightManager.ts","../src/helpers/createInitialState.ts","../src/core/BaseForesightModule.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/helpers/CircularBuffer.ts","../src/types/types.ts","../src/managers/ForesightManager.ts","../src/helpers/createInitialState.ts","../src/core/BaseForesightModule.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{a as e,i as t,n,o as r,t as i}from"./rectAndHitSlop-T7Z3PZlb.mjs";var a=class{constructor(e,t){this.source=e,this.derive=t}get size(){return this.source.size}get(e){let t=this.source.get(e);return t===void 0?void 0:this.derive(t)}has(e){return this.source.has(e)}forEach(e){this.source.forEach((t,n)=>e(this.derive(t),n,this))}*entries(){for(let[e,t]of this.source)yield[e,this.derive(t)]}*values(){for(let e of this.source.values())yield this.derive(e)}keys(){return this.source.keys()}[Symbol.iterator](){return this.entries()}get[Symbol.toStringTag](){return`DerivedMapView`}};const o=e=>{let t=s(),n=c(e);return{isTouchDevice:t,isLimitedConnection:n,shouldRegister:!n}},s=()=>typeof window>`u`||typeof navigator>`u`?!1:window.matchMedia(`(pointer: coarse)`).matches&&navigator.maxTouchPoints>0,c=e=>{let t=navigator.connection;if(!t)return!1;let n=[`slow-2g`,`2g`,`3g`,`4g`];return n.indexOf(t.effectiveType)<n.indexOf(e)||t.saveData},l=e=>{if(typeof window>`u`||typeof document>`u`)return!1;let t=window.innerWidth||document.documentElement.clientWidth,n=window.innerHeight||document.documentElement.clientHeight;return e.top<n&&e.bottom>0&&e.left<t&&e.right>0},u=()=>({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}),d=()=>({enableManagerLogging:!1,enableMousePrediction:!0,enableScrollPrediction:!0,positionHistorySize:8,trajectoryPredictionTime:120,scrollMargin:150,defaultHitSlop:{top:0,left:0,right:0,bottom:0},enableTabPrediction:!0,tabOffset:2,touchDeviceStrategy:`onTouchStart`,minimumConnectionType:`3g`}),f=(e,r,i,a)=>{let{element:o,callback:s,hitSlop:c,name:u,meta:d,reactivateAfter:f,enabled:p}=e,m=o.getBoundingClientRect(),h=c?t(c):i,g=p!==!1;return{state:{id:r,name:u||o.id||`unnamed`,meta:d??{},elementBounds:{originalRect:m,expandedRect:n(m,h),hitSlop:h},isLimitedConnection:a,isIntersectingWithViewport:l(m),isRegistered:!0,isActive:g&&!a,isParked:!1,isEnabled:g,isPredicted:!1,isCallbackRunning:!1,hitCount:0,registerCount:1,durationMs:void 0,status:void 0,error:null,reactivateAfter:f??1/0},invokedAt:void 0,completedAt:void 0,element:o,callback:s,reactivateTimeoutId:void 0,subscribers:new Set}},p={x:0,y:0,width:0,height:0,top:0,right:0,bottom:0,left:0,toJSON:()=>({})},m=e=>({id:``,name:``,meta:{},elementBounds:{originalRect:p,expandedRect:{top:0,left:0,right:0,bottom:0},hitSlop:{top:0,left:0,right:0,bottom:0}},isLimitedConnection:e,isIntersectingWithViewport:!1,isRegistered:!1,isActive:!1,isParked:!1,isEnabled:!1,isPredicted:!1,isCallbackRunning:!1,hitCount:0,registerCount:0,durationMs:void 0,status:void 0,error:null,reactivateAfter:r});var h=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,n){if(n?.signal?.aborted)return;let r=this.eventListeners.get(e)??[];r.push(t),this.eventListeners.set(e,r),n?.signal?.addEventListener(`abort`,()=>this.removeEventListener(e,t))}removeEventListener(e,t){let n=this.eventListeners.get(e);if(!n)return;let r=n.indexOf(t);r>-1&&n.splice(r,1)}emit(e){let t=this.eventListeners.get(e.type);if(!(!t||t.length===0))for(let n=0;n<t.length;n++)try{let r=t[n];r&&r(e)}catch(t){console.error(`Error in ForesightManager event listener ${n} for ${e.type}:`,t)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};const g=(e,t)=>e!==void 0&&t!==e,_={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}},v=(t,n,r)=>{if(!g(r,t[n]))return!1;let{min:i,max:a}=_[n];return t[n]=e(r,i,a,n),!0},y=(e,t,n)=>g(n,e[t])?(e[t]=n,!0):!1,b=(e,n)=>{v(e,`trajectoryPredictionTime`,n.trajectoryPredictionTime),v(e,`positionHistorySize`,n.positionHistorySize),v(e,`scrollMargin`,n.scrollMargin),v(e,`tabOffset`,n.tabOffset),y(e,`enableMousePrediction`,n.enableMousePrediction),y(e,`enableScrollPrediction`,n.enableScrollPrediction),y(e,`enableTabPrediction`,n.enableTabPrediction),y(e,`enableManagerLogging`,n.enableManagerLogging),n.defaultHitSlop!==void 0&&(e.defaultHitSlop=t(n.defaultHitSlop)),n.touchDeviceStrategy!==void 0&&(e.touchDeviceStrategy=n.touchDeviceStrategy),n.minimumConnectionType!==void 0&&(e.minimumConnectionType=n.minimumConnectionType)},x=(e,n)=>{let r=[],a=!1,o=!1,s=!1,c=!1,l=!1;if(!n)return{changedSettings:r,positionHistorySizeChanged:a,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l};for(let t of[`trajectoryPredictionTime`,`positionHistorySize`,`scrollMargin`,`tabOffset`]){let i=e[t];v(e,t,n[t])&&(r.push({setting:t,oldValue:i,newValue:e[t]}),t===`positionHistorySize`&&(a=!0))}for(let t of[`enableMousePrediction`,`enableScrollPrediction`,`enableTabPrediction`]){let i=e[t];y(e,t,n[t])&&(r.push({setting:t,oldValue:i,newValue:e[t]}),t===`enableScrollPrediction`&&(o=!0),t===`enableTabPrediction`&&(s=!0))}if(n.defaultHitSlop!==void 0){let a=e.defaultHitSlop,o=t(n.defaultHitSlop);i(a,o)||(e.defaultHitSlop=o,r.push({setting:`defaultHitSlop`,oldValue:a,newValue:o}),c=!0)}if(n.touchDeviceStrategy!==void 0){let t=e.touchDeviceStrategy;e.touchDeviceStrategy=n.touchDeviceStrategy,r.push({setting:`touchDeviceStrategy`,oldValue:t,newValue:n.touchDeviceStrategy}),l=!0}if(n.minimumConnectionType!==void 0){let t=e.minimumConnectionType;e.minimumConnectionType=n.minimumConnectionType,r.push({setting:`minimumConnectionType`,oldValue:t,newValue:n.minimumConnectionType})}return{changedSettings:r,positionHistorySizeChanged:a,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l}};var S=class e{constructor(e){this.elementEntries=new Map,this.registeredElements=new a(this.elementEntries,e=>e.state),this.idCounter=0,this.activeElementCount=0,this.parkedElementCount=0,this.desktopHandler=null,this.touchDeviceHandler=null,this.currentlyActiveHandler=null,this.isSetup=!1,this.pendingPointerEvent=null,this.rafId=null,this.domObserver=null,this.currentDeviceStrategy=s()?`touch`:`mouse`,this.eventEmitter=new h,this._globalCallbackHits=u(),this._globalSettings=d(),this.handlePointerMove=e=>{this.pendingPointerEvent=e,e.pointerType!==this.currentDeviceStrategy&&(this.eventEmitter.emit({type:`deviceStrategyChanged`,timestamp:Date.now(),newStrategy:e.pointerType,oldStrategy:this.currentDeviceStrategy}),this.currentDeviceStrategy=e.pointerType,this.setDeviceStrategy(this.currentDeviceStrategy)),!this.rafId&&(this.rafId=requestAnimationFrame(()=>{if(!this.isUsingDesktopHandler){this.rafId=null;return}this.pendingPointerEvent&&this.desktopHandler?.processMouseMovement(this.pendingPointerEvent),this.rafId=null}))},this.handleDomMutations=e=>{if(!e.length)return;this.desktopHandler?.invalidateTabCache();let t=!1;for(let n=0;n<e.length;n++){let r=e[n];if(r&&r.type===`childList`&&(r.removedNodes.length>0||r.addedNodes.length>0)){t=!0;break}}if(t)for(let e of this.elementEntries.values())e.state.isParked?e.element.isConnected&&this.resumeReconnected(e):e.element.isConnected||this.parkDisconnected(e)},e!==void 0&&b(this._globalSettings,e),this.handlerDependencies={elements:this.elementEntries,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),updateElementState:this.updateElementState.bind(this),settings:this._globalSettings},this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`),this.initializeGlobalListeners()}async getOrCreateDesktopHandler(){if(!this.desktopHandler){let{DesktopHandler:e}=await import(`./DesktopHandler-BgCQieJ9.mjs`);this.desktopHandler=new e(this.handlerDependencies),this.devLog(`DesktopHandler lazy loaded`)}return this.desktopHandler}async getOrCreateTouchHandler(){if(!this.touchDeviceHandler){let{TouchDeviceHandler:e}=await import(`./TouchDeviceHandler-CKhsCqLJ.mjs`);this.touchDeviceHandler=new e(this.handlerDependencies),this.devLog(`TouchDeviceHandler lazy loaded`)}return this.touchDeviceHandler}static initialize(t){return this.isInitiated||(e.manager=new e(t)),e.manager}static get isInitiated(){return!!e.manager}static get instance(){return this.initialize()}generateId(){return`foresight-${++this.idCounter}`}get isUsingDesktopHandler(){return this.currentDeviceStrategy===`mouse`||this.currentDeviceStrategy===`pen`}addEventListener(e,t,n){this.eventEmitter.addEventListener(e,t,n)}removeEventListener(e,t){this.eventEmitter.removeEventListener(e,t)}hasListeners(e){return this.eventEmitter.hasListeners(e)}subscribeToElement(e,t){let n=this.elementEntries.get(e);if(n)return n.subscribers.add(t),()=>{n.subscribers.delete(t)}}get getManagerData(){return{registeredElements:this.registeredElements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount,parkedElementCount:this.parkedElementCount,loadedModules:this.getLoadedModulesSnapshot()}}getLoadedModulesSnapshot(){let e=this.desktopHandler?.loadedPredictors,t=this.touchDeviceHandler?.loadedPredictors;return{desktopHandler:this.desktopHandler!==null,touchHandler:this.touchDeviceHandler!==null,predictors:{mouse:e?.mouse??!1,tab:e?.tab??!1,scroll:e?.scroll??!1,viewport:t?.viewport??!1,touchStart:t?.touchStart??!1}}}register(e){let{element:t,...n}=e;return t instanceof NodeList?Array.from(t,e=>this.registerElement({...n,element:e})):this.registerElement({...n,element:t})}registerElement(e){let{isLimitedConnection:t}=o(this._globalSettings.minimumConnectionType),n=this.elementEntries.get(e.element);if(n)return this.updateElementOptions(e.element,e),this.updateElementState(n,{registerCount:n.state.registerCount+1}),{...n.state,unregister:()=>{this.unregister(e.element)},subscribe:this.makeSubscribe(n),getSnapshot:()=>n.state};this.isSetup||this.initializeGlobalListeners();let r=f(e,this.generateId(),this._globalSettings.defaultHitSlop,t);return this.elementEntries.set(e.element,r),r.state.isActive&&(this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e.element)),this.eventEmitter.emit({type:`elementRegistered`,timestamp:Date.now(),element:e.element,state:r.state}),{...r.state,unregister:()=>{this.unregister(e.element)},subscribe:this.makeSubscribe(r),getSnapshot:()=>r.state}}updateElementOptions(e,r){let a=this.elementEntries.get(e);if(!a)throw Error(`Cannot update options: element is not registered.`);r.callback&&(a.callback=r.callback),r.enabled!==void 0&&this.setElementEnabled(a,e,r.enabled!==!1);let o=a.state.elementBounds;if(r.hitSlop!==void 0){let a=t(r.hitSlop);if(!i(a,o.hitSlop)){let t=e.getBoundingClientRect();o={originalRect:t,expandedRect:n(t,a),hitSlop:a}}}let s=a.state.reactivateAfter,c=r.reactivateAfter??s,l=this.updateElementState(a,{name:r.name||a.state.name,meta:r.meta??a.state.meta,reactivateAfter:c,elementBounds:o});return c!==s&&(a.reactivateTimeoutId!==void 0&&this.clearReactivateTimeout(a),c!==1/0&&l.isPredicted&&(a.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e)},c))),l}makeSubscribe(e){return t=>(e.subscribers.add(t),()=>{e.subscribers.delete(t)})}updateElementState(e,t){let n=e.state,r=!1;for(let e in t)if(t[e]!==n[e]){r=!0;break}if(!r)return n;let i={...n,...t};e.state=i;for(let t of e.subscribers)try{t()}catch(e){console.error(`Error in element subscriber for ${i.name}:`,e)}return i}unregister(e,t){e instanceof NodeList?e.forEach(e=>this.unregisterElement(e,t)):this.unregisterElement(e,t)}unregisterElement(e,t){let n=this.elementEntries.get(e);if(!n)return;this.clearReactivateTimeout(n),this.currentlyActiveHandler?.unobserveElement(e),n.state.isActive&&this.activeElementCount--,n.state.isParked&&this.parkedElementCount--;let r=this.updateElementState(n,{isRegistered:!1,isActive:!1,isParked:!1,isPredicted:!1,isCallbackRunning:!1});this.elementEntries.delete(e),n.subscribers.clear();let i=this.elementEntries.size===0&&this.isSetup;i&&(this.devLog(`All elements unregistered, removing global listeners`),this.removeGlobalListeners()),this.eventEmitter.emit({type:`elementUnregistered`,element:e,state:r,timestamp:Date.now(),unregisterReason:t??`by user`,wasLastRegisteredElement:i})}reactivate(e){e instanceof NodeList?e.forEach(e=>this.reactivateElement(e)):this.reactivateElement(e)}reactivateElement(e){let t=this.elementEntries.get(e);!t||!t.state.isEnabled||(this.isSetup||this.initializeGlobalListeners(),this.clearReactivateTimeout(t),!(t.state.isCallbackRunning||t.state.isActive)&&(this.updateElementState(t,{isActive:!0,isPredicted:!1}),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e)))}setElementEnabled(e,t,n){if(e.state.isEnabled===n)return;let r=n&&!e.state.isLimitedConnection&&!e.state.isParked;r?(this.isSetup||this.initializeGlobalListeners(),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(t)):(this.clearReactivateTimeout(e),this.currentlyActiveHandler?.unobserveElement(t),e.state.isActive&&this.activeElementCount--),this.updateElementState(e,{isEnabled:n,isActive:r,isPredicted:!1,isCallbackRunning:!1}),this.removeGlobalListenersIfIdle()}clearReactivateTimeout(e){clearTimeout(e.reactivateTimeoutId),e.reactivateTimeoutId=void 0}callCallback(e,t){e.state.isPredicted||!e.state.isActive||(this.markElementAsRunning(e),this.executeCallbackAsync(e,t))}markElementAsRunning(e){this.clearReactivateTimeout(e),e.invokedAt=Date.now(),this.updateElementState(e,{isPredicted:!0,isCallbackRunning:!0,hitCount:e.state.hitCount+1})}async executeCallbackAsync(e,t){this.updateHitCounters(t),this.eventEmitter.emit({type:`callbackInvoked`,timestamp:Date.now(),element:e.element,state:e.state,hitType:t});let n=performance.now(),r=null;try{await e.callback(e.state)}catch(t){r=t instanceof Error?t.message:String(t),console.error(`Error in callback for element ${e.state.name}:`,t)}let i=r===null?`success`:`error`;this.finalizeCallback(e,t,n,i,r)}finalizeCallback(e,t,n,r,i){let a=performance.now()-n;e.state.isActive&&this.activeElementCount--,this.currentlyActiveHandler?.unobserveElement(e.element),e.completedAt=Date.now();let o=this.updateElementState(e,{isCallbackRunning:!1,isActive:!1,durationMs:a,status:r,error:i});o.reactivateAfter!==1/0&&(e.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},o.reactivateAfter));let s=this.activeElementCount===0;this.removeGlobalListenersIfIdle(),this.eventEmitter.emit({type:`callbackCompleted`,timestamp:Date.now(),element:e.element,state:o,hitType:t,elapsed:a,status:r,errorMessage:i,wasLastActiveElement:s})}updateHitCounters(e){switch(e.kind){case`mouse`:this._globalCallbackHits.mouse[e.subType]++;break;case`tab`:this._globalCallbackHits.tab[e.subType]++;break;case`scroll`:this._globalCallbackHits.scroll[e.subType]++;break;case`touch`:this._globalCallbackHits.touch++;break;case`viewport`:this._globalCallbackHits.viewport++;break;default:}this._globalCallbackHits.total++}async setDeviceStrategy(e){let t=this.currentDeviceStrategy;t!==e&&this.devLog(`Switching device strategy from ${t} to ${e}`),this.currentlyActiveHandler?.disconnect(),this.currentlyActiveHandler=e===`mouse`||e===`pen`?await this.getOrCreateDesktopHandler():await this.getOrCreateTouchHandler(),this.currentlyActiveHandler.connect()}initializeGlobalListeners(){this.isSetup||typeof document>`u`||(this.devLog(`Initializing global listeners (pointermove, MutationObserver)`),this.setDeviceStrategy(this.currentDeviceStrategy),document.addEventListener(`pointermove`,this.handlePointerMove),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.isSetup=!0)}removeGlobalListeners(){typeof document>`u`||(this.isSetup=!1,this.domObserver?.disconnect(),this.domObserver=null,document.removeEventListener(`pointermove`,this.handlePointerMove),this.currentlyActiveHandler?.disconnect(),this.rafId&&=(cancelAnimationFrame(this.rafId),null),this.pendingPointerEvent=null)}parkDisconnected(e){this.clearReactivateTimeout(e),this.currentlyActiveHandler?.unobserveElement(e.element),e.state.isActive&&this.activeElementCount--,this.parkedElementCount++,this.updateElementState(e,{isActive:!1,isCallbackRunning:!1,isParked:!0})}resumeReconnected(e){this.parkedElementCount--;let t=e.state.isEnabled&&!e.state.isLimitedConnection,n=t&&!e.state.isPredicted;n&&(this.isSetup||this.initializeGlobalListeners(),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e.element)),this.updateElementState(e,{isActive:n,isParked:!1}),t&&e.state.isPredicted&&e.state.reactivateAfter!==1/0&&(e.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.state.reactivateAfter))}removeGlobalListenersIfIdle(){this.activeElementCount>0||this.parkedElementCount>0||this.removeGlobalListeners()}alterGlobalSettings(e){let t=x(this._globalSettings,e);t.positionHistorySizeChanged&&this.desktopHandler&&this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize),t.scrollPredictionChanged&&this.isUsingDesktopHandler&&this.desktopHandler&&(this._globalSettings.enableScrollPrediction?this.desktopHandler.connectScrollPredictor():this.desktopHandler.disconnectScrollPredictor()),t.tabPredictionChanged&&this.isUsingDesktopHandler&&this.desktopHandler&&(this._globalSettings.enableTabPrediction?this.desktopHandler.connectTabPredictor():this.desktopHandler.disconnectTabPredictor()),t.hitSlopChanged&&this.forceUpdateAllElementBounds(),t.touchStrategyChanged&&!this.isUsingDesktopHandler&&this.touchDeviceHandler&&this.touchDeviceHandler.setTouchPredictor(),t.changedSettings.length>0&&this.eventEmitter.emit({type:`managerSettingsChanged`,timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:t.changedSettings})}forceUpdateAllElementBounds(){for(let e of this.elementEntries.values())e.state.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),r=n(t,e.state.elementBounds.hitSlop);i(r,e.state.elementBounds.expandedRect)||this.updateElementState(e,{elementBounds:{hitSlop:e.state.elementBounds.hitSlop,originalRect:t,expandedRect:r}})}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c🛠️ ForesightManager: ${e}`,`color: #16a34a; font-weight: bold;`)}};export{S as ForesightManager,m as createUnregisteredSnapshot};
1
+ import{a as e,i as t,n,o as r,t as i}from"./rectAndHitSlop-T7Z3PZlb.mjs";var a=class{constructor(e,t){this.source=e,this.derive=t}get size(){return this.source.size}get(e){let t=this.source.get(e);return t===void 0?void 0:this.derive(t)}has(e){return this.source.has(e)}forEach(e){this.source.forEach((t,n)=>e(this.derive(t),n,this))}*entries(){for(let[e,t]of this.source)yield[e,this.derive(t)]}*values(){for(let e of this.source.values())yield this.derive(e)}keys(){return this.source.keys()}[Symbol.iterator](){return this.entries()}get[Symbol.toStringTag](){return`DerivedMapView`}};const o=e=>{let t=s(),n=c(e);return{isTouchDevice:t,isLimitedConnection:n,shouldRegister:!n}},s=()=>typeof window>`u`||typeof navigator>`u`?!1:window.matchMedia(`(pointer: coarse)`).matches&&navigator.maxTouchPoints>0,c=e=>{let t=navigator.connection;if(!t)return!1;let n=[`slow-2g`,`2g`,`3g`,`4g`];return n.indexOf(t.effectiveType)<n.indexOf(e)||t.saveData},l=e=>{if(typeof window>`u`||typeof document>`u`)return!1;let t=window.innerWidth||document.documentElement.clientWidth,n=window.innerHeight||document.documentElement.clientHeight;return e.top<n&&e.bottom>0&&e.left<t&&e.right>0},u=()=>({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}),d=()=>({enableManagerLogging:!1,enableMousePrediction:!0,enableScrollPrediction:!0,positionHistorySize:8,trajectoryPredictionTime:120,scrollMargin:150,defaultHitSlop:{top:0,left:0,right:0,bottom:0},enableTabPrediction:!0,tabOffset:2,touchDeviceStrategy:`onTouchStart`,minimumConnectionType:`3g`}),f=(e,r,i,a)=>{let{element:o,callback:s,hitSlop:c,name:u,meta:d,reactivateAfter:f,enabled:p}=e,m=o.getBoundingClientRect(),h=c===void 0?i:t(c),g=p!==!1;return{state:{id:r,name:u||o.id||`unnamed`,meta:d??{},hitSlop:h,isLimitedConnection:a,isIntersectingWithViewport:l(m),isRegistered:!0,isActive:g&&!a,isParked:!1,isEnabled:g,isPredicted:!1,isCallbackRunning:!1,hitCount:0,registerCount:1,durationMs:void 0,status:void 0,error:null,reactivateAfter:f??1/0},bounds:{originalRect:m,expandedRect:n(m,h)},invokedAt:void 0,completedAt:void 0,element:o,callback:s,reactivateTimeoutId:void 0,subscribers:new Set,boundsSubscribers:new Set}},p=e=>({id:``,name:``,meta:{},hitSlop:{top:0,left:0,right:0,bottom:0},isLimitedConnection:e,isIntersectingWithViewport:!1,isRegistered:!1,isActive:!1,isParked:!1,isEnabled:!1,isPredicted:!1,isCallbackRunning:!1,hitCount:0,registerCount:0,durationMs:void 0,status:void 0,error:null,reactivateAfter:r});var m=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,n){if(n?.signal?.aborted)return;let r=this.eventListeners.get(e)??[];r.push(t),this.eventListeners.set(e,r),n?.signal?.addEventListener(`abort`,()=>this.removeEventListener(e,t))}removeEventListener(e,t){let n=this.eventListeners.get(e);if(!n)return;let r=n.indexOf(t);r>-1&&n.splice(r,1)}emit(e){let t=this.eventListeners.get(e.type);if(!(!t||t.length===0))for(let n=0;n<t.length;n++)try{let r=t[n];r&&r(e)}catch(t){console.error(`Error in ForesightManager event listener ${n} for ${e.type}:`,t)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};const h=(e,t)=>e!==void 0&&t!==e,g={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}},_=(t,n,r)=>{if(!h(r,t[n]))return!1;let{min:i,max:a}=g[n];return t[n]=e(r,i,a,n),!0},v=(e,t,n)=>h(n,e[t])?(e[t]=n,!0):!1,y=(e,n)=>{_(e,`trajectoryPredictionTime`,n.trajectoryPredictionTime),_(e,`positionHistorySize`,n.positionHistorySize),_(e,`scrollMargin`,n.scrollMargin),_(e,`tabOffset`,n.tabOffset),v(e,`enableMousePrediction`,n.enableMousePrediction),v(e,`enableScrollPrediction`,n.enableScrollPrediction),v(e,`enableTabPrediction`,n.enableTabPrediction),v(e,`enableManagerLogging`,n.enableManagerLogging),n.defaultHitSlop!==void 0&&(e.defaultHitSlop=t(n.defaultHitSlop)),n.touchDeviceStrategy!==void 0&&(e.touchDeviceStrategy=n.touchDeviceStrategy),n.minimumConnectionType!==void 0&&(e.minimumConnectionType=n.minimumConnectionType)},b=(e,n)=>{let r=[],a=!1,o=!1,s=!1,c=!1,l=!1;if(!n)return{changedSettings:r,positionHistorySizeChanged:a,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l};for(let t of[`trajectoryPredictionTime`,`positionHistorySize`,`scrollMargin`,`tabOffset`]){let i=e[t];_(e,t,n[t])&&(r.push({setting:t,oldValue:i,newValue:e[t]}),t===`positionHistorySize`&&(a=!0))}for(let t of[`enableMousePrediction`,`enableScrollPrediction`,`enableTabPrediction`]){let i=e[t];v(e,t,n[t])&&(r.push({setting:t,oldValue:i,newValue:e[t]}),t===`enableScrollPrediction`&&(o=!0),t===`enableTabPrediction`&&(s=!0))}if(n.defaultHitSlop!==void 0){let a=e.defaultHitSlop,o=t(n.defaultHitSlop);i(a,o)||(e.defaultHitSlop=o,r.push({setting:`defaultHitSlop`,oldValue:a,newValue:o}),c=!0)}if(n.touchDeviceStrategy!==void 0){let t=e.touchDeviceStrategy;e.touchDeviceStrategy=n.touchDeviceStrategy,r.push({setting:`touchDeviceStrategy`,oldValue:t,newValue:n.touchDeviceStrategy}),l=!0}if(n.minimumConnectionType!==void 0){let t=e.minimumConnectionType;e.minimumConnectionType=n.minimumConnectionType,r.push({setting:`minimumConnectionType`,oldValue:t,newValue:n.minimumConnectionType})}return{changedSettings:r,positionHistorySizeChanged:a,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l}};var x=class e{constructor(e){this.elementEntries=new Map,this.registeredElements=new a(this.elementEntries,e=>e.state),this.idCounter=0,this.activeElementCount=0,this.parkedElementCount=0,this.desktopHandler=null,this.touchDeviceHandler=null,this.currentlyActiveHandler=null,this.isSetup=!1,this.pendingPointerEvent=null,this.rafId=null,this.domObserver=null,this.currentDeviceStrategy=s()?`touch`:`mouse`,this.eventEmitter=new m,this._globalCallbackHits=u(),this._globalSettings=d(),this.handlePointerMove=e=>{this.pendingPointerEvent=e,e.pointerType!==this.currentDeviceStrategy&&(this.eventEmitter.emit({type:`deviceStrategyChanged`,timestamp:Date.now(),newStrategy:e.pointerType,oldStrategy:this.currentDeviceStrategy}),this.currentDeviceStrategy=e.pointerType,this.setDeviceStrategy(this.currentDeviceStrategy)),!this.rafId&&(this.rafId=requestAnimationFrame(()=>{if(!this.isUsingDesktopHandler){this.rafId=null;return}this.pendingPointerEvent&&this.desktopHandler?.processMouseMovement(this.pendingPointerEvent),this.rafId=null}))},this.handleDomMutations=e=>{if(!e.length)return;this.desktopHandler?.invalidateTabCache();let t=!1;for(let n=0;n<e.length;n++){let r=e[n];if(r&&r.type===`childList`&&(r.removedNodes.length>0||r.addedNodes.length>0)){t=!0;break}}if(t)for(let e of this.elementEntries.values())e.state.isParked?e.element.isConnected&&this.resumeReconnected(e):e.element.isConnected||this.parkDisconnected(e)},e!==void 0&&y(this._globalSettings,e),this.handlerDependencies={elements:this.elementEntries,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),updateElementState:this.updateElementState.bind(this),updateElementBounds:this.updateElementBounds.bind(this),settings:this._globalSettings},this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`),this.initializeGlobalListeners()}async getOrCreateDesktopHandler(){if(!this.desktopHandler){let{DesktopHandler:e}=await import(`./DesktopHandler-CR9nLuzu.mjs`);this.desktopHandler=new e(this.handlerDependencies),this.devLog(`DesktopHandler lazy loaded`)}return this.desktopHandler}async getOrCreateTouchHandler(){if(!this.touchDeviceHandler){let{TouchDeviceHandler:e}=await import(`./TouchDeviceHandler-BPzPdr-z.mjs`);this.touchDeviceHandler=new e(this.handlerDependencies),this.devLog(`TouchDeviceHandler lazy loaded`)}return this.touchDeviceHandler}static initialize(t){return this.isInitiated||(e.manager=new e(t)),e.manager}static get isInitiated(){return!!e.manager}static get instance(){return this.initialize()}generateId(){return`foresight-${++this.idCounter}`}get isUsingDesktopHandler(){return this.currentDeviceStrategy===`mouse`||this.currentDeviceStrategy===`pen`}addEventListener(e,t,n){this.eventEmitter.addEventListener(e,t,n)}removeEventListener(e,t){this.eventEmitter.removeEventListener(e,t)}hasListeners(e){return this.eventEmitter.hasListeners(e)}subscribeToElement(e,t){let n=this.elementEntries.get(e);if(n)return this.makeSubscribe(n.subscribers)(t)}subscribeToElementBounds(e,t){let n=this.elementEntries.get(e);if(n)return this.makeSubscribe(n.boundsSubscribers)(t)}getElementBounds(e){return this.elementEntries.get(e)?.bounds}get getManagerData(){return{registeredElements:this.registeredElements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount,parkedElementCount:this.parkedElementCount,loadedModules:this.getLoadedModulesSnapshot()}}getLoadedModulesSnapshot(){let e=this.desktopHandler?.loadedPredictors,t=this.touchDeviceHandler?.loadedPredictors;return{desktopHandler:this.desktopHandler!==null,touchHandler:this.touchDeviceHandler!==null,predictors:{mouse:e?.mouse??!1,tab:e?.tab??!1,scroll:e?.scroll??!1,viewport:t?.viewport??!1,touchStart:t?.touchStart??!1}}}register(e){let{element:t,...n}=e;return t instanceof NodeList?Array.from(t,e=>this.registerElement({...n,element:e})):this.registerElement({...n,element:t})}registerElement(e){let{isLimitedConnection:t}=o(this._globalSettings.minimumConnectionType),n=this.elementEntries.get(e.element);if(n)return this.updateElementOptions(e.element,e),this.updateElementState(n,{registerCount:n.state.registerCount+1}),{...n.state,unregister:()=>{this.unregister(e.element)},subscribe:this.makeSubscribe(n.subscribers),getSnapshot:()=>n.state,subscribeToBounds:this.makeSubscribe(n.boundsSubscribers),getBounds:()=>n.bounds};this.isSetup||this.initializeGlobalListeners();let r=f(e,this.generateId(),this._globalSettings.defaultHitSlop,t);return this.elementEntries.set(e.element,r),r.state.isActive&&(this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e.element)),this.eventEmitter.emit({type:`elementRegistered`,timestamp:Date.now(),element:e.element,state:r.state}),{...r.state,unregister:()=>{this.unregister(e.element)},subscribe:this.makeSubscribe(r.subscribers),getSnapshot:()=>r.state,subscribeToBounds:this.makeSubscribe(r.boundsSubscribers),getBounds:()=>r.bounds}}updateElementOptions(e,r){let a=this.elementEntries.get(e);if(!a)throw Error(`Cannot update options: element is not registered.`);r.callback&&(a.callback=r.callback),r.enabled!==void 0&&this.setElementEnabled(a,e,r.enabled!==!1);let o=a.state.hitSlop;if(r.hitSlop!==void 0){let s=t(r.hitSlop);if(!i(s,o)){o=s;let t=e.getBoundingClientRect();this.updateElementBounds(a,{originalRect:t,expandedRect:n(t,o)})}}let s=a.state.reactivateAfter,c=r.reactivateAfter??s,l=this.updateElementState(a,{name:r.name||a.state.name,meta:r.meta??a.state.meta,reactivateAfter:c,hitSlop:o});return c!==s&&(a.reactivateTimeoutId!==void 0&&this.clearReactivateTimeout(a),c!==1/0&&l.isPredicted&&(a.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e)},c))),l}makeSubscribe(e){return t=>(e.add(t),()=>{e.delete(t)})}updateElementState(e,t){let n=e.state,r=!1;for(let e in t)if(t[e]!==n[e]){r=!0;break}if(!r)return n;let i={...n,...t};e.state=i;for(let t of e.subscribers)try{t()}catch(e){console.error(`Error in element subscriber for ${i.name}:`,e)}return i}updateElementBounds(e,t){let n=e.bounds;if(i(t.originalRect,n.originalRect)&&i(t.expandedRect,n.expandedRect))return n;e.bounds=t;for(let t of e.boundsSubscribers)try{t()}catch(t){console.error(`Error in element bounds subscriber for ${e.state.name}:`,t)}return t}unregister(e,t){e instanceof NodeList?e.forEach(e=>this.unregisterElement(e,t)):this.unregisterElement(e,t)}unregisterElement(e,t){let n=this.elementEntries.get(e);if(!n)return;this.clearReactivateTimeout(n),this.currentlyActiveHandler?.unobserveElement(e),n.state.isActive&&this.activeElementCount--,n.state.isParked&&this.parkedElementCount--;let r=this.updateElementState(n,{isRegistered:!1,isActive:!1,isParked:!1,isPredicted:!1,isCallbackRunning:!1});this.elementEntries.delete(e),n.subscribers.clear(),n.boundsSubscribers.clear();let i=this.elementEntries.size===0&&this.isSetup;i&&(this.devLog(`All elements unregistered, removing global listeners`),this.removeGlobalListeners()),this.eventEmitter.emit({type:`elementUnregistered`,element:e,state:r,timestamp:Date.now(),unregisterReason:t??`by user`,wasLastRegisteredElement:i})}reactivate(e){e instanceof NodeList?e.forEach(e=>this.reactivateElement(e)):this.reactivateElement(e)}reactivateElement(e){let t=this.elementEntries.get(e);!t||!t.state.isEnabled||(this.isSetup||this.initializeGlobalListeners(),this.clearReactivateTimeout(t),!(t.state.isCallbackRunning||t.state.isActive)&&(this.updateElementState(t,{isActive:!0,isPredicted:!1}),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e)))}setElementEnabled(e,t,n){if(e.state.isEnabled===n)return;let r=n&&!e.state.isLimitedConnection&&!e.state.isParked;r?(this.isSetup||this.initializeGlobalListeners(),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(t)):(this.clearReactivateTimeout(e),this.currentlyActiveHandler?.unobserveElement(t),e.state.isActive&&this.activeElementCount--),this.updateElementState(e,{isEnabled:n,isActive:r,isPredicted:!1,isCallbackRunning:!1}),this.removeGlobalListenersIfIdle()}clearReactivateTimeout(e){clearTimeout(e.reactivateTimeoutId),e.reactivateTimeoutId=void 0}callCallback(e,t){e.state.isPredicted||!e.state.isActive||(this.markElementAsRunning(e),this.executeCallbackAsync(e,t))}markElementAsRunning(e){this.clearReactivateTimeout(e),e.invokedAt=Date.now(),this.updateElementState(e,{isPredicted:!0,isCallbackRunning:!0,hitCount:e.state.hitCount+1})}async executeCallbackAsync(e,t){this.updateHitCounters(t),this.eventEmitter.emit({type:`callbackInvoked`,timestamp:Date.now(),element:e.element,state:e.state,hitType:t});let n=performance.now(),r=null;try{await e.callback(e.state)}catch(t){r=t instanceof Error?t.message:String(t),console.error(`Error in callback for element ${e.state.name}:`,t)}let i=r===null?`success`:`error`;this.finalizeCallback(e,t,n,i,r)}finalizeCallback(e,t,n,r,i){let a=performance.now()-n;e.state.isActive&&this.activeElementCount--,this.currentlyActiveHandler?.unobserveElement(e.element),e.completedAt=Date.now();let o=this.updateElementState(e,{isCallbackRunning:!1,isActive:!1,durationMs:a,status:r,error:i});o.reactivateAfter!==1/0&&(e.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},o.reactivateAfter));let s=this.activeElementCount===0;this.removeGlobalListenersIfIdle(),this.eventEmitter.emit({type:`callbackCompleted`,timestamp:Date.now(),element:e.element,state:o,hitType:t,elapsed:a,status:r,errorMessage:i,wasLastActiveElement:s})}updateHitCounters(e){switch(e.kind){case`mouse`:this._globalCallbackHits.mouse[e.subType]++;break;case`tab`:this._globalCallbackHits.tab[e.subType]++;break;case`scroll`:this._globalCallbackHits.scroll[e.subType]++;break;case`touch`:this._globalCallbackHits.touch++;break;case`viewport`:this._globalCallbackHits.viewport++;break;default:}this._globalCallbackHits.total++}async setDeviceStrategy(e){let t=this.currentDeviceStrategy;t!==e&&this.devLog(`Switching device strategy from ${t} to ${e}`),this.currentlyActiveHandler?.disconnect(),this.currentlyActiveHandler=e===`mouse`||e===`pen`?await this.getOrCreateDesktopHandler():await this.getOrCreateTouchHandler(),this.currentlyActiveHandler.connect()}initializeGlobalListeners(){this.isSetup||typeof document>`u`||(this.devLog(`Initializing global listeners (pointermove, MutationObserver)`),this.setDeviceStrategy(this.currentDeviceStrategy),document.addEventListener(`pointermove`,this.handlePointerMove),this.domObserver=new MutationObserver(this.handleDomMutations),this.domObserver.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!1}),this.isSetup=!0)}removeGlobalListeners(){typeof document>`u`||(this.isSetup=!1,this.domObserver?.disconnect(),this.domObserver=null,document.removeEventListener(`pointermove`,this.handlePointerMove),this.currentlyActiveHandler?.disconnect(),this.rafId&&=(cancelAnimationFrame(this.rafId),null),this.pendingPointerEvent=null)}parkDisconnected(e){this.clearReactivateTimeout(e),this.currentlyActiveHandler?.unobserveElement(e.element),e.state.isActive&&this.activeElementCount--,this.parkedElementCount++,this.updateElementState(e,{isActive:!1,isCallbackRunning:!1,isParked:!0})}resumeReconnected(e){this.parkedElementCount--;let t=e.state.isEnabled&&!e.state.isLimitedConnection,n=t&&!e.state.isPredicted;n&&(this.isSetup||this.initializeGlobalListeners(),this.activeElementCount++,this.currentlyActiveHandler?.observeElement(e.element)),this.updateElementState(e,{isActive:n,isParked:!1}),t&&e.state.isPredicted&&e.state.reactivateAfter!==1/0&&(e.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.state.reactivateAfter))}removeGlobalListenersIfIdle(){this.activeElementCount>0||this.parkedElementCount>0||this.removeGlobalListeners()}alterGlobalSettings(e){let t=b(this._globalSettings,e);t.positionHistorySizeChanged&&this.desktopHandler&&this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize),t.scrollPredictionChanged&&this.isUsingDesktopHandler&&this.desktopHandler&&(this._globalSettings.enableScrollPrediction?this.desktopHandler.connectScrollPredictor():this.desktopHandler.disconnectScrollPredictor()),t.tabPredictionChanged&&this.isUsingDesktopHandler&&this.desktopHandler&&(this._globalSettings.enableTabPrediction?this.desktopHandler.connectTabPredictor():this.desktopHandler.disconnectTabPredictor()),t.hitSlopChanged&&this.forceUpdateAllElementBounds(),t.touchStrategyChanged&&!this.isUsingDesktopHandler&&this.touchDeviceHandler&&this.touchDeviceHandler.setTouchPredictor(),t.changedSettings.length>0&&this.eventEmitter.emit({type:`managerSettingsChanged`,timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:t.changedSettings})}forceUpdateAllElementBounds(){for(let e of this.elementEntries.values())e.state.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect();this.updateElementBounds(e,{originalRect:t,expandedRect:n(t,e.state.hitSlop)})}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c🛠️ ForesightManager: ${e}`,`color: #16a34a; font-weight: bold;`)}};export{x as ForesightManager,p as createUnregisteredSnapshot};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/helpers/DerivedMapView.ts","../src/helpers/shouldRegister.ts","../src/helpers/initialViewportState.ts","../src/helpers/createInitialState.ts","../src/core/ForesightEventEmitter.ts","../src/helpers/shouldUpdateSetting.ts","../src/managers/SettingsManager.ts","../src/managers/ForesightManager.ts"],"sourcesContent":["/**\n * Read-only map that exposes external state derived from internal state,\n * so internals are never leaked to consumers.\n */\nexport class DerivedMapView<K, TSource, TDerived> implements ReadonlyMap<K, TDerived> {\n constructor(\n private source: Map<K, TSource>,\n private derive: (value: TSource) => TDerived\n ) {}\n\n get size() {\n return this.source.size\n }\n\n get(key: K): TDerived | undefined {\n const entry = this.source.get(key)\n\n return entry === undefined ? undefined : this.derive(entry)\n }\n\n has(key: K): boolean {\n return this.source.has(key)\n }\n\n forEach(cb: (value: TDerived, key: K, map: ReadonlyMap<K, TDerived>) => void): void {\n this.source.forEach((entry, key) => cb(this.derive(entry), key, this))\n }\n\n *entries(): MapIterator<[K, TDerived]> {\n for (const [key, entry] of this.source) {\n yield [key, this.derive(entry)]\n }\n }\n\n *values(): MapIterator<TDerived> {\n for (const entry of this.source.values()) {\n yield this.derive(entry)\n }\n }\n\n keys(): MapIterator<K> {\n return this.source.keys()\n }\n\n [Symbol.iterator](): MapIterator<[K, TDerived]> {\n return this.entries()\n }\n\n get [Symbol.toStringTag](): string {\n return \"DerivedMapView\"\n }\n}\n","import { MinimumConnectionType } from \"../types/types\"\n\ntype ShouldRegister = {\n shouldRegister: boolean\n isTouchDevice: boolean\n isLimitedConnection: boolean\n}\n\nexport const evaluateRegistrationConditions = (\n minimumConnectionType: MinimumConnectionType\n): ShouldRegister => {\n const isTouchDevice = userUsesTouchDevice()\n const isLimitedConnection = hasConnectionLimitations(minimumConnectionType)\n const shouldRegister = !isLimitedConnection\n\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 */\nexport const userUsesTouchDevice = (): boolean => {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n return false\n }\n\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 */\nconst hasConnectionLimitations = (minimumConnectionType: MinimumConnectionType): boolean => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const connection = (navigator as any).connection\n if (!connection) {\n return false\n }\n\n // Define array of connection types from slowest to fastest\n const connectionTypes: MinimumConnectionType[] = [\"slow-2g\", \"2g\", \"3g\", \"4g\"]\n // Get index of user's current connection speed in the array (e.g. \"4g\" would be index 3)\n const currentConnectionIndex = connectionTypes.indexOf(\n connection.effectiveType as MinimumConnectionType\n )\n // Get index of the minimum connection speed required in settings (e.g. \"3g\" would be index 2)\n const minimumConnectionIndex = connectionTypes.indexOf(minimumConnectionType)\n\n // If user's connection is slower than the minimum required, or data saver is enabled, return true\n return currentConnectionIndex < minimumConnectionIndex || connection.saveData\n}\n","export const initialViewportState = (rect: DOMRect) => {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return false\n }\n\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 {\n DEFAULT_ENABLE_MOUSE_PREDICTION,\n DEFAULT_ENABLE_SCROLL_PREDICTION,\n DEFAULT_ENABLE_TAB_PREDICTION,\n DEFAULT_HITSLOP,\n DEFAULT_POSITION_HISTORY_SIZE,\n DEFAULT_SCROLL_MARGIN,\n DEFAULT_STALE_TIME,\n DEFAULT_TAB_OFFSET,\n DEFAULT_TRAJECTORY_PREDICTION_TIME,\n} from \"../constants\"\nimport type {\n CallbackHits,\n ForesightElementInternal,\n ForesightElementState,\n ForesightManagerSettings,\n ForesightRegisterOptions,\n HitSlop,\n} from \"../types/types\"\nimport { getExpandedRect, normalizeHitSlop } from \"./rectAndHitSlop\"\nimport { initialViewportState } from \"./initialViewportState\"\n\nexport const createInitialCallbackHits = (): CallbackHits => {\n return {\n mouse: {\n hover: 0,\n trajectory: 0,\n },\n tab: {\n forwards: 0,\n reverse: 0,\n },\n scroll: {\n down: 0,\n left: 0,\n right: 0,\n up: 0,\n },\n touch: 0,\n viewport: 0,\n total: 0,\n }\n}\n\nexport const createDefaultManagerSettings = (): ForesightManagerSettings => {\n return {\n enableManagerLogging: false,\n enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,\n enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,\n positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,\n trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,\n scrollMargin: DEFAULT_SCROLL_MARGIN,\n defaultHitSlop: {\n top: DEFAULT_HITSLOP,\n left: DEFAULT_HITSLOP,\n right: DEFAULT_HITSLOP,\n bottom: DEFAULT_HITSLOP,\n },\n enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,\n tabOffset: DEFAULT_TAB_OFFSET,\n touchDeviceStrategy: \"onTouchStart\",\n minimumConnectionType: \"3g\",\n }\n}\n\n/**\n * Creates the internal record for a newly registered element, including the\n * initial immutable state snapshot.\n */\nexport const createElementInternal = (\n options: ForesightRegisterOptions,\n id: string,\n defaultHitSlop: Exclude<HitSlop, number>,\n isLimitedConnection: boolean\n): ForesightElementInternal => {\n const { element, callback, hitSlop, name, meta, reactivateAfter, enabled } = options\n\n const initialRect = element.getBoundingClientRect()\n const normalizedHitSlop = hitSlop ? normalizeHitSlop(hitSlop) : defaultHitSlop\n const isEnabled = enabled !== false\n\n const state: ForesightElementState = {\n id,\n name: name || element.id || \"unnamed\",\n meta: meta ?? {},\n elementBounds: {\n originalRect: initialRect,\n expandedRect: getExpandedRect(initialRect, normalizedHitSlop),\n hitSlop: normalizedHitSlop,\n },\n isLimitedConnection,\n isIntersectingWithViewport: initialViewportState(initialRect),\n isRegistered: true,\n isActive: isEnabled && !isLimitedConnection,\n isParked: false,\n isEnabled,\n isPredicted: false,\n isCallbackRunning: false,\n hitCount: 0,\n registerCount: 1,\n durationMs: undefined,\n status: undefined,\n error: null,\n reactivateAfter: reactivateAfter ?? DEFAULT_STALE_TIME,\n }\n\n return {\n state,\n invokedAt: undefined,\n completedAt: undefined,\n element,\n callback,\n reactivateTimeoutId: undefined,\n subscribers: new Set(),\n }\n}\n\n// new DOMRectReadOnly(0, 0, 0, 0) doesn't work in ssr, so we use this constant instead\nconst EMPTY_DOM_RECT: DOMRectReadOnly = {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n toJSON: () => ({}),\n}\n\n/**\n * Snapshot of the flat state shape for an element that is not (yet) tracked by the\n * manager. Reuses the same fields as a registered element so consumers don't need\n * to special-case `null`.\n *\n * Used in two situations:\n * 1. The manager refuses to register the element (touch device, limited connection,\n * etc.) - pass `isLimitedConnection` to reflect that.\n * 2. Framework wrappers (React, Vue) need an initial snapshot before `register()`\n * has run. `register()` requires a real DOM element, which only exists after\n * the consumer's first render commits, so the wrapper returns this snapshot\n * during that brief window. Pass `isLimitedConnection: false` for this case.\n */\nexport const createUnregisteredSnapshot = (isLimitedConnection: boolean): ForesightElementState => {\n return {\n id: \"\",\n name: \"\",\n meta: {},\n elementBounds: {\n originalRect: EMPTY_DOM_RECT,\n expandedRect: { top: 0, left: 0, right: 0, bottom: 0 },\n hitSlop: { top: 0, left: 0, right: 0, bottom: 0 },\n },\n isLimitedConnection,\n isIntersectingWithViewport: false,\n isRegistered: false,\n isActive: false,\n isParked: false,\n isEnabled: false,\n isPredicted: false,\n isCallbackRunning: false,\n hitCount: 0,\n registerCount: 0,\n durationMs: undefined,\n status: undefined,\n error: null,\n reactivateAfter: DEFAULT_STALE_TIME,\n }\n}\n","import type { ForesightEvent, ForesightEventListener, ForesightEventMap } from \"../types/types\"\n\n/**\n * Generic event emitter for ForesightJS events.\n * Handles event registration, removal, and emission with error handling.\n */\nexport class ForesightEventEmitter {\n private eventListeners: Map<ForesightEvent, ForesightEventListener[]> = new Map()\n\n public addEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>,\n options?: { signal?: AbortSignal }\n ): void {\n if (options?.signal?.aborted) {\n return\n }\n\n const listeners = this.eventListeners.get(eventType) ?? []\n listeners.push(listener as ForesightEventListener)\n this.eventListeners.set(eventType, listeners)\n\n options?.signal?.addEventListener(\"abort\", () => this.removeEventListener(eventType, listener))\n }\n\n public removeEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>\n ): void {\n const listeners = this.eventListeners.get(eventType)\n\n if (!listeners) {\n return\n }\n\n const index = listeners.indexOf(listener as ForesightEventListener)\n if (index > -1) {\n listeners.splice(index, 1)\n }\n }\n\n /**\n * Emit an event to all registered listeners.\n * Errors in listeners are caught and logged, not propagated.\n */\n public emit<K extends ForesightEvent>(event: ForesightEventMap[K]): void {\n const listeners = this.eventListeners.get(event.type)\n\n if (!listeners || listeners.length === 0) {\n return\n }\n\n for (let i = 0; i < listeners.length; i++) {\n try {\n const listener = listeners[i]\n if (listener) {\n listener(event)\n }\n } catch (error) {\n console.error(`Error in ForesightManager event listener ${i} for ${event.type}:`, error)\n }\n }\n }\n\n /**\n * Check if there are any listeners registered for a specific event type.\n * Useful for avoiding expensive event object creation when no one is listening.\n */\n public hasListeners<K extends ForesightEvent>(eventType: K): boolean {\n const listeners = this.eventListeners.get(eventType)\n\n return listeners !== undefined && listeners.length > 0\n }\n\n public getEventListeners(): ReadonlyMap<ForesightEvent, ForesightEventListener[]> {\n return this.eventListeners\n }\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 const 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 {\n MAX_POSITION_HISTORY_SIZE,\n MAX_SCROLL_MARGIN,\n MAX_TAB_OFFSET,\n MAX_TRAJECTORY_PREDICTION_TIME,\n MIN_POSITION_HISTORY_SIZE,\n MIN_SCROLL_MARGIN,\n MIN_TAB_OFFSET,\n MIN_TRAJECTORY_PREDICTION_TIME,\n} from \"../constants\"\nimport { clampNumber } from \"../helpers/clampNumber\"\nimport { areRectsEqual, normalizeHitSlop } from \"../helpers/rectAndHitSlop\"\nimport { shouldUpdateSetting } from \"../helpers/shouldUpdateSetting\"\nimport type {\n ForesightManagerSettings,\n ManagerBooleanSettingKeys,\n NumericSettingKeys,\n UpdatedManagerSetting,\n UpdateForsightManagerSettings,\n} from \"../types/types\"\n\n/** Configuration for numeric settings with their min/max constraints */\nconst NUMERIC_SETTING_CONFIGS: Record<NumericSettingKeys, { min: number; max: number }> = {\n trajectoryPredictionTime: {\n min: MIN_TRAJECTORY_PREDICTION_TIME,\n max: MAX_TRAJECTORY_PREDICTION_TIME,\n },\n positionHistorySize: {\n min: MIN_POSITION_HISTORY_SIZE,\n max: MAX_POSITION_HISTORY_SIZE,\n },\n scrollMargin: {\n min: MIN_SCROLL_MARGIN,\n max: MAX_SCROLL_MARGIN,\n },\n tabOffset: {\n min: MIN_TAB_OFFSET,\n max: MAX_TAB_OFFSET,\n },\n}\n\n/**\n * Updates a numeric setting with clamping.\n * Returns true if the setting was changed.\n */\nconst updateNumericSetting = (\n settings: ForesightManagerSettings,\n key: NumericSettingKeys,\n newValue: number | undefined\n): boolean => {\n if (!shouldUpdateSetting(newValue, settings[key])) {\n return false\n }\n\n const { min, max } = NUMERIC_SETTING_CONFIGS[key]\n settings[key] = clampNumber(newValue, min, max, key)\n\n return true\n}\n\n/**\n * Updates a boolean setting.\n * Returns true if the setting was changed.\n */\nconst updateBooleanSetting = (\n settings: ForesightManagerSettings,\n key: ManagerBooleanSettingKeys,\n newValue: boolean | undefined\n): boolean => {\n if (!shouldUpdateSetting(newValue, settings[key])) {\n return false\n }\n\n settings[key] = newValue as boolean\n\n return true\n}\n\n/**\n * Apply initial settings during construction (no event emission, no side effects).\n * Mutates the settings object directly.\n */\nexport const initializeSettings = (\n settings: ForesightManagerSettings,\n props: Partial<UpdateForsightManagerSettings>\n): void => {\n // Numeric settings\n updateNumericSetting(settings, \"trajectoryPredictionTime\", props.trajectoryPredictionTime)\n updateNumericSetting(settings, \"positionHistorySize\", props.positionHistorySize)\n updateNumericSetting(settings, \"scrollMargin\", props.scrollMargin)\n updateNumericSetting(settings, \"tabOffset\", props.tabOffset)\n\n // Boolean settings\n updateBooleanSetting(settings, \"enableMousePrediction\", props.enableMousePrediction)\n updateBooleanSetting(settings, \"enableScrollPrediction\", props.enableScrollPrediction)\n updateBooleanSetting(settings, \"enableTabPrediction\", props.enableTabPrediction)\n updateBooleanSetting(settings, \"enableManagerLogging\", props.enableManagerLogging)\n\n // Object/special settings\n if (props.defaultHitSlop !== undefined) {\n settings.defaultHitSlop = normalizeHitSlop(props.defaultHitSlop)\n }\n\n if (props.touchDeviceStrategy !== undefined) {\n settings.touchDeviceStrategy = props.touchDeviceStrategy\n }\n\n if (props.minimumConnectionType !== undefined) {\n settings.minimumConnectionType = props.minimumConnectionType\n }\n}\n\n/**\n * Result of applying settings changes at runtime.\n * Contains the list of changed settings for event emission.\n */\ninterface SettingsChangeResult {\n changedSettings: UpdatedManagerSetting[]\n positionHistorySizeChanged: boolean\n scrollPredictionChanged: boolean\n tabPredictionChanged: boolean\n hitSlopChanged: boolean\n touchStrategyChanged: boolean\n}\n\n/**\n * Apply settings changes at runtime.\n * Returns information about what changed for the caller to handle side effects.\n */\nexport const applySettingsChanges = (\n settings: ForesightManagerSettings,\n props: Partial<UpdateForsightManagerSettings> | undefined\n): SettingsChangeResult => {\n const changedSettings: UpdatedManagerSetting[] = []\n let positionHistorySizeChanged = false\n let scrollPredictionChanged = false\n let tabPredictionChanged = false\n let hitSlopChanged = false\n let touchStrategyChanged = false\n\n if (!props) {\n return {\n changedSettings,\n positionHistorySizeChanged,\n scrollPredictionChanged,\n tabPredictionChanged,\n hitSlopChanged,\n touchStrategyChanged,\n }\n }\n\n // Numeric settings\n const numericKeys: NumericSettingKeys[] = [\n \"trajectoryPredictionTime\",\n \"positionHistorySize\",\n \"scrollMargin\",\n \"tabOffset\",\n ]\n\n for (const key of numericKeys) {\n const oldValue = settings[key]\n if (updateNumericSetting(settings, key, props[key])) {\n changedSettings.push({\n setting: key,\n oldValue,\n newValue: settings[key],\n } as UpdatedManagerSetting)\n\n if (key === \"positionHistorySize\") {\n positionHistorySizeChanged = true\n }\n }\n }\n\n // Boolean settings\n const booleanKeys: ManagerBooleanSettingKeys[] = [\n \"enableMousePrediction\",\n \"enableScrollPrediction\",\n \"enableTabPrediction\",\n ]\n\n for (const key of booleanKeys) {\n const oldValue = settings[key]\n if (updateBooleanSetting(settings, key, props[key])) {\n changedSettings.push({\n setting: key,\n oldValue,\n newValue: settings[key],\n } as UpdatedManagerSetting)\n\n if (key === \"enableScrollPrediction\") {\n scrollPredictionChanged = true\n }\n\n if (key === \"enableTabPrediction\") {\n tabPredictionChanged = true\n }\n }\n }\n\n // HitSlop\n if (props.defaultHitSlop !== undefined) {\n const oldHitSlop = settings.defaultHitSlop\n const normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop)\n\n if (!areRectsEqual(oldHitSlop, normalizedNewHitSlop)) {\n settings.defaultHitSlop = normalizedNewHitSlop\n changedSettings.push({\n setting: \"defaultHitSlop\",\n oldValue: oldHitSlop,\n newValue: normalizedNewHitSlop,\n })\n hitSlopChanged = true\n }\n }\n\n // Touch strategy\n if (props.touchDeviceStrategy !== undefined) {\n const oldValue = settings.touchDeviceStrategy\n settings.touchDeviceStrategy = props.touchDeviceStrategy\n changedSettings.push({\n setting: \"touchDeviceStrategy\",\n oldValue,\n newValue: props.touchDeviceStrategy,\n })\n touchStrategyChanged = true\n }\n\n // Minimum connection type\n if (props.minimumConnectionType !== undefined) {\n const oldValue = settings.minimumConnectionType\n settings.minimumConnectionType = props.minimumConnectionType\n changedSettings.push({\n setting: \"minimumConnectionType\",\n oldValue,\n newValue: props.minimumConnectionType,\n })\n }\n\n return {\n changedSettings,\n positionHistorySizeChanged,\n scrollPredictionChanged,\n tabPredictionChanged,\n hitSlopChanged,\n touchStrategyChanged,\n }\n}\n","import { DerivedMapView } from \"../helpers/DerivedMapView\"\nimport { areRectsEqual, getExpandedRect, normalizeHitSlop } from \"../helpers/rectAndHitSlop\"\nimport { evaluateRegistrationConditions, userUsesTouchDevice } from \"../helpers/shouldRegister\"\nimport {\n createDefaultManagerSettings,\n createElementInternal,\n createInitialCallbackHits,\n} from \"../helpers/createInitialState\"\nimport { ForesightEventEmitter } from \"../core/ForesightEventEmitter\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport type { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport { applySettingsChanges, initializeSettings } from \"./SettingsManager\"\nimport type {\n CallbackHits,\n CallbackHitType,\n callbackStatus,\n CurrentDeviceStrategy,\n ElementUnregisteredReason,\n ForesightElement,\n ForesightElementInternal,\n ForesightElementState,\n ForesightEvent,\n ForesightEventListener,\n ForesightManagerData,\n ForesightManagerSettings,\n ForesightModules,\n ForesightRegisterNodeListOptions,\n ForesightRegisterOptions,\n ForesightRegisterOptionsWithoutElement,\n ForesightRegisterResult,\n UpdateForsightManagerSettings,\n} from \"../types/types\"\nimport type { DesktopHandler } from \"./DesktopHandler\"\nimport type { TouchDeviceHandler } from \"./TouchDeviceHandler\"\n\n/**\n * Manages the prediction of user intent based on mouse trajectory and element interactions.\n *\n * ForesightManager is a singleton class responsible for:\n * - Registering HTML elements to monitor.\n * - Tracking mouse movements and predicting future cursor positions.\n * - Detecting when a predicted trajectory intersects with a registered element's bounds.\n * - Invoking callbacks associated with elements upon predicted or actual interaction.\n * - Deactivating elements after their callback completes, with optional reactivation via `reactivateAfter`.\n * - Handling global settings for prediction behavior (e.g., history size, prediction time).\n * - Delegating element bounds observation to device handlers ({@link DesktopHandler}, {@link TouchDeviceHandler}).\n * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.\n * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.\n *\n * It should be initialized once using {@link ForesightManager.initialize} and then\n * accessed via the static getter {@link ForesightManager.instance}.\n */\nexport class ForesightManager {\n private static manager: ForesightManager\n\n /** Internal entries containing full element data, callbacks, and subscribers. */\n private elementEntries: Map<ForesightElement, ForesightElementInternal> = new Map()\n /** Public read-only view exposing only external state, derived from {@link elementEntries}. */\n public readonly registeredElements: ReadonlyMap<ForesightElement, ForesightElementState> =\n new DerivedMapView(this.elementEntries, (entry: ForesightElementInternal) => entry.state)\n\n private idCounter: number = 0\n private activeElementCount: number = 0\n private parkedElementCount: number = 0\n\n private desktopHandler: DesktopHandler | null = null\n private touchDeviceHandler: TouchDeviceHandler | null = null\n private currentlyActiveHandler: ElementObservingModule | null = null\n private handlerDependencies: ForesightModuleDependencies\n\n private isSetup: boolean = false\n private pendingPointerEvent: PointerEvent | null = null\n private rafId: number | null = null\n private domObserver: MutationObserver | null = null\n private currentDeviceStrategy: CurrentDeviceStrategy = userUsesTouchDevice() ? \"touch\" : \"mouse\"\n\n private eventEmitter = new ForesightEventEmitter()\n private _globalCallbackHits: CallbackHits = createInitialCallbackHits()\n private _globalSettings: ForesightManagerSettings = createDefaultManagerSettings()\n\n private constructor(initialSettings?: Partial<UpdateForsightManagerSettings>) {\n if (initialSettings !== undefined) {\n initializeSettings(this._globalSettings, initialSettings)\n }\n\n this.handlerDependencies = {\n elements: this.elementEntries,\n callCallback: this.callCallback.bind(this),\n emit: this.eventEmitter.emit.bind(this.eventEmitter),\n hasListeners: this.eventEmitter.hasListeners.bind(this.eventEmitter),\n updateElementState: this.updateElementState.bind(this),\n settings: this._globalSettings,\n }\n\n // Handlers are created lazily when first needed as of 3.4.0\n this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`)\n this.initializeGlobalListeners()\n }\n\n private async getOrCreateDesktopHandler(): Promise<DesktopHandler> {\n if (!this.desktopHandler) {\n const { DesktopHandler } = await import(\"./DesktopHandler\")\n this.desktopHandler = new DesktopHandler(this.handlerDependencies)\n this.devLog(\"DesktopHandler lazy loaded\")\n }\n\n return this.desktopHandler\n }\n\n private async getOrCreateTouchHandler(): Promise<TouchDeviceHandler> {\n if (!this.touchDeviceHandler) {\n const { TouchDeviceHandler } = await import(\"./TouchDeviceHandler\")\n this.touchDeviceHandler = new TouchDeviceHandler(this.handlerDependencies)\n this.devLog(\"TouchDeviceHandler lazy loaded\")\n }\n\n return this.touchDeviceHandler\n }\n\n public static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager {\n if (!this.isInitiated) {\n ForesightManager.manager = new ForesightManager(props)\n }\n\n return ForesightManager.manager\n }\n\n public static get isInitiated(): Readonly<boolean> {\n return !!ForesightManager.manager\n }\n\n public static get instance(): ForesightManager {\n return this.initialize()\n }\n\n private generateId(): string {\n return `foresight-${++this.idCounter}`\n }\n\n private get isUsingDesktopHandler(): boolean {\n return this.currentDeviceStrategy === \"mouse\" || this.currentDeviceStrategy === \"pen\"\n }\n\n public addEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>,\n options?: { signal?: AbortSignal }\n ): void {\n this.eventEmitter.addEventListener(eventType, listener, options)\n }\n\n public removeEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>\n ): void {\n this.eventEmitter.removeEventListener(eventType, listener)\n }\n\n public hasListeners<K extends ForesightEvent>(eventType: K): boolean {\n return this.eventEmitter.hasListeners(eventType)\n }\n\n /**\n * Subscribe to state changes for a specific element.\n * The listener is called (with no arguments) whenever the element's\n * immutable state snapshot is replaced. Use {@link registeredElements}\n * to read the latest state inside the listener.\n *\n * @returns An unsubscribe function, or `undefined` if the element is not registered.\n */\n public subscribeToElement(\n element: ForesightElement,\n listener: () => void\n ): (() => void) | undefined {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return undefined\n }\n\n entry.subscribers.add(listener)\n\n return () => {\n entry.subscribers.delete(listener)\n }\n }\n\n public get getManagerData(): Readonly<ForesightManagerData> {\n return {\n registeredElements: this.registeredElements,\n globalSettings: this._globalSettings,\n globalCallbackHits: this._globalCallbackHits,\n eventListeners: this.eventEmitter.getEventListeners(),\n currentDeviceStrategy: this.currentDeviceStrategy,\n activeElementCount: this.activeElementCount,\n parkedElementCount: this.parkedElementCount,\n loadedModules: this.getLoadedModulesSnapshot(),\n }\n }\n\n private getLoadedModulesSnapshot(): ForesightModules {\n const desktopPredictors = this.desktopHandler?.loadedPredictors\n const touchPredictors = this.touchDeviceHandler?.loadedPredictors\n\n return {\n desktopHandler: this.desktopHandler !== null,\n touchHandler: this.touchDeviceHandler !== null,\n predictors: {\n mouse: desktopPredictors?.mouse ?? false,\n tab: desktopPredictors?.tab ?? false,\n scroll: desktopPredictors?.scroll ?? false,\n viewport: touchPredictors?.viewport ?? false,\n touchStart: touchPredictors?.touchStart ?? false,\n },\n }\n }\n\n public register(options: ForesightRegisterNodeListOptions): ForesightRegisterResult[]\n public register(options: ForesightRegisterOptions): ForesightRegisterResult\n public register(\n options: ForesightRegisterOptions | ForesightRegisterNodeListOptions\n ): ForesightRegisterResult | ForesightRegisterResult[] {\n const { element: elements, ...rest } = options\n\n if (elements instanceof NodeList) {\n return Array.from(elements, element => this.registerElement({ ...rest, element }))\n }\n\n return this.registerElement({ ...rest, element: elements })\n }\n\n private registerElement(options: ForesightRegisterOptions): ForesightRegisterResult {\n // On a limited connection (data saver / slow network) the element is\n // registered so it can be patched and tracked, but stays inactive (never\n // predicted, never fires its callback) to avoid consuming data.\n // See createElementInternal / setElementEnabled.\n const { isLimitedConnection } = evaluateRegistrationConditions(\n this._globalSettings.minimumConnectionType\n )\n\n const previousEntry = this.elementEntries.get(options.element)\n\n if (previousEntry) {\n this.updateElementOptions(options.element, options)\n this.updateElementState(previousEntry, {\n registerCount: previousEntry.state.registerCount + 1,\n })\n\n return {\n ...previousEntry.state,\n unregister: () => {\n this.unregister(options.element)\n },\n subscribe: this.makeSubscribe(previousEntry),\n getSnapshot: () => previousEntry.state,\n }\n }\n\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n const entry = createElementInternal(\n options,\n this.generateId(),\n this._globalSettings.defaultHitSlop,\n isLimitedConnection\n )\n\n this.elementEntries.set(options.element, entry)\n\n // Inactive elements (disabled or limited connection) are not observed or\n // counted as active until they are (re)activated.\n if (entry.state.isActive) {\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(options.element)\n }\n\n this.eventEmitter.emit({\n type: \"elementRegistered\",\n timestamp: Date.now(),\n element: options.element,\n state: entry.state,\n })\n\n return {\n ...entry.state,\n unregister: () => {\n this.unregister(options.element)\n },\n subscribe: this.makeSubscribe(entry),\n getSnapshot: () => entry.state,\n }\n }\n\n /**\n * Updates the options of an already-registered element.\n * Only the provided fields are updated; omitted fields keep their current values.\n * If a reactivation timeout is pending and reactivateAfter changed, the timeout is rescheduled.\n *\n * @throws Error if the element is not registered.\n */\n public updateElementOptions(\n element: ForesightElement,\n options: Partial<ForesightRegisterOptionsWithoutElement>\n ): ForesightElementState {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n throw new Error(\"Cannot update options: element is not registered.\")\n }\n\n if (options.callback) {\n entry.callback = options.callback\n }\n\n if (options.enabled !== undefined) {\n this.setElementEnabled(entry, element, options.enabled !== false)\n }\n\n // Keep the current bounds reference when hitSlop is omitted or content-equal,\n // so updateElementState sees no change; otherwise remeasure and re-expand.\n let elementBounds = entry.state.elementBounds\n if (options.hitSlop !== undefined) {\n const hitSlop = normalizeHitSlop(options.hitSlop)\n if (!areRectsEqual(hitSlop, elementBounds.hitSlop)) {\n const originalRect = element.getBoundingClientRect()\n elementBounds = {\n originalRect,\n expandedRect: getExpandedRect(originalRect, hitSlop),\n hitSlop,\n }\n }\n }\n\n const prevReactivateAfter = entry.state.reactivateAfter\n const reactivateAfter = options.reactivateAfter ?? prevReactivateAfter\n const next = this.updateElementState(entry, {\n name: options.name || entry.state.name,\n meta: options.meta ?? entry.state.meta,\n reactivateAfter,\n elementBounds,\n })\n\n // Only clear and reschedule the reactivation timeout if reactivateAfter actually changed\n if (reactivateAfter !== prevReactivateAfter) {\n if (entry.reactivateTimeoutId !== undefined) {\n this.clearReactivateTimeout(entry)\n }\n\n if (reactivateAfter !== Infinity && next.isPredicted) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(element)\n }, reactivateAfter)\n }\n }\n\n return next\n }\n\n /**\n * Create a subscribe function for an element.\n * Returns an unsubscribe callback when called.\n */\n private makeSubscribe(entry: ForesightElementInternal) {\n return (listener: () => void): (() => void) => {\n entry.subscribers.add(listener)\n\n return () => {\n entry.subscribers.delete(listener)\n }\n }\n }\n\n /**\n * Replace the immutable state ref for an element and notify subscribers.\n * No-op when every patch value already matches current state - preserves the\n * stable-reference contract relied on by useSyncExternalStore and shallowRef.\n */\n private updateElementState(\n entry: ForesightElementInternal,\n patch: Partial<ForesightElementState>\n ): ForesightElementState {\n const current = entry.state\n let changed = false\n for (const key in patch) {\n if (\n patch[key as keyof ForesightElementState] !== current[key as keyof ForesightElementState]\n ) {\n changed = true\n break\n }\n }\n if (!changed) {\n return current\n }\n\n const next = { ...current, ...patch }\n entry.state = next\n for (const listener of entry.subscribers) {\n try {\n listener()\n } catch (error) {\n console.error(`Error in element subscriber for ${next.name}:`, error)\n }\n }\n\n return next\n }\n\n public unregister(\n element: ForesightElement | NodeListOf<ForesightElement>,\n unregisterReason?: ElementUnregisteredReason\n ): void {\n if (element instanceof NodeList) {\n element.forEach(el => this.unregisterElement(el, unregisterReason))\n } else {\n this.unregisterElement(element, unregisterReason)\n }\n }\n\n private unregisterElement(\n element: ForesightElement,\n unregisterReason?: ElementUnregisteredReason\n ): void {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return\n }\n\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(element)\n\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n if (entry.state.isParked) {\n this.parkedElementCount--\n }\n\n const finalState = this.updateElementState(entry, {\n isRegistered: false,\n isActive: false,\n isParked: false,\n isPredicted: false,\n isCallbackRunning: false,\n })\n\n this.elementEntries.delete(element)\n entry.subscribers.clear()\n\n const wasLastRegisteredElement = this.elementEntries.size === 0 && this.isSetup\n if (wasLastRegisteredElement) {\n this.devLog(\"All elements unregistered, removing global listeners\")\n this.removeGlobalListeners()\n }\n\n this.eventEmitter.emit({\n type: \"elementUnregistered\",\n element: element,\n state: finalState,\n timestamp: Date.now(),\n unregisterReason: unregisterReason ?? \"by user\",\n wasLastRegisteredElement,\n })\n }\n\n public reactivate(element: ForesightElement | NodeListOf<ForesightElement>): void {\n if (element instanceof NodeList) {\n element.forEach(el => this.reactivateElement(el))\n } else {\n this.reactivateElement(element)\n }\n }\n\n private reactivateElement(element: ForesightElement): void {\n const entry = this.elementEntries.get(element)\n if (!entry || !entry.state.isEnabled) {\n return\n }\n\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.clearReactivateTimeout(entry)\n\n if (entry.state.isCallbackRunning || entry.state.isActive) {\n return\n }\n\n this.updateElementState(entry, { isActive: true, isPredicted: false })\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(element)\n }\n\n /**\n * Toggle prediction for a registered element without unregistering it.\n * Disabling deactivates and stops observing; enabling reactivates it.\n */\n private setElementEnabled(\n entry: ForesightElementInternal,\n element: ForesightElement,\n enabled: boolean\n ): void {\n if (entry.state.isEnabled === enabled) {\n return\n }\n\n // A limited connection keeps the element inactive even when enabled (a data\n // saver never starts firing just because enabled flipped); a parked element\n // (detached from the DOM) stays inactive until it reconnects.\n const isActive = enabled && !entry.state.isLimitedConnection && !entry.state.isParked\n\n if (isActive) {\n // Global listeners may have been torn down when the active count last hit\n // zero; re-arm them so prediction actually resumes.\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(element)\n } else {\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(element)\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n }\n\n this.updateElementState(entry, {\n isEnabled: enabled,\n isActive,\n isPredicted: false,\n isCallbackRunning: false,\n })\n\n // Disabling the last active element leaves nothing to predict on.\n this.removeGlobalListenersIfIdle()\n }\n\n private clearReactivateTimeout(entry: ForesightElementInternal): void {\n clearTimeout(entry.reactivateTimeoutId)\n entry.reactivateTimeoutId = undefined\n }\n\n private callCallback(entry: ForesightElementInternal, callbackHitType: CallbackHitType): void {\n if (entry.state.isPredicted || !entry.state.isActive) {\n return\n }\n\n this.markElementAsRunning(entry)\n this.executeCallbackAsync(entry, callbackHitType)\n }\n\n private markElementAsRunning(entry: ForesightElementInternal): void {\n this.clearReactivateTimeout(entry)\n\n entry.invokedAt = Date.now()\n\n this.updateElementState(entry, {\n isPredicted: true,\n isCallbackRunning: true,\n hitCount: entry.state.hitCount + 1,\n })\n }\n\n private async executeCallbackAsync(\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType\n ): Promise<void> {\n this.updateHitCounters(callbackHitType)\n\n this.eventEmitter.emit({\n type: \"callbackInvoked\",\n timestamp: Date.now(),\n element: entry.element,\n state: entry.state,\n hitType: callbackHitType,\n })\n\n const start = performance.now()\n let errorMessage: string | null = null\n\n try {\n await entry.callback(entry.state)\n } catch (error) {\n errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`Error in callback for element ${entry.state.name}:`, error)\n }\n\n const status: callbackStatus = errorMessage !== null ? \"error\" : \"success\"\n this.finalizeCallback(entry, callbackHitType, start, status, errorMessage)\n }\n\n private finalizeCallback(\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType,\n startTime: number,\n status: callbackStatus,\n errorMessage: string | null\n ): void {\n const elapsed = performance.now() - startTime\n\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n this.currentlyActiveHandler?.unobserveElement(entry.element)\n\n entry.completedAt = Date.now()\n const next = this.updateElementState(entry, {\n isCallbackRunning: false,\n isActive: false,\n durationMs: elapsed,\n status,\n error: errorMessage,\n })\n\n if (next.reactivateAfter !== Infinity) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(entry.element)\n }, next.reactivateAfter)\n }\n\n const isLastActiveElement = this.activeElementCount === 0\n this.removeGlobalListenersIfIdle()\n\n this.eventEmitter.emit({\n type: \"callbackCompleted\",\n timestamp: Date.now(),\n element: entry.element,\n state: next,\n hitType: callbackHitType,\n elapsed,\n status,\n errorMessage,\n wasLastActiveElement: isLastActiveElement,\n })\n }\n\n private updateHitCounters(callbackHitType: CallbackHitType): void {\n switch (callbackHitType.kind) {\n case \"mouse\":\n this._globalCallbackHits.mouse[callbackHitType.subType]++\n break\n case \"tab\":\n this._globalCallbackHits.tab[callbackHitType.subType]++\n break\n case \"scroll\":\n this._globalCallbackHits.scroll[callbackHitType.subType]++\n break\n case \"touch\":\n this._globalCallbackHits.touch++\n break\n case \"viewport\":\n this._globalCallbackHits.viewport++\n break\n default:\n callbackHitType satisfies never\n }\n this._globalCallbackHits.total++\n }\n\n private async setDeviceStrategy(strategy: CurrentDeviceStrategy): Promise<void> {\n const previousStrategy = this.currentDeviceStrategy\n\n if (previousStrategy !== strategy) {\n this.devLog(`Switching device strategy from ${previousStrategy} to ${strategy}`)\n }\n\n this.currentlyActiveHandler?.disconnect()\n\n // Lazy load the handler\n this.currentlyActiveHandler =\n strategy === \"mouse\" || strategy === \"pen\"\n ? await this.getOrCreateDesktopHandler()\n : await this.getOrCreateTouchHandler()\n\n this.currentlyActiveHandler.connect()\n }\n\n private handlePointerMove = (e: PointerEvent): void => {\n this.pendingPointerEvent = e\n\n if (e.pointerType !== this.currentDeviceStrategy) {\n this.eventEmitter.emit({\n type: \"deviceStrategyChanged\",\n timestamp: Date.now(),\n newStrategy: e.pointerType as CurrentDeviceStrategy,\n oldStrategy: this.currentDeviceStrategy,\n })\n\n this.currentDeviceStrategy = e.pointerType as CurrentDeviceStrategy\n this.setDeviceStrategy(this.currentDeviceStrategy)\n }\n\n if (this.rafId) {\n return\n }\n\n this.rafId = requestAnimationFrame(() => {\n // Only process mouse movements for desktop handler (mouse/pen)\n if (!this.isUsingDesktopHandler) {\n this.rafId = null\n\n return\n }\n\n if (this.pendingPointerEvent) {\n this.desktopHandler?.processMouseMovement(this.pendingPointerEvent)\n }\n\n this.rafId = null\n })\n }\n\n private initializeGlobalListeners(): void {\n if (this.isSetup || typeof document === \"undefined\") {\n return\n }\n\n this.devLog(\"Initializing global listeners (pointermove, MutationObserver)\")\n this.setDeviceStrategy(this.currentDeviceStrategy)\n\n document.addEventListener(\"pointermove\", this.handlePointerMove)\n\n this.domObserver = new MutationObserver(this.handleDomMutations)\n this.domObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: false,\n })\n\n this.isSetup = true\n }\n\n private removeGlobalListeners(): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n this.isSetup = false\n this.domObserver?.disconnect()\n this.domObserver = null\n\n document.removeEventListener(\"pointermove\", this.handlePointerMove)\n this.currentlyActiveHandler?.disconnect()\n\n if (this.rafId) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n\n this.pendingPointerEvent = null\n }\n\n private handleDomMutations = (mutationsList: MutationRecord[]): void => {\n if (!mutationsList.length) {\n return\n }\n\n this.desktopHandler?.invalidateTabCache()\n\n let hasChildListChange = false\n for (let i = 0; i < mutationsList.length; i++) {\n const mutation = mutationsList[i]\n if (\n mutation &&\n mutation.type === \"childList\" &&\n (mutation.removedNodes.length > 0 || mutation.addedNodes.length > 0)\n ) {\n hasChildListChange = true\n break\n }\n }\n\n if (!hasChildListChange) {\n return\n }\n\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isParked) {\n if (entry.element.isConnected) {\n this.resumeReconnected(entry)\n }\n } else if (!entry.element.isConnected) {\n this.parkDisconnected(entry)\n }\n }\n }\n\n /**\n * Deactivate an element that was detached from the DOM. It is kept in\n * {@link elementEntries} (still registered) and flagged `isParked` so it can be\n * resumed when it reconnects.\n */\n private parkDisconnected(entry: ForesightElementInternal): void {\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(entry.element)\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n this.parkedElementCount++\n // Preserve `isPredicted`: an element that already fired its callback must stay\n // \"fired\" so it is not treated as fresh (and reactivated) when it reconnects.\n this.updateElementState(entry, {\n isActive: false,\n isCallbackRunning: false,\n isParked: true,\n })\n }\n\n /**\n * Re-activate a previously parked element once it is back in the DOM. Mirrors\n * the activation rules used everywhere else: disabled / limited connections stay\n * inactive, and an element that already fired its callback stays inactive too\n * (it resumes the same state it had before it detached).\n */\n private resumeReconnected(entry: ForesightElementInternal): void {\n this.parkedElementCount--\n\n const eligible = entry.state.isEnabled && !entry.state.isLimitedConnection\n // Only resume as active if it was active before detaching. A fired element\n // (isPredicted) was already inactive, so it stays inactive on reconnect.\n const isActive = eligible && !entry.state.isPredicted\n if (isActive) {\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(entry.element)\n }\n\n this.updateElementState(entry, { isActive, isParked: false })\n\n // If it fired with a finite reactivateAfter, resume the reactivation timer that\n // was cleared when it parked, so the cooldown continues from reconnect.\n if (eligible && entry.state.isPredicted && entry.state.reactivateAfter !== Infinity) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(entry.element)\n }, entry.state.reactivateAfter)\n }\n }\n\n /**\n * Tear down global listeners only when nothing needs them: no active elements\n * to predict on, and no parked elements waiting to reconnect (which need the\n * MutationObserver to detect their return).\n */\n private removeGlobalListenersIfIdle(): void {\n if (this.activeElementCount > 0 || this.parkedElementCount > 0) {\n return\n }\n\n this.removeGlobalListeners()\n }\n\n public alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void {\n const result = applySettingsChanges(this._globalSettings, props)\n\n if (result.positionHistorySizeChanged && this.desktopHandler) {\n this.desktopHandler.trajectoryPositions.positions.resize(\n this._globalSettings.positionHistorySize\n )\n }\n\n if (result.scrollPredictionChanged && this.isUsingDesktopHandler && this.desktopHandler) {\n if (this._globalSettings.enableScrollPrediction) {\n this.desktopHandler.connectScrollPredictor()\n } else {\n this.desktopHandler.disconnectScrollPredictor()\n }\n }\n\n if (result.tabPredictionChanged && this.isUsingDesktopHandler && this.desktopHandler) {\n if (this._globalSettings.enableTabPrediction) {\n this.desktopHandler.connectTabPredictor()\n } else {\n this.desktopHandler.disconnectTabPredictor()\n }\n }\n\n if (result.hitSlopChanged) {\n this.forceUpdateAllElementBounds()\n }\n\n if (result.touchStrategyChanged && !this.isUsingDesktopHandler && this.touchDeviceHandler) {\n this.touchDeviceHandler.setTouchPredictor()\n }\n\n if (result.changedSettings.length > 0) {\n this.eventEmitter.emit({\n type: \"managerSettingsChanged\",\n timestamp: Date.now(),\n managerData: this.getManagerData,\n updatedSettings: result.changedSettings,\n })\n }\n }\n\n private forceUpdateAllElementBounds(): void {\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isIntersectingWithViewport) {\n this.forceUpdateElementBounds(entry)\n }\n }\n }\n\n /**\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.\n * We need an observer for that\n */\n private forceUpdateElementBounds(entry: ForesightElementInternal): void {\n const newOriginalRect = entry.element.getBoundingClientRect()\n const expandedRect = getExpandedRect(newOriginalRect, entry.state.elementBounds.hitSlop)\n\n if (areRectsEqual(expandedRect, entry.state.elementBounds.expandedRect)) {\n return\n }\n\n this.updateElementState(entry, {\n elementBounds: {\n hitSlop: entry.state.elementBounds.hitSlop,\n originalRect: newOriginalRect,\n expandedRect,\n },\n })\n }\n\n private devLog(message: string): void {\n if (this._globalSettings.enableManagerLogging) {\n console.log(`%c🛠️ ForesightManager: ${message}`, \"color: #16a34a; font-weight: bold;\")\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/helpers/DerivedMapView.ts","../src/helpers/shouldRegister.ts","../src/helpers/initialViewportState.ts","../src/helpers/createInitialState.ts","../src/core/ForesightEventEmitter.ts","../src/helpers/shouldUpdateSetting.ts","../src/managers/SettingsManager.ts","../src/managers/ForesightManager.ts"],"sourcesContent":["/**\n * Read-only map that exposes external state derived from internal state,\n * so internals are never leaked to consumers.\n */\nexport class DerivedMapView<K, TSource, TDerived> implements ReadonlyMap<K, TDerived> {\n constructor(\n private source: Map<K, TSource>,\n private derive: (value: TSource) => TDerived\n ) {}\n\n get size() {\n return this.source.size\n }\n\n get(key: K): TDerived | undefined {\n const entry = this.source.get(key)\n\n return entry === undefined ? undefined : this.derive(entry)\n }\n\n has(key: K): boolean {\n return this.source.has(key)\n }\n\n forEach(cb: (value: TDerived, key: K, map: ReadonlyMap<K, TDerived>) => void): void {\n this.source.forEach((entry, key) => cb(this.derive(entry), key, this))\n }\n\n *entries(): MapIterator<[K, TDerived]> {\n for (const [key, entry] of this.source) {\n yield [key, this.derive(entry)]\n }\n }\n\n *values(): MapIterator<TDerived> {\n for (const entry of this.source.values()) {\n yield this.derive(entry)\n }\n }\n\n keys(): MapIterator<K> {\n return this.source.keys()\n }\n\n [Symbol.iterator](): MapIterator<[K, TDerived]> {\n return this.entries()\n }\n\n get [Symbol.toStringTag](): string {\n return \"DerivedMapView\"\n }\n}\n","import { MinimumConnectionType } from \"../types/types\"\n\ntype ShouldRegister = {\n shouldRegister: boolean\n isTouchDevice: boolean\n isLimitedConnection: boolean\n}\n\nexport const evaluateRegistrationConditions = (\n minimumConnectionType: MinimumConnectionType\n): ShouldRegister => {\n const isTouchDevice = userUsesTouchDevice()\n const isLimitedConnection = hasConnectionLimitations(minimumConnectionType)\n const shouldRegister = !isLimitedConnection\n\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 */\nexport const userUsesTouchDevice = (): boolean => {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n return false\n }\n\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 */\nconst hasConnectionLimitations = (minimumConnectionType: MinimumConnectionType): boolean => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const connection = (navigator as any).connection\n if (!connection) {\n return false\n }\n\n // Define array of connection types from slowest to fastest\n const connectionTypes: MinimumConnectionType[] = [\"slow-2g\", \"2g\", \"3g\", \"4g\"]\n // Get index of user's current connection speed in the array (e.g. \"4g\" would be index 3)\n const currentConnectionIndex = connectionTypes.indexOf(\n connection.effectiveType as MinimumConnectionType\n )\n // Get index of the minimum connection speed required in settings (e.g. \"3g\" would be index 2)\n const minimumConnectionIndex = connectionTypes.indexOf(minimumConnectionType)\n\n // If user's connection is slower than the minimum required, or data saver is enabled, return true\n return currentConnectionIndex < minimumConnectionIndex || connection.saveData\n}\n","export const initialViewportState = (rect: DOMRect) => {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return false\n }\n\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 {\n DEFAULT_ENABLE_MOUSE_PREDICTION,\n DEFAULT_ENABLE_SCROLL_PREDICTION,\n DEFAULT_ENABLE_TAB_PREDICTION,\n DEFAULT_HITSLOP,\n DEFAULT_POSITION_HISTORY_SIZE,\n DEFAULT_SCROLL_MARGIN,\n DEFAULT_STALE_TIME,\n DEFAULT_TAB_OFFSET,\n DEFAULT_TRAJECTORY_PREDICTION_TIME,\n} from \"../constants\"\nimport type {\n CallbackHits,\n ForesightElementInternal,\n ForesightElementState,\n ForesightManagerSettings,\n ForesightRegisterOptions,\n HitSlop,\n} from \"../types/types\"\nimport { getExpandedRect, normalizeHitSlop } from \"./rectAndHitSlop\"\nimport { initialViewportState } from \"./initialViewportState\"\n\nexport const createInitialCallbackHits = (): CallbackHits => {\n return {\n mouse: {\n hover: 0,\n trajectory: 0,\n },\n tab: {\n forwards: 0,\n reverse: 0,\n },\n scroll: {\n down: 0,\n left: 0,\n right: 0,\n up: 0,\n },\n touch: 0,\n viewport: 0,\n total: 0,\n }\n}\n\nexport const createDefaultManagerSettings = (): ForesightManagerSettings => {\n return {\n enableManagerLogging: false,\n enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,\n enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,\n positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,\n trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,\n scrollMargin: DEFAULT_SCROLL_MARGIN,\n defaultHitSlop: {\n top: DEFAULT_HITSLOP,\n left: DEFAULT_HITSLOP,\n right: DEFAULT_HITSLOP,\n bottom: DEFAULT_HITSLOP,\n },\n enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,\n tabOffset: DEFAULT_TAB_OFFSET,\n touchDeviceStrategy: \"onTouchStart\",\n minimumConnectionType: \"3g\",\n }\n}\n\n/**\n * Creates the internal record for a newly registered element, including the\n * initial immutable state snapshot.\n */\nexport const createElementInternal = (\n options: ForesightRegisterOptions,\n id: string,\n defaultHitSlop: Exclude<HitSlop, number>,\n isLimitedConnection: boolean\n): ForesightElementInternal => {\n const { element, callback, hitSlop, name, meta, reactivateAfter, enabled } = options\n\n const initialRect = element.getBoundingClientRect()\n const normalizedHitSlop = hitSlop !== undefined ? normalizeHitSlop(hitSlop) : defaultHitSlop\n const isEnabled = enabled !== false\n\n const state: ForesightElementState = {\n id,\n name: name || element.id || \"unnamed\",\n meta: meta ?? {},\n hitSlop: normalizedHitSlop,\n isLimitedConnection,\n isIntersectingWithViewport: initialViewportState(initialRect),\n isRegistered: true,\n isActive: isEnabled && !isLimitedConnection,\n isParked: false,\n isEnabled,\n isPredicted: false,\n isCallbackRunning: false,\n hitCount: 0,\n registerCount: 1,\n durationMs: undefined,\n status: undefined,\n error: null,\n reactivateAfter: reactivateAfter ?? DEFAULT_STALE_TIME,\n }\n\n return {\n state,\n bounds: {\n originalRect: initialRect,\n expandedRect: getExpandedRect(initialRect, normalizedHitSlop),\n },\n invokedAt: undefined,\n completedAt: undefined,\n element,\n callback,\n reactivateTimeoutId: undefined,\n subscribers: new Set(),\n boundsSubscribers: new Set(),\n }\n}\n\n/**\n * Snapshot of the flat state shape for an element that is not (yet) tracked by the\n * manager. Reuses the same fields as a registered element so consumers don't need\n * to special-case `null`.\n *\n * Used in two situations:\n * 1. The manager refuses to register the element (touch device, limited connection,\n * etc.) - pass `isLimitedConnection` to reflect that.\n * 2. Framework wrappers (React, Vue) need an initial snapshot before `register()`\n * has run. `register()` requires a real DOM element, which only exists after\n * the consumer's first render commits, so the wrapper returns this snapshot\n * during that brief window. Pass `isLimitedConnection: false` for this case.\n */\nexport const createUnregisteredSnapshot = (isLimitedConnection: boolean): ForesightElementState => {\n return {\n id: \"\",\n name: \"\",\n meta: {},\n // Fresh object per call - Vue wraps the snapshot reactively.\n hitSlop: { top: 0, left: 0, right: 0, bottom: 0 },\n isLimitedConnection,\n isIntersectingWithViewport: false,\n isRegistered: false,\n isActive: false,\n isParked: false,\n isEnabled: false,\n isPredicted: false,\n isCallbackRunning: false,\n hitCount: 0,\n registerCount: 0,\n durationMs: undefined,\n status: undefined,\n error: null,\n reactivateAfter: DEFAULT_STALE_TIME,\n }\n}\n","import type { ForesightEvent, ForesightEventListener, ForesightEventMap } from \"../types/types\"\n\n/**\n * Generic event emitter for ForesightJS events.\n * Handles event registration, removal, and emission with error handling.\n */\nexport class ForesightEventEmitter {\n private eventListeners: Map<ForesightEvent, ForesightEventListener[]> = new Map()\n\n public addEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>,\n options?: { signal?: AbortSignal }\n ): void {\n if (options?.signal?.aborted) {\n return\n }\n\n const listeners = this.eventListeners.get(eventType) ?? []\n listeners.push(listener as ForesightEventListener)\n this.eventListeners.set(eventType, listeners)\n\n options?.signal?.addEventListener(\"abort\", () => this.removeEventListener(eventType, listener))\n }\n\n public removeEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>\n ): void {\n const listeners = this.eventListeners.get(eventType)\n\n if (!listeners) {\n return\n }\n\n const index = listeners.indexOf(listener as ForesightEventListener)\n if (index > -1) {\n listeners.splice(index, 1)\n }\n }\n\n /**\n * Emit an event to all registered listeners.\n * Errors in listeners are caught and logged, not propagated.\n */\n public emit<K extends ForesightEvent>(event: ForesightEventMap[K]): void {\n const listeners = this.eventListeners.get(event.type)\n\n if (!listeners || listeners.length === 0) {\n return\n }\n\n for (let i = 0; i < listeners.length; i++) {\n try {\n const listener = listeners[i]\n if (listener) {\n listener(event)\n }\n } catch (error) {\n console.error(`Error in ForesightManager event listener ${i} for ${event.type}:`, error)\n }\n }\n }\n\n /**\n * Check if there are any listeners registered for a specific event type.\n * Useful for avoiding expensive event object creation when no one is listening.\n */\n public hasListeners<K extends ForesightEvent>(eventType: K): boolean {\n const listeners = this.eventListeners.get(eventType)\n\n return listeners !== undefined && listeners.length > 0\n }\n\n public getEventListeners(): ReadonlyMap<ForesightEvent, ForesightEventListener[]> {\n return this.eventListeners\n }\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 const 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 {\n MAX_POSITION_HISTORY_SIZE,\n MAX_SCROLL_MARGIN,\n MAX_TAB_OFFSET,\n MAX_TRAJECTORY_PREDICTION_TIME,\n MIN_POSITION_HISTORY_SIZE,\n MIN_SCROLL_MARGIN,\n MIN_TAB_OFFSET,\n MIN_TRAJECTORY_PREDICTION_TIME,\n} from \"../constants\"\nimport { clampNumber } from \"../helpers/clampNumber\"\nimport { areRectsEqual, normalizeHitSlop } from \"../helpers/rectAndHitSlop\"\nimport { shouldUpdateSetting } from \"../helpers/shouldUpdateSetting\"\nimport type {\n ForesightManagerSettings,\n ManagerBooleanSettingKeys,\n NumericSettingKeys,\n UpdatedManagerSetting,\n UpdateForsightManagerSettings,\n} from \"../types/types\"\n\n/** Configuration for numeric settings with their min/max constraints */\nconst NUMERIC_SETTING_CONFIGS: Record<NumericSettingKeys, { min: number; max: number }> = {\n trajectoryPredictionTime: {\n min: MIN_TRAJECTORY_PREDICTION_TIME,\n max: MAX_TRAJECTORY_PREDICTION_TIME,\n },\n positionHistorySize: {\n min: MIN_POSITION_HISTORY_SIZE,\n max: MAX_POSITION_HISTORY_SIZE,\n },\n scrollMargin: {\n min: MIN_SCROLL_MARGIN,\n max: MAX_SCROLL_MARGIN,\n },\n tabOffset: {\n min: MIN_TAB_OFFSET,\n max: MAX_TAB_OFFSET,\n },\n}\n\n/**\n * Updates a numeric setting with clamping.\n * Returns true if the setting was changed.\n */\nconst updateNumericSetting = (\n settings: ForesightManagerSettings,\n key: NumericSettingKeys,\n newValue: number | undefined\n): boolean => {\n if (!shouldUpdateSetting(newValue, settings[key])) {\n return false\n }\n\n const { min, max } = NUMERIC_SETTING_CONFIGS[key]\n settings[key] = clampNumber(newValue, min, max, key)\n\n return true\n}\n\n/**\n * Updates a boolean setting.\n * Returns true if the setting was changed.\n */\nconst updateBooleanSetting = (\n settings: ForesightManagerSettings,\n key: ManagerBooleanSettingKeys,\n newValue: boolean | undefined\n): boolean => {\n if (!shouldUpdateSetting(newValue, settings[key])) {\n return false\n }\n\n settings[key] = newValue as boolean\n\n return true\n}\n\n/**\n * Apply initial settings during construction (no event emission, no side effects).\n * Mutates the settings object directly.\n */\nexport const initializeSettings = (\n settings: ForesightManagerSettings,\n props: Partial<UpdateForsightManagerSettings>\n): void => {\n // Numeric settings\n updateNumericSetting(settings, \"trajectoryPredictionTime\", props.trajectoryPredictionTime)\n updateNumericSetting(settings, \"positionHistorySize\", props.positionHistorySize)\n updateNumericSetting(settings, \"scrollMargin\", props.scrollMargin)\n updateNumericSetting(settings, \"tabOffset\", props.tabOffset)\n\n // Boolean settings\n updateBooleanSetting(settings, \"enableMousePrediction\", props.enableMousePrediction)\n updateBooleanSetting(settings, \"enableScrollPrediction\", props.enableScrollPrediction)\n updateBooleanSetting(settings, \"enableTabPrediction\", props.enableTabPrediction)\n updateBooleanSetting(settings, \"enableManagerLogging\", props.enableManagerLogging)\n\n // Object/special settings\n if (props.defaultHitSlop !== undefined) {\n settings.defaultHitSlop = normalizeHitSlop(props.defaultHitSlop)\n }\n\n if (props.touchDeviceStrategy !== undefined) {\n settings.touchDeviceStrategy = props.touchDeviceStrategy\n }\n\n if (props.minimumConnectionType !== undefined) {\n settings.minimumConnectionType = props.minimumConnectionType\n }\n}\n\n/**\n * Result of applying settings changes at runtime.\n * Contains the list of changed settings for event emission.\n */\ninterface SettingsChangeResult {\n changedSettings: UpdatedManagerSetting[]\n positionHistorySizeChanged: boolean\n scrollPredictionChanged: boolean\n tabPredictionChanged: boolean\n hitSlopChanged: boolean\n touchStrategyChanged: boolean\n}\n\n/**\n * Apply settings changes at runtime.\n * Returns information about what changed for the caller to handle side effects.\n */\nexport const applySettingsChanges = (\n settings: ForesightManagerSettings,\n props: Partial<UpdateForsightManagerSettings> | undefined\n): SettingsChangeResult => {\n const changedSettings: UpdatedManagerSetting[] = []\n let positionHistorySizeChanged = false\n let scrollPredictionChanged = false\n let tabPredictionChanged = false\n let hitSlopChanged = false\n let touchStrategyChanged = false\n\n if (!props) {\n return {\n changedSettings,\n positionHistorySizeChanged,\n scrollPredictionChanged,\n tabPredictionChanged,\n hitSlopChanged,\n touchStrategyChanged,\n }\n }\n\n // Numeric settings\n const numericKeys: NumericSettingKeys[] = [\n \"trajectoryPredictionTime\",\n \"positionHistorySize\",\n \"scrollMargin\",\n \"tabOffset\",\n ]\n\n for (const key of numericKeys) {\n const oldValue = settings[key]\n if (updateNumericSetting(settings, key, props[key])) {\n changedSettings.push({\n setting: key,\n oldValue,\n newValue: settings[key],\n } as UpdatedManagerSetting)\n\n if (key === \"positionHistorySize\") {\n positionHistorySizeChanged = true\n }\n }\n }\n\n // Boolean settings\n const booleanKeys: ManagerBooleanSettingKeys[] = [\n \"enableMousePrediction\",\n \"enableScrollPrediction\",\n \"enableTabPrediction\",\n ]\n\n for (const key of booleanKeys) {\n const oldValue = settings[key]\n if (updateBooleanSetting(settings, key, props[key])) {\n changedSettings.push({\n setting: key,\n oldValue,\n newValue: settings[key],\n } as UpdatedManagerSetting)\n\n if (key === \"enableScrollPrediction\") {\n scrollPredictionChanged = true\n }\n\n if (key === \"enableTabPrediction\") {\n tabPredictionChanged = true\n }\n }\n }\n\n // HitSlop\n if (props.defaultHitSlop !== undefined) {\n const oldHitSlop = settings.defaultHitSlop\n const normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop)\n\n if (!areRectsEqual(oldHitSlop, normalizedNewHitSlop)) {\n settings.defaultHitSlop = normalizedNewHitSlop\n changedSettings.push({\n setting: \"defaultHitSlop\",\n oldValue: oldHitSlop,\n newValue: normalizedNewHitSlop,\n })\n hitSlopChanged = true\n }\n }\n\n // Touch strategy\n if (props.touchDeviceStrategy !== undefined) {\n const oldValue = settings.touchDeviceStrategy\n settings.touchDeviceStrategy = props.touchDeviceStrategy\n changedSettings.push({\n setting: \"touchDeviceStrategy\",\n oldValue,\n newValue: props.touchDeviceStrategy,\n })\n touchStrategyChanged = true\n }\n\n // Minimum connection type\n if (props.minimumConnectionType !== undefined) {\n const oldValue = settings.minimumConnectionType\n settings.minimumConnectionType = props.minimumConnectionType\n changedSettings.push({\n setting: \"minimumConnectionType\",\n oldValue,\n newValue: props.minimumConnectionType,\n })\n }\n\n return {\n changedSettings,\n positionHistorySizeChanged,\n scrollPredictionChanged,\n tabPredictionChanged,\n hitSlopChanged,\n touchStrategyChanged,\n }\n}\n","import { DerivedMapView } from \"../helpers/DerivedMapView\"\nimport { areRectsEqual, getExpandedRect, normalizeHitSlop } from \"../helpers/rectAndHitSlop\"\nimport { evaluateRegistrationConditions, userUsesTouchDevice } from \"../helpers/shouldRegister\"\nimport {\n createDefaultManagerSettings,\n createElementInternal,\n createInitialCallbackHits,\n} from \"../helpers/createInitialState\"\nimport { ForesightEventEmitter } from \"../core/ForesightEventEmitter\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport type { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport { applySettingsChanges, initializeSettings } from \"./SettingsManager\"\nimport type {\n CallbackHits,\n CallbackHitType,\n callbackStatus,\n CurrentDeviceStrategy,\n ElementBounds,\n ElementUnregisteredReason,\n ForesightElement,\n ForesightElementInternal,\n ForesightElementState,\n ForesightEvent,\n ForesightEventListener,\n ForesightManagerData,\n ForesightManagerSettings,\n ForesightModules,\n ForesightRegisterNodeListOptions,\n ForesightRegisterOptions,\n ForesightRegisterOptionsWithoutElement,\n ForesightRegisterResult,\n UpdateForsightManagerSettings,\n} from \"../types/types\"\nimport type { DesktopHandler } from \"./DesktopHandler\"\nimport type { TouchDeviceHandler } from \"./TouchDeviceHandler\"\n\n/**\n * Manages the prediction of user intent based on mouse trajectory and element interactions.\n *\n * ForesightManager is a singleton class responsible for:\n * - Registering HTML elements to monitor.\n * - Tracking mouse movements and predicting future cursor positions.\n * - Detecting when a predicted trajectory intersects with a registered element's bounds.\n * - Invoking callbacks associated with elements upon predicted or actual interaction.\n * - Deactivating elements after their callback completes, with optional reactivation via `reactivateAfter`.\n * - Handling global settings for prediction behavior (e.g., history size, prediction time).\n * - Delegating element bounds observation to device handlers ({@link DesktopHandler}, {@link TouchDeviceHandler}).\n * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.\n * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.\n *\n * It should be initialized once using {@link ForesightManager.initialize} and then\n * accessed via the static getter {@link ForesightManager.instance}.\n */\nexport class ForesightManager {\n private static manager: ForesightManager\n\n /** Internal entries containing full element data, callbacks, and subscribers. */\n private elementEntries: Map<ForesightElement, ForesightElementInternal> = new Map()\n /** Public read-only view exposing only external state, derived from {@link elementEntries}. */\n public readonly registeredElements: ReadonlyMap<ForesightElement, ForesightElementState> =\n new DerivedMapView(this.elementEntries, (entry: ForesightElementInternal) => entry.state)\n\n private idCounter: number = 0\n private activeElementCount: number = 0\n private parkedElementCount: number = 0\n\n private desktopHandler: DesktopHandler | null = null\n private touchDeviceHandler: TouchDeviceHandler | null = null\n private currentlyActiveHandler: ElementObservingModule | null = null\n private handlerDependencies: ForesightModuleDependencies\n\n private isSetup: boolean = false\n private pendingPointerEvent: PointerEvent | null = null\n private rafId: number | null = null\n private domObserver: MutationObserver | null = null\n private currentDeviceStrategy: CurrentDeviceStrategy = userUsesTouchDevice() ? \"touch\" : \"mouse\"\n\n private eventEmitter = new ForesightEventEmitter()\n private _globalCallbackHits: CallbackHits = createInitialCallbackHits()\n private _globalSettings: ForesightManagerSettings = createDefaultManagerSettings()\n\n private constructor(initialSettings?: Partial<UpdateForsightManagerSettings>) {\n if (initialSettings !== undefined) {\n initializeSettings(this._globalSettings, initialSettings)\n }\n\n this.handlerDependencies = {\n elements: this.elementEntries,\n callCallback: this.callCallback.bind(this),\n emit: this.eventEmitter.emit.bind(this.eventEmitter),\n hasListeners: this.eventEmitter.hasListeners.bind(this.eventEmitter),\n updateElementState: this.updateElementState.bind(this),\n updateElementBounds: this.updateElementBounds.bind(this),\n settings: this._globalSettings,\n }\n\n // Handlers are created lazily when first needed as of 3.4.0\n this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`)\n this.initializeGlobalListeners()\n }\n\n private async getOrCreateDesktopHandler(): Promise<DesktopHandler> {\n if (!this.desktopHandler) {\n const { DesktopHandler } = await import(\"./DesktopHandler\")\n this.desktopHandler = new DesktopHandler(this.handlerDependencies)\n this.devLog(\"DesktopHandler lazy loaded\")\n }\n\n return this.desktopHandler\n }\n\n private async getOrCreateTouchHandler(): Promise<TouchDeviceHandler> {\n if (!this.touchDeviceHandler) {\n const { TouchDeviceHandler } = await import(\"./TouchDeviceHandler\")\n this.touchDeviceHandler = new TouchDeviceHandler(this.handlerDependencies)\n this.devLog(\"TouchDeviceHandler lazy loaded\")\n }\n\n return this.touchDeviceHandler\n }\n\n public static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager {\n if (!this.isInitiated) {\n ForesightManager.manager = new ForesightManager(props)\n }\n\n return ForesightManager.manager\n }\n\n public static get isInitiated(): Readonly<boolean> {\n return !!ForesightManager.manager\n }\n\n public static get instance(): ForesightManager {\n return this.initialize()\n }\n\n private generateId(): string {\n return `foresight-${++this.idCounter}`\n }\n\n private get isUsingDesktopHandler(): boolean {\n return this.currentDeviceStrategy === \"mouse\" || this.currentDeviceStrategy === \"pen\"\n }\n\n public addEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>,\n options?: { signal?: AbortSignal }\n ): void {\n this.eventEmitter.addEventListener(eventType, listener, options)\n }\n\n public removeEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>\n ): void {\n this.eventEmitter.removeEventListener(eventType, listener)\n }\n\n public hasListeners<K extends ForesightEvent>(eventType: K): boolean {\n return this.eventEmitter.hasListeners(eventType)\n }\n\n /**\n * Subscribe to logical state changes for a specific element.\n * The listener is called (with no arguments) whenever the element's\n * immutable state snapshot is replaced. Never fires for geometry-only\n * changes (scroll/resize) - see {@link subscribeToElementBounds}.\n * Use {@link registeredElements} to read the latest state inside the listener.\n *\n * @returns An unsubscribe function, or `undefined` if the element is not registered.\n */\n public subscribeToElement(\n element: ForesightElement,\n listener: () => void\n ): (() => void) | undefined {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return undefined\n }\n\n return this.makeSubscribe(entry.subscribers)(listener)\n }\n\n /**\n * Subscribe to geometry changes for a specific element (position/size, fired\n * on every scroll/resize tick while visible). Use {@link getElementBounds}\n * to read the latest geometry inside the listener.\n *\n * @returns An unsubscribe function, or `undefined` if the element is not registered.\n */\n public subscribeToElementBounds(\n element: ForesightElement,\n listener: () => void\n ): (() => void) | undefined {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return undefined\n }\n\n return this.makeSubscribe(entry.boundsSubscribers)(listener)\n }\n\n /**\n * Returns the current immutable geometry snapshot for a registered element,\n * or `undefined` if the element is not registered.\n */\n public getElementBounds(element: ForesightElement): ElementBounds | undefined {\n return this.elementEntries.get(element)?.bounds\n }\n\n public get getManagerData(): Readonly<ForesightManagerData> {\n return {\n registeredElements: this.registeredElements,\n globalSettings: this._globalSettings,\n globalCallbackHits: this._globalCallbackHits,\n eventListeners: this.eventEmitter.getEventListeners(),\n currentDeviceStrategy: this.currentDeviceStrategy,\n activeElementCount: this.activeElementCount,\n parkedElementCount: this.parkedElementCount,\n loadedModules: this.getLoadedModulesSnapshot(),\n }\n }\n\n private getLoadedModulesSnapshot(): ForesightModules {\n const desktopPredictors = this.desktopHandler?.loadedPredictors\n const touchPredictors = this.touchDeviceHandler?.loadedPredictors\n\n return {\n desktopHandler: this.desktopHandler !== null,\n touchHandler: this.touchDeviceHandler !== null,\n predictors: {\n mouse: desktopPredictors?.mouse ?? false,\n tab: desktopPredictors?.tab ?? false,\n scroll: desktopPredictors?.scroll ?? false,\n viewport: touchPredictors?.viewport ?? false,\n touchStart: touchPredictors?.touchStart ?? false,\n },\n }\n }\n\n public register(options: ForesightRegisterNodeListOptions): ForesightRegisterResult[]\n public register(options: ForesightRegisterOptions): ForesightRegisterResult\n public register(\n options: ForesightRegisterOptions | ForesightRegisterNodeListOptions\n ): ForesightRegisterResult | ForesightRegisterResult[] {\n const { element: elements, ...rest } = options\n\n if (elements instanceof NodeList) {\n return Array.from(elements, element => this.registerElement({ ...rest, element }))\n }\n\n return this.registerElement({ ...rest, element: elements })\n }\n\n private registerElement(options: ForesightRegisterOptions): ForesightRegisterResult {\n // On a limited connection (data saver / slow network) the element is\n // registered so it can be patched and tracked, but stays inactive (never\n // predicted, never fires its callback) to avoid consuming data.\n // See createElementInternal / setElementEnabled.\n const { isLimitedConnection } = evaluateRegistrationConditions(\n this._globalSettings.minimumConnectionType\n )\n\n const previousEntry = this.elementEntries.get(options.element)\n\n if (previousEntry) {\n this.updateElementOptions(options.element, options)\n this.updateElementState(previousEntry, {\n registerCount: previousEntry.state.registerCount + 1,\n })\n\n return {\n ...previousEntry.state,\n unregister: () => {\n this.unregister(options.element)\n },\n subscribe: this.makeSubscribe(previousEntry.subscribers),\n getSnapshot: () => previousEntry.state,\n subscribeToBounds: this.makeSubscribe(previousEntry.boundsSubscribers),\n getBounds: () => previousEntry.bounds,\n }\n }\n\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n const entry = createElementInternal(\n options,\n this.generateId(),\n this._globalSettings.defaultHitSlop,\n isLimitedConnection\n )\n\n this.elementEntries.set(options.element, entry)\n\n // Inactive elements (disabled or limited connection) are not observed or\n // counted as active until they are (re)activated.\n if (entry.state.isActive) {\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(options.element)\n }\n\n this.eventEmitter.emit({\n type: \"elementRegistered\",\n timestamp: Date.now(),\n element: options.element,\n state: entry.state,\n })\n\n return {\n ...entry.state,\n unregister: () => {\n this.unregister(options.element)\n },\n subscribe: this.makeSubscribe(entry.subscribers),\n getSnapshot: () => entry.state,\n subscribeToBounds: this.makeSubscribe(entry.boundsSubscribers),\n getBounds: () => entry.bounds,\n }\n }\n\n /**\n * Updates the options of an already-registered element.\n * Only the provided fields are updated; omitted fields keep their current values.\n * If a reactivation timeout is pending and reactivateAfter changed, the timeout is rescheduled.\n *\n * @throws Error if the element is not registered.\n */\n public updateElementOptions(\n element: ForesightElement,\n options: Partial<ForesightRegisterOptionsWithoutElement>\n ): ForesightElementState {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n throw new Error(\"Cannot update options: element is not registered.\")\n }\n\n if (options.callback) {\n entry.callback = options.callback\n }\n\n if (options.enabled !== undefined) {\n this.setElementEnabled(entry, element, options.enabled !== false)\n }\n\n // Keep the current hitSlop reference when it is omitted or content-equal,\n // so updateElementState sees no change; otherwise remeasure and re-expand.\n // Bounds are updated BEFORE state so state subscribers read fresh geometry.\n let hitSlop = entry.state.hitSlop\n if (options.hitSlop !== undefined) {\n const normalized = normalizeHitSlop(options.hitSlop)\n if (!areRectsEqual(normalized, hitSlop)) {\n hitSlop = normalized\n const originalRect = element.getBoundingClientRect()\n this.updateElementBounds(entry, {\n originalRect,\n expandedRect: getExpandedRect(originalRect, hitSlop),\n })\n }\n }\n\n const prevReactivateAfter = entry.state.reactivateAfter\n const reactivateAfter = options.reactivateAfter ?? prevReactivateAfter\n const next = this.updateElementState(entry, {\n name: options.name || entry.state.name,\n meta: options.meta ?? entry.state.meta,\n reactivateAfter,\n hitSlop,\n })\n\n // Only clear and reschedule the reactivation timeout if reactivateAfter actually changed\n if (reactivateAfter !== prevReactivateAfter) {\n if (entry.reactivateTimeoutId !== undefined) {\n this.clearReactivateTimeout(entry)\n }\n\n if (reactivateAfter !== Infinity && next.isPredicted) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(element)\n }, reactivateAfter)\n }\n }\n\n return next\n }\n\n /**\n * Create a subscribe function for a listener set (state or bounds subscribers).\n * Returns an unsubscribe callback when called.\n */\n private makeSubscribe(subscribers: Set<() => void>) {\n return (listener: () => void): (() => void) => {\n subscribers.add(listener)\n\n return () => {\n subscribers.delete(listener)\n }\n }\n }\n\n /**\n * Replace the immutable state ref for an element and notify subscribers.\n * No-op when every patch value already matches current state - preserves the\n * stable-reference contract relied on by useSyncExternalStore and shallowRef.\n */\n private updateElementState(\n entry: ForesightElementInternal,\n patch: Partial<ForesightElementState>\n ): ForesightElementState {\n const current = entry.state\n let changed = false\n for (const key in patch) {\n if (\n patch[key as keyof ForesightElementState] !== current[key as keyof ForesightElementState]\n ) {\n changed = true\n break\n }\n }\n if (!changed) {\n return current\n }\n\n const next = { ...current, ...patch }\n entry.state = next\n for (const listener of entry.subscribers) {\n try {\n listener()\n } catch (error) {\n console.error(`Error in element subscriber for ${next.name}:`, error)\n }\n }\n\n return next\n }\n\n /**\n * Replace the immutable geometry ref for an element and notify bounds\n * subscribers. No-op when both rects are content-equal. Preserves the\n * stable-reference contract, mirroring {@link updateElementState}.\n *\n * When a single trigger changes both geometry and logical state (position\n * change, hitSlop update), bounds must be updated BEFORE the state patch so\n * state subscribers always read fresh geometry.\n */\n private updateElementBounds(entry: ForesightElementInternal, next: ElementBounds): ElementBounds {\n const current = entry.bounds\n if (\n areRectsEqual(next.originalRect, current.originalRect) &&\n areRectsEqual(next.expandedRect, current.expandedRect)\n ) {\n return current\n }\n\n entry.bounds = next\n for (const listener of entry.boundsSubscribers) {\n try {\n listener()\n } catch (error) {\n console.error(`Error in element bounds subscriber for ${entry.state.name}:`, error)\n }\n }\n\n return next\n }\n\n public unregister(\n element: ForesightElement | NodeListOf<ForesightElement>,\n unregisterReason?: ElementUnregisteredReason\n ): void {\n if (element instanceof NodeList) {\n element.forEach(el => this.unregisterElement(el, unregisterReason))\n } else {\n this.unregisterElement(element, unregisterReason)\n }\n }\n\n private unregisterElement(\n element: ForesightElement,\n unregisterReason?: ElementUnregisteredReason\n ): void {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return\n }\n\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(element)\n\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n if (entry.state.isParked) {\n this.parkedElementCount--\n }\n\n const finalState = this.updateElementState(entry, {\n isRegistered: false,\n isActive: false,\n isParked: false,\n isPredicted: false,\n isCallbackRunning: false,\n })\n\n this.elementEntries.delete(element)\n entry.subscribers.clear()\n entry.boundsSubscribers.clear()\n\n const wasLastRegisteredElement = this.elementEntries.size === 0 && this.isSetup\n if (wasLastRegisteredElement) {\n this.devLog(\"All elements unregistered, removing global listeners\")\n this.removeGlobalListeners()\n }\n\n this.eventEmitter.emit({\n type: \"elementUnregistered\",\n element: element,\n state: finalState,\n timestamp: Date.now(),\n unregisterReason: unregisterReason ?? \"by user\",\n wasLastRegisteredElement,\n })\n }\n\n public reactivate(element: ForesightElement | NodeListOf<ForesightElement>): void {\n if (element instanceof NodeList) {\n element.forEach(el => this.reactivateElement(el))\n } else {\n this.reactivateElement(element)\n }\n }\n\n private reactivateElement(element: ForesightElement): void {\n const entry = this.elementEntries.get(element)\n if (!entry || !entry.state.isEnabled) {\n return\n }\n\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.clearReactivateTimeout(entry)\n\n if (entry.state.isCallbackRunning || entry.state.isActive) {\n return\n }\n\n this.updateElementState(entry, { isActive: true, isPredicted: false })\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(element)\n }\n\n /**\n * Toggle prediction for a registered element without unregistering it.\n * Disabling deactivates and stops observing; enabling reactivates it.\n */\n private setElementEnabled(\n entry: ForesightElementInternal,\n element: ForesightElement,\n enabled: boolean\n ): void {\n if (entry.state.isEnabled === enabled) {\n return\n }\n\n // A limited connection keeps the element inactive even when enabled (a data\n // saver never starts firing just because enabled flipped); a parked element\n // (detached from the DOM) stays inactive until it reconnects.\n const isActive = enabled && !entry.state.isLimitedConnection && !entry.state.isParked\n\n if (isActive) {\n // Global listeners may have been torn down when the active count last hit\n // zero; re-arm them so prediction actually resumes.\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(element)\n } else {\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(element)\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n }\n\n this.updateElementState(entry, {\n isEnabled: enabled,\n isActive,\n isPredicted: false,\n isCallbackRunning: false,\n })\n\n // Disabling the last active element leaves nothing to predict on.\n this.removeGlobalListenersIfIdle()\n }\n\n private clearReactivateTimeout(entry: ForesightElementInternal): void {\n clearTimeout(entry.reactivateTimeoutId)\n entry.reactivateTimeoutId = undefined\n }\n\n private callCallback(entry: ForesightElementInternal, callbackHitType: CallbackHitType): void {\n if (entry.state.isPredicted || !entry.state.isActive) {\n return\n }\n\n this.markElementAsRunning(entry)\n this.executeCallbackAsync(entry, callbackHitType)\n }\n\n private markElementAsRunning(entry: ForesightElementInternal): void {\n this.clearReactivateTimeout(entry)\n\n entry.invokedAt = Date.now()\n\n this.updateElementState(entry, {\n isPredicted: true,\n isCallbackRunning: true,\n hitCount: entry.state.hitCount + 1,\n })\n }\n\n private async executeCallbackAsync(\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType\n ): Promise<void> {\n this.updateHitCounters(callbackHitType)\n\n this.eventEmitter.emit({\n type: \"callbackInvoked\",\n timestamp: Date.now(),\n element: entry.element,\n state: entry.state,\n hitType: callbackHitType,\n })\n\n const start = performance.now()\n let errorMessage: string | null = null\n\n try {\n await entry.callback(entry.state)\n } catch (error) {\n errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`Error in callback for element ${entry.state.name}:`, error)\n }\n\n const status: callbackStatus = errorMessage !== null ? \"error\" : \"success\"\n this.finalizeCallback(entry, callbackHitType, start, status, errorMessage)\n }\n\n private finalizeCallback(\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType,\n startTime: number,\n status: callbackStatus,\n errorMessage: string | null\n ): void {\n const elapsed = performance.now() - startTime\n\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n this.currentlyActiveHandler?.unobserveElement(entry.element)\n\n entry.completedAt = Date.now()\n const next = this.updateElementState(entry, {\n isCallbackRunning: false,\n isActive: false,\n durationMs: elapsed,\n status,\n error: errorMessage,\n })\n\n if (next.reactivateAfter !== Infinity) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(entry.element)\n }, next.reactivateAfter)\n }\n\n const isLastActiveElement = this.activeElementCount === 0\n this.removeGlobalListenersIfIdle()\n\n this.eventEmitter.emit({\n type: \"callbackCompleted\",\n timestamp: Date.now(),\n element: entry.element,\n state: next,\n hitType: callbackHitType,\n elapsed,\n status,\n errorMessage,\n wasLastActiveElement: isLastActiveElement,\n })\n }\n\n private updateHitCounters(callbackHitType: CallbackHitType): void {\n switch (callbackHitType.kind) {\n case \"mouse\":\n this._globalCallbackHits.mouse[callbackHitType.subType]++\n break\n case \"tab\":\n this._globalCallbackHits.tab[callbackHitType.subType]++\n break\n case \"scroll\":\n this._globalCallbackHits.scroll[callbackHitType.subType]++\n break\n case \"touch\":\n this._globalCallbackHits.touch++\n break\n case \"viewport\":\n this._globalCallbackHits.viewport++\n break\n default:\n callbackHitType satisfies never\n }\n this._globalCallbackHits.total++\n }\n\n private async setDeviceStrategy(strategy: CurrentDeviceStrategy): Promise<void> {\n const previousStrategy = this.currentDeviceStrategy\n\n if (previousStrategy !== strategy) {\n this.devLog(`Switching device strategy from ${previousStrategy} to ${strategy}`)\n }\n\n this.currentlyActiveHandler?.disconnect()\n\n // Lazy load the handler\n this.currentlyActiveHandler =\n strategy === \"mouse\" || strategy === \"pen\"\n ? await this.getOrCreateDesktopHandler()\n : await this.getOrCreateTouchHandler()\n\n this.currentlyActiveHandler.connect()\n }\n\n private handlePointerMove = (e: PointerEvent): void => {\n this.pendingPointerEvent = e\n\n if (e.pointerType !== this.currentDeviceStrategy) {\n this.eventEmitter.emit({\n type: \"deviceStrategyChanged\",\n timestamp: Date.now(),\n newStrategy: e.pointerType as CurrentDeviceStrategy,\n oldStrategy: this.currentDeviceStrategy,\n })\n\n this.currentDeviceStrategy = e.pointerType as CurrentDeviceStrategy\n this.setDeviceStrategy(this.currentDeviceStrategy)\n }\n\n if (this.rafId) {\n return\n }\n\n this.rafId = requestAnimationFrame(() => {\n // Only process mouse movements for desktop handler (mouse/pen)\n if (!this.isUsingDesktopHandler) {\n this.rafId = null\n\n return\n }\n\n if (this.pendingPointerEvent) {\n this.desktopHandler?.processMouseMovement(this.pendingPointerEvent)\n }\n\n this.rafId = null\n })\n }\n\n private initializeGlobalListeners(): void {\n if (this.isSetup || typeof document === \"undefined\") {\n return\n }\n\n this.devLog(\"Initializing global listeners (pointermove, MutationObserver)\")\n this.setDeviceStrategy(this.currentDeviceStrategy)\n\n document.addEventListener(\"pointermove\", this.handlePointerMove)\n\n this.domObserver = new MutationObserver(this.handleDomMutations)\n this.domObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: false,\n })\n\n this.isSetup = true\n }\n\n private removeGlobalListeners(): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n this.isSetup = false\n this.domObserver?.disconnect()\n this.domObserver = null\n\n document.removeEventListener(\"pointermove\", this.handlePointerMove)\n this.currentlyActiveHandler?.disconnect()\n\n if (this.rafId) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n\n this.pendingPointerEvent = null\n }\n\n private handleDomMutations = (mutationsList: MutationRecord[]): void => {\n if (!mutationsList.length) {\n return\n }\n\n this.desktopHandler?.invalidateTabCache()\n\n let hasChildListChange = false\n for (let i = 0; i < mutationsList.length; i++) {\n const mutation = mutationsList[i]\n if (\n mutation &&\n mutation.type === \"childList\" &&\n (mutation.removedNodes.length > 0 || mutation.addedNodes.length > 0)\n ) {\n hasChildListChange = true\n break\n }\n }\n\n if (!hasChildListChange) {\n return\n }\n\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isParked) {\n if (entry.element.isConnected) {\n this.resumeReconnected(entry)\n }\n } else if (!entry.element.isConnected) {\n this.parkDisconnected(entry)\n }\n }\n }\n\n /**\n * Deactivate an element that was detached from the DOM. It is kept in\n * {@link elementEntries} (still registered) and flagged `isParked` so it can be\n * resumed when it reconnects.\n */\n private parkDisconnected(entry: ForesightElementInternal): void {\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(entry.element)\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n this.parkedElementCount++\n // Preserve `isPredicted`: an element that already fired its callback must stay\n // \"fired\" so it is not treated as fresh (and reactivated) when it reconnects.\n this.updateElementState(entry, {\n isActive: false,\n isCallbackRunning: false,\n isParked: true,\n })\n }\n\n /**\n * Re-activate a previously parked element once it is back in the DOM. Mirrors\n * the activation rules used everywhere else: disabled / limited connections stay\n * inactive, and an element that already fired its callback stays inactive too\n * (it resumes the same state it had before it detached).\n */\n private resumeReconnected(entry: ForesightElementInternal): void {\n this.parkedElementCount--\n\n const eligible = entry.state.isEnabled && !entry.state.isLimitedConnection\n // Only resume as active if it was active before detaching. A fired element\n // (isPredicted) was already inactive, so it stays inactive on reconnect.\n const isActive = eligible && !entry.state.isPredicted\n if (isActive) {\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(entry.element)\n }\n\n this.updateElementState(entry, { isActive, isParked: false })\n\n // If it fired with a finite reactivateAfter, resume the reactivation timer that\n // was cleared when it parked, so the cooldown continues from reconnect.\n if (eligible && entry.state.isPredicted && entry.state.reactivateAfter !== Infinity) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(entry.element)\n }, entry.state.reactivateAfter)\n }\n }\n\n /**\n * Tear down global listeners only when nothing needs them: no active elements\n * to predict on, and no parked elements waiting to reconnect (which need the\n * MutationObserver to detect their return).\n */\n private removeGlobalListenersIfIdle(): void {\n if (this.activeElementCount > 0 || this.parkedElementCount > 0) {\n return\n }\n\n this.removeGlobalListeners()\n }\n\n public alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void {\n const result = applySettingsChanges(this._globalSettings, props)\n\n if (result.positionHistorySizeChanged && this.desktopHandler) {\n this.desktopHandler.trajectoryPositions.positions.resize(\n this._globalSettings.positionHistorySize\n )\n }\n\n if (result.scrollPredictionChanged && this.isUsingDesktopHandler && this.desktopHandler) {\n if (this._globalSettings.enableScrollPrediction) {\n this.desktopHandler.connectScrollPredictor()\n } else {\n this.desktopHandler.disconnectScrollPredictor()\n }\n }\n\n if (result.tabPredictionChanged && this.isUsingDesktopHandler && this.desktopHandler) {\n if (this._globalSettings.enableTabPrediction) {\n this.desktopHandler.connectTabPredictor()\n } else {\n this.desktopHandler.disconnectTabPredictor()\n }\n }\n\n if (result.hitSlopChanged) {\n this.forceUpdateAllElementBounds()\n }\n\n if (result.touchStrategyChanged && !this.isUsingDesktopHandler && this.touchDeviceHandler) {\n this.touchDeviceHandler.setTouchPredictor()\n }\n\n if (result.changedSettings.length > 0) {\n this.eventEmitter.emit({\n type: \"managerSettingsChanged\",\n timestamp: Date.now(),\n managerData: this.getManagerData,\n updatedSettings: result.changedSettings,\n })\n }\n }\n\n private forceUpdateAllElementBounds(): void {\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isIntersectingWithViewport) {\n this.forceUpdateElementBounds(entry)\n }\n }\n }\n\n /**\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.\n * We need an observer for that\n */\n private forceUpdateElementBounds(entry: ForesightElementInternal): void {\n const newOriginalRect = entry.element.getBoundingClientRect()\n\n this.updateElementBounds(entry, {\n originalRect: newOriginalRect,\n expandedRect: getExpandedRect(newOriginalRect, entry.state.hitSlop),\n })\n }\n\n private devLog(message: string): void {\n if (this._globalSettings.enableManagerLogging) {\n console.log(`%c🛠️ ForesightManager: ${message}`, \"color: #16a34a; font-weight: bold;\")\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js.foresight",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Predicts where users will click based on mouse movement, keyboard navigation, and scroll behavior. Includes touch device support. Triggers callbacks before interactions happen to enable prefetching and faster UI responses. Works with any framework.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,2 +0,0 @@
1
- var e=class{get isConnected(){return this._isConnected}constructor(e){this._isConnected=!1,this._cachedLogStyle=null,this.elements=e.elements,this.callCallback=e.callCallback,this.emit=e.emit,this.hasListeners=e.hasListeners,this.updateElementState=e.updateElementState,this.settings=e.settings}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.isConnected||(this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0)}devLog(e){this.settings.enableManagerLogging&&(this._cachedLogStyle===null&&(this._cachedLogStyle=`color: ${this.moduleName.includes(`Predictor`)?`#ea580c`:`#2563eb`}; font-weight: bold;`),console.log(`%c${this.moduleName}: ${e}`,this._cachedLogStyle))}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};export{e as t};
2
- //# sourceMappingURL=BaseForesightModule-BxWJ6mzK.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BaseForesightModule-BxWJ6mzK.mjs","names":[],"sources":["../src/core/BaseForesightModule.ts"],"sourcesContent":["import type {\n CallbackHitType,\n ForesightElement,\n ForesightElementInternal,\n ForesightElementState,\n ForesightEvent,\n ForesightEventMap,\n ForesightManagerSettings,\n} from \"../types/types\"\n\ntype CallCallbackFunction = (\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType\n) => void\n\ntype EmitFunction = <K extends ForesightEvent>(event: ForesightEventMap[K]) => void\n\nexport type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean\n\ntype UpdateElementStateFunction = (\n entry: ForesightElementInternal,\n patch: Partial<ForesightElementState>\n) => ForesightElementState\n\nexport type ForesightModuleDependencies = {\n elements: ReadonlyMap<ForesightElement, ForesightElementInternal>\n callCallback: CallCallbackFunction\n emit: EmitFunction\n hasListeners: HasListenersFunction\n updateElementState: UpdateElementStateFunction\n settings: ForesightManagerSettings\n}\n\nexport abstract class BaseForesightModule {\n protected abortController?: AbortController\n protected elements: ReadonlyMap<ForesightElement, ForesightElementInternal>\n protected callCallback: CallCallbackFunction\n protected emit: EmitFunction\n protected hasListeners: HasListenersFunction\n protected updateElementState: UpdateElementStateFunction\n protected settings: ForesightManagerSettings\n private _isConnected = false\n private _cachedLogStyle: string | null = null\n\n public get isConnected(): boolean {\n return this._isConnected\n }\n\n protected abstract readonly moduleName: string // Name of the implementor class for debugging purposes\n\n constructor(dependencies: ForesightModuleDependencies) {\n this.elements = dependencies.elements\n this.callCallback = dependencies.callCallback\n this.emit = dependencies.emit\n this.hasListeners = dependencies.hasListeners\n this.updateElementState = dependencies.updateElementState\n this.settings = dependencies.settings\n }\n\n public disconnect(): void {\n if (!this.isConnected) {\n return\n }\n\n this.devLog(`Disconnecting ${this.moduleName}...`)\n this.abortController?.abort(`${this.moduleName} module disconnected`)\n this.onDisconnect()\n this._isConnected = false\n }\n\n public connect(): void {\n if (this.isConnected) {\n return\n }\n\n this.devLog(`Connecting ${this.moduleName}...`)\n this.onConnect()\n this._isConnected = true\n }\n\n public devLog(message: string): void {\n if (!this.settings.enableManagerLogging) {\n return\n }\n\n // Cache the log style on first use to avoid repeated string operations\n if (this._cachedLogStyle === null) {\n const color = this.moduleName.includes(\"Predictor\") ? \"#ea580c\" : \"#2563eb\"\n this._cachedLogStyle = `color: ${color}; font-weight: bold;`\n }\n\n console.log(`%c${this.moduleName}: ${message}`, this._cachedLogStyle)\n }\n\n protected abstract onConnect(): void\n protected abstract onDisconnect(): void\n\n protected createAbortController(): void {\n if (this.abortController && !this.abortController.signal.aborted) {\n return\n }\n\n this.abortController = new AbortController()\n\n this.devLog(`Created new AbortController for ${this.moduleName}`)\n }\n}\n"],"mappings":"AAiCA,IAAsB,EAAtB,KAA0C,CAWxC,IAAW,aAAuB,CAChC,OAAO,KAAK,aAKd,YAAY,EAA2C,mBAThC,wBACkB,KASvC,KAAK,SAAW,EAAa,SAC7B,KAAK,aAAe,EAAa,aACjC,KAAK,KAAO,EAAa,KACzB,KAAK,aAAe,EAAa,aACjC,KAAK,mBAAqB,EAAa,mBACvC,KAAK,SAAW,EAAa,SAG/B,YAA0B,CACnB,KAAK,cAIV,KAAK,OAAO,iBAAiB,KAAK,WAAW,KAAK,CAClD,KAAK,iBAAiB,MAAM,GAAG,KAAK,WAAW,sBAAsB,CACrE,KAAK,cAAc,CACnB,KAAK,aAAe,IAGtB,SAAuB,CACjB,KAAK,cAIT,KAAK,OAAO,cAAc,KAAK,WAAW,KAAK,CAC/C,KAAK,WAAW,CAChB,KAAK,aAAe,IAGtB,OAAc,EAAuB,CAC9B,KAAK,SAAS,uBAKf,KAAK,kBAAoB,OAE3B,KAAK,gBAAkB,UADT,KAAK,WAAW,SAAS,YAAY,CAAG,UAAY,UAC3B,uBAGzC,QAAQ,IAAI,KAAK,KAAK,WAAW,IAAI,IAAW,KAAK,gBAAgB,EAMvE,uBAAwC,CAClC,KAAK,iBAAmB,CAAC,KAAK,gBAAgB,OAAO,UAIzD,KAAK,gBAAkB,IAAI,gBAE3B,KAAK,OAAO,mCAAmC,KAAK,aAAa"}
@@ -1,2 +0,0 @@
1
- import{n as e,r as t,t as n}from"./rectAndHitSlop-T7Z3PZlb.mjs";import{t as r}from"./BaseForesightModule-BxWJ6mzK.mjs";import{t as i}from"./ElementObservingModule-OqTcnagr.mjs";import{t as a}from"./lineSegmentIntersectsRect-x9nicliA.mjs";import{PositionObserver as o}from"position-observer";const s=(e,t,n,r)=>{let i=performance.now();t.add({point:{x:e.x,y:e.y},time:i});let{x:a,y:o}=e;if(t.length<2){r.x=a,r.y=o;return}let[s,c]=t.getFirstLast();if(!s||!c){r.x=a,r.y=o;return}let l=(c.time-s.time)*.001;if(l===0){r.x=a,r.y=o;return}let u=c.point.x-s.point.x,d=c.point.y-s.point.y,f=u/l,p=d/l,m=n*.001;r.x=a+f*m,r.y=o+p*m};var c=class extends r{constructor(e){super(e.dependencies),this.moduleName=`MousePredictor`,this.trajectoryPositions=e.trajectoryPositions,this.mouseTrajectoryEvent={type:`mouseTrajectoryUpdate`,predictionEnabled:!1,trajectoryPositions:this.trajectoryPositions}}updatePointerState(e){let t=this.trajectoryPositions.currentPoint;t.x=e.clientX,t.y=e.clientY,this.settings.enableMousePrediction?s(t,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime,this.trajectoryPositions.predictedPoint):(this.trajectoryPositions.predictedPoint.x=t.x,this.trajectoryPositions.predictedPoint.y=t.y)}processMouseMovement(e){this.updatePointerState(e);let n=this.settings.enableMousePrediction,r=this.trajectoryPositions.currentPoint;for(let e of this.elements.values()){let i=e.state;if(!i.isIntersectingWithViewport||!i.isActive||i.isPredicted)continue;let o=i.elementBounds.expandedRect;if(n)a(r,this.trajectoryPositions.predictedPoint,o)&&this.callCallback(e,{kind:`mouse`,subType:`trajectory`});else if(t(r,o)){this.callCallback(e,{kind:`mouse`,subType:`hover`});continue}}this.hasListeners(`mouseTrajectoryUpdate`)&&(this.mouseTrajectoryEvent.predictionEnabled=n,this.emit(this.mouseTrajectoryEvent))}onDisconnect(){}onConnect(){}},l=class{constructor(e){if(this.head=0,this.count=0,e<=0)throw Error(`CircularBuffer capacity must be greater than 0`);this.capacity=e,this.buffer=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 e=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[e,e]}return[this.getFirst(),this.getLast()]}resize(e){if(e<=0)throw Error(`CircularBuffer capacity must be greater than 0`);if(e===this.capacity)return;let t=this.getAllItems();if(this.capacity=e,this.buffer=Array(e),this.head=0,this.count=0,t.length>e){let n=t.slice(-e);for(let e of n)this.add(e)}else for(let e of t)this.add(e)}getAllItems(){if(this.count===0)return[];let e=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 n=0;n<this.capacity;n++){let r=(t+n)%this.capacity;e[n]=this.buffer[r]}}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}},u=class extends i{constructor(r){super(r),this.moduleName=`DesktopHandler`,this.tabPredictor=null,this.scrollPredictor=null,this.positionObserver=null,this.trajectoryPositions={positions:new l(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}},this.handlePositionChange=e=>{let t=this.settings.enableScrollPrediction;for(let n of e){let e=this.elements.get(n.target);e&&(t?this.scrollPredictor?.handleScrollPrefetch(e,n.boundingClientRect):this.checkForMouseHover(e),this.handlePositionChangeDataUpdates(e,n))}t&&this.scrollPredictor?.resetScrollProps()},this.checkForMouseHover=e=>{t(this.trajectoryPositions.currentPoint,e.state.elementBounds.expandedRect)&&this.callCallback(e,{kind:`mouse`,subType:`hover`})},this.handlePositionChangeDataUpdates=(t,r)=>{let i=r.isIntersecting,a=t.state,o={};a.isIntersectingWithViewport!==i&&(o.isIntersectingWithViewport=i),i&&!n(r.boundingClientRect,a.elementBounds.originalRect)&&(o.elementBounds={hitSlop:a.elementBounds.hitSlop,originalRect:r.boundingClientRect,expandedRect:e(r.boundingClientRect,a.elementBounds.hitSlop)}),Object.keys(o).length!==0&&this.updateElementState(t,o)},this.processMouseMovement=e=>this.mousePredictor.processMouseMovement(e),this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache(),this.connectTabPredictor=async()=>{if(!this.tabPredictor){let{TabPredictor:e}=await import(`./TabPredictor-CohoaRM5.mjs`);this.tabPredictor=new e(this.storedDependencies),this.devLog(`TabPredictor lazy loaded`)}this.tabPredictor.connect()},this.connectScrollPredictor=async()=>{if(!this.scrollPredictor){let{ScrollPredictor:e}=await import(`./ScrollPredictor-r4YnqcfQ.mjs`);this.scrollPredictor=new e({dependencies:this.storedDependencies,trajectoryPositions:this.trajectoryPositions}),this.devLog(`ScrollPredictor lazy loaded`)}this.scrollPredictor.connect()},this.connectMousePredictor=()=>this.mousePredictor.connect(),this.disconnectTabPredictor=()=>this.tabPredictor?.disconnect(),this.disconnectScrollPredictor=()=>this.scrollPredictor?.disconnect(),this.disconnectMousePredictor=()=>this.mousePredictor.disconnect(),this.storedDependencies=r,this.mousePredictor=new c({dependencies:r,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new o(this.handlePositionChange);let e=[`mouse`];this.settings.enableTabPrediction&&e.push(`tab (loading...)`),this.settings.enableScrollPrediction&&e.push(`scroll (loading...)`),this.devLog(`Connected predictors: [${e.join(`, `)}] and PositionObserver`);for(let e of this.elements.keys())this.positionObserver.observe(e)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}observeElement(e){this.positionObserver?.observe(e)}unobserveElement(e){this.positionObserver?.unobserve(e)}get loadedPredictors(){return{mouse:this.mousePredictor!==null,tab:this.tabPredictor!==null,scroll:this.scrollPredictor!==null}}};export{u as DesktopHandler};
2
- //# sourceMappingURL=DesktopHandler-BgCQieJ9.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DesktopHandler-BgCQieJ9.mjs","names":[],"sources":["../src/helpers/predictNextMousePosition.ts","../src/predictors/MousePredictor.ts","../src/helpers/CircularBuffer.ts","../src/managers/DesktopHandler.ts"],"sourcesContent":["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 * @param out - Output point to mutate with the predicted position.\n */\nexport const predictNextMousePosition = (\n currentPoint: Point,\n buffer: CircularBuffer<MousePosition>,\n trajectoryPredictionTimeInMs: number,\n out: Point\n): void => {\n const now = performance.now()\n // Create a copy of currentPoint for buffer storage (buffer needs independent copies)\n buffer.add({ point: { x: currentPoint.x, y: currentPoint.y }, time: now })\n\n const { x, y } = currentPoint\n\n if (buffer.length < 2) {\n out.x = x\n out.y = y\n\n return\n }\n\n const [first, last] = buffer.getFirstLast()\n if (!first || !last) {\n out.x = x\n out.y = y\n\n return\n }\n\n const dt = (last.time - first.time) * 0.001\n if (dt === 0) {\n out.x = x\n out.y = y\n\n return\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 out.x = x + vx * trajectoryPredictionTimeInSeconds\n out.y = y + vy * trajectoryPredictionTimeInSeconds\n}\n","import { lineSegmentIntersectsRect } from \"../helpers/lineSegmentIntersectsRect\"\nimport { predictNextMousePosition } from \"../helpers/predictNextMousePosition\"\nimport { isPointInRectangle } from \"../helpers/rectAndHitSlop\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport type { MouseTrajectoryUpdateEvent, TrajectoryPositions } from \"../types/types\"\n\ninterface MousePredictorConfig {\n dependencies: ForesightModuleDependencies\n trajectoryPositions: TrajectoryPositions\n}\n\nexport class MousePredictor extends BaseForesightModule {\n protected readonly moduleName = \"MousePredictor\"\n\n private trajectoryPositions: TrajectoryPositions\n\n // Pre-allocated event object to avoid creating a new object every frame (~60/sec)\n private readonly mouseTrajectoryEvent: MouseTrajectoryUpdateEvent\n\n constructor(config: MousePredictorConfig) {\n super(config.dependencies)\n\n this.trajectoryPositions = config.trajectoryPositions\n this.mouseTrajectoryEvent = {\n type: \"mouseTrajectoryUpdate\",\n predictionEnabled: false,\n trajectoryPositions: this.trajectoryPositions,\n }\n }\n\n private updatePointerState(e: MouseEvent): void {\n // Mutate existing point objects instead of creating new ones (perf optimization)\n const currentPoint = this.trajectoryPositions.currentPoint\n currentPoint.x = e.clientX\n currentPoint.y = e.clientY\n\n if (this.settings.enableMousePrediction) {\n predictNextMousePosition(\n currentPoint,\n this.trajectoryPositions.positions,\n this.settings.trajectoryPredictionTime,\n this.trajectoryPositions.predictedPoint // reuse existing object\n )\n } else {\n this.trajectoryPositions.predictedPoint.x = currentPoint.x\n this.trajectoryPositions.predictedPoint.y = currentPoint.y\n }\n }\n\n public processMouseMovement(e: MouseEvent): void {\n this.updatePointerState(e)\n const enablePrediction = this.settings.enableMousePrediction\n const currentPoint = this.trajectoryPositions.currentPoint\n\n for (const internal of this.elements.values()) {\n const state = internal.state\n if (!state.isIntersectingWithViewport || !state.isActive || state.isPredicted) {\n continue\n }\n\n const expandedRect = state.elementBounds.expandedRect\n\n if (!enablePrediction) {\n if (isPointInRectangle(currentPoint, expandedRect)) {\n this.callCallback(internal, { kind: \"mouse\", subType: \"hover\" })\n\n continue\n }\n\n // when enable mouse prediction is off, we only check if the mouse is physically hovering over the element\n } else if (\n lineSegmentIntersectsRect(\n currentPoint,\n this.trajectoryPositions.predictedPoint,\n expandedRect\n )\n ) {\n this.callCallback(internal, { kind: \"mouse\", subType: \"trajectory\" })\n }\n }\n\n if (this.hasListeners(\"mouseTrajectoryUpdate\")) {\n this.mouseTrajectoryEvent.predictionEnabled = enablePrediction\n this.emit(this.mouseTrajectoryEvent)\n }\n }\n\n protected onDisconnect(): void {}\n protected onConnect(): void {}\n}\n","export class CircularBuffer<T> {\n private buffer: T[]\n private head: number = 0\n private count: number = 0\n private capacity: number\n\n constructor(capacity: number) {\n if (capacity <= 0) {\n throw new Error(\"CircularBuffer capacity must be greater than 0\")\n }\n\n this.capacity = capacity\n this.buffer = new Array(capacity)\n }\n\n add(item: T): void {\n this.buffer[this.head] = item\n this.head = (this.head + 1) % this.capacity\n\n if (this.count < this.capacity) {\n this.count++\n }\n }\n\n getFirst(): T | undefined {\n if (this.count === 0) {\n return undefined\n }\n\n if (this.count < this.capacity) {\n return this.buffer[0]\n } else {\n return this.buffer[this.head]\n }\n }\n\n getLast(): T | undefined {\n if (this.count === 0) {\n return undefined\n }\n\n if (this.count < this.capacity) {\n return this.buffer[this.count - 1]\n } else {\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity\n\n return this.buffer[lastIndex]\n }\n }\n\n getFirstLast(): [T | undefined, T | undefined] {\n if (this.count === 0) {\n return [undefined, undefined]\n }\n\n if (this.count === 1) {\n const item = this.count < this.capacity ? this.buffer[0] : this.buffer[this.head]\n\n return [item, item]\n }\n\n const first = this.getFirst()\n const last = this.getLast()\n\n return [first, last]\n }\n\n resize(newCapacity: number): void {\n if (newCapacity <= 0) {\n throw new Error(\"CircularBuffer capacity must be greater than 0\")\n }\n\n if (newCapacity === this.capacity) {\n return\n }\n\n const currentItems = this.getAllItems()\n this.capacity = newCapacity\n this.buffer = new Array(newCapacity)\n this.head = 0\n this.count = 0\n\n if (currentItems.length > newCapacity) {\n const itemsToKeep = currentItems.slice(-newCapacity)\n for (const item of itemsToKeep) {\n this.add(item)\n }\n } else {\n for (const item of currentItems) {\n this.add(item)\n }\n }\n }\n\n private getAllItems(): T[] {\n if (this.count === 0) {\n return []\n }\n\n const result: T[] = new Array(this.count)\n\n if (this.count < this.capacity) {\n for (let i = 0; i < this.count; i++) {\n result[i] = this.buffer[i]\n }\n } else {\n const startIndex = this.head\n for (let i = 0; i < this.capacity; i++) {\n const bufferIndex = (startIndex + i) % this.capacity\n result[i] = this.buffer[bufferIndex]\n }\n }\n\n return result\n }\n\n clear(): void {\n this.head = 0\n this.count = 0\n }\n\n get length(): number {\n return this.count\n }\n\n get size(): number {\n return this.capacity\n }\n\n get isFull(): boolean {\n return this.count === this.capacity\n }\n\n get isEmpty(): boolean {\n return this.count === 0\n }\n}\n","import type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport { MousePredictor } from \"../predictors/MousePredictor\"\nimport type { ScrollPredictor } from \"../predictors/ScrollPredictor\"\nimport type { TabPredictor } from \"../predictors/TabPredictor\"\nimport { PositionObserver, PositionObserverEntry } from \"position-observer\"\nimport type {\n ForesightElement,\n ForesightElementInternal,\n ForesightElementState,\n TrajectoryPositions,\n} from \"../types/types\"\nimport { CircularBuffer } from \"../helpers/CircularBuffer\"\nimport { DEFAULT_POSITION_HISTORY_SIZE } from \"../constants\"\nimport { areRectsEqual, getExpandedRect, isPointInRectangle } from \"../helpers/rectAndHitSlop\"\n\nexport class DesktopHandler extends ElementObservingModule {\n protected readonly moduleName = \"DesktopHandler\"\n\n private mousePredictor: MousePredictor\n private tabPredictor: TabPredictor | null = null\n private scrollPredictor: ScrollPredictor | null = null\n private positionObserver: PositionObserver | null = null\n private storedDependencies: ForesightModuleDependencies\n\n public trajectoryPositions: TrajectoryPositions = {\n positions: new CircularBuffer(DEFAULT_POSITION_HISTORY_SIZE),\n currentPoint: { x: 0, y: 0 },\n predictedPoint: { x: 0, y: 0 },\n }\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n this.storedDependencies = dependencies\n\n // Only MousePredictor is instantiated immediately - Tab and Scroll are lazy-loaded\n this.mousePredictor = new MousePredictor({\n dependencies,\n trajectoryPositions: this.trajectoryPositions,\n })\n }\n\n protected onConnect(): void {\n if (this.settings.enableTabPrediction) {\n this.connectTabPredictor()\n }\n\n if (this.settings.enableScrollPrediction) {\n this.connectScrollPredictor()\n }\n\n this.connectMousePredictor() // We always connect the mouse predictor\n this.positionObserver = new PositionObserver(this.handlePositionChange)\n\n const enabledPredictors = [\"mouse\"]\n\n if (this.settings.enableTabPrediction) {\n enabledPredictors.push(\"tab (loading...)\")\n }\n\n if (this.settings.enableScrollPrediction) {\n enabledPredictors.push(\"scroll (loading...)\")\n }\n\n this.devLog(`Connected predictors: [${enabledPredictors.join(\", \")}] and PositionObserver`)\n\n for (const element of this.elements.keys()) {\n this.positionObserver.observe(element)\n }\n }\n\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\n const enableScrollPosition = this.settings.enableScrollPrediction\n\n for (const positionEntry of entries) {\n const entry = this.elements.get(positionEntry.target)\n\n if (!entry) {\n continue\n }\n\n if (enableScrollPosition) {\n this.scrollPredictor?.handleScrollPrefetch(entry, positionEntry.boundingClientRect)\n } else {\n // If we dont check for scroll prediction, check if the user is hovering over the element during a scroll instead\n this.checkForMouseHover(entry)\n }\n\n // Must run AFTER handleScrollPrefetch - scroll direction is derived from\n // the difference between the old and new originalRect.\n this.handlePositionChangeDataUpdates(entry, positionEntry)\n }\n\n if (enableScrollPosition) {\n this.scrollPredictor?.resetScrollProps()\n }\n }\n\n private checkForMouseHover = (entry: ForesightElementInternal) => {\n if (\n isPointInRectangle(\n this.trajectoryPositions.currentPoint,\n entry.state.elementBounds.expandedRect\n )\n ) {\n this.callCallback(entry, {\n kind: \"mouse\",\n subType: \"hover\",\n })\n }\n }\n\n private handlePositionChangeDataUpdates = (\n entry: ForesightElementInternal,\n positionEntry: PositionObserverEntry\n ) => {\n const isNowIntersecting = positionEntry.isIntersecting\n const state = entry.state\n const patch: Partial<ForesightElementState> = {}\n\n if (state.isIntersectingWithViewport !== isNowIntersecting) {\n patch.isIntersectingWithViewport = isNowIntersecting\n }\n\n if (\n isNowIntersecting &&\n !areRectsEqual(positionEntry.boundingClientRect, state.elementBounds.originalRect)\n ) {\n patch.elementBounds = {\n hitSlop: state.elementBounds.hitSlop,\n originalRect: positionEntry.boundingClientRect,\n expandedRect: getExpandedRect(\n positionEntry.boundingClientRect,\n state.elementBounds.hitSlop\n ),\n }\n }\n\n if (Object.keys(patch).length === 0) {\n return\n }\n\n this.updateElementState(entry, patch)\n }\n\n protected onDisconnect(): void {\n this.disconnectMousePredictor()\n this.disconnectTabPredictor()\n this.disconnectScrollPredictor()\n this.positionObserver?.disconnect()\n this.positionObserver = null\n }\n\n public processMouseMovement = (event: PointerEvent) =>\n this.mousePredictor.processMouseMovement(event)\n\n public invalidateTabCache = () => this.tabPredictor?.invalidateCache()\n\n public observeElement(element: ForesightElement): void {\n this.positionObserver?.observe(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.positionObserver?.unobserve(element)\n }\n\n public connectTabPredictor = async () => {\n if (!this.tabPredictor) {\n const { TabPredictor } = await import(\"../predictors/TabPredictor\")\n this.tabPredictor = new TabPredictor(this.storedDependencies)\n this.devLog(\"TabPredictor lazy loaded\")\n }\n\n this.tabPredictor.connect()\n }\n\n public connectScrollPredictor = async () => {\n if (!this.scrollPredictor) {\n const { ScrollPredictor } = await import(\"../predictors/ScrollPredictor\")\n this.scrollPredictor = new ScrollPredictor({\n dependencies: this.storedDependencies,\n trajectoryPositions: this.trajectoryPositions,\n })\n\n this.devLog(\"ScrollPredictor lazy loaded\")\n }\n\n this.scrollPredictor.connect()\n }\n\n public connectMousePredictor = () => this.mousePredictor.connect()\n\n public disconnectTabPredictor = () => this.tabPredictor?.disconnect()\n public disconnectScrollPredictor = () => this.scrollPredictor?.disconnect()\n public disconnectMousePredictor = () => this.mousePredictor.disconnect()\n\n /** For debugging: returns which predictors have been lazy loaded */\n public get loadedPredictors() {\n return {\n mouse: this.mousePredictor !== null,\n tab: this.tabPredictor !== null,\n scroll: this.scrollPredictor !== null,\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{t as e}from"./BaseForesightModule-BxWJ6mzK.mjs";var t=class extends e{};export{t};
2
- //# sourceMappingURL=ElementObservingModule-OqTcnagr.mjs.map
@@ -1,2 +0,0 @@
1
- import{t as e}from"./BaseForesightModule-BxWJ6mzK.mjs";import{t}from"./lineSegmentIntersectsRect-x9nicliA.mjs";const n=(e,t)=>{let n=t.top-e.top,r=t.left-e.left;return n<-1?`down`:n>1?`up`:r<-1?`right`:r>1?`left`:`none`},r=(e,t,n)=>{let{x:r,y:i}=e,a={x:r,y:i};switch(t){case`down`:a.y+=n;break;case`up`:a.y-=n;break;case`left`:a.x-=n;break;case`right`:a.x+=n;break;case`none`:break;default:}return a};var i=class extends e{constructor(e){super(e.dependencies),this.moduleName=`ScrollPredictor`,this.predictedScrollPoint=null,this.scrollDirection=null,this.onDisconnect=()=>this.resetScrollProps(),this.trajectoryPositions=e.trajectoryPositions,this.scrollTrajectoryEvent={type:`scrollTrajectoryUpdate`,currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:{x:0,y:0},scrollDirection:`none`}}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(e,i){let a=e.state;!a.isIntersectingWithViewport||a.isPredicted||!a.isActive||(this.scrollDirection=this.scrollDirection??n(a.elementBounds.originalRect,i),this.scrollDirection!==`none`&&(this.predictedScrollPoint=this.predictedScrollPoint??r(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),t(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,a.elementBounds.expandedRect)&&this.callCallback(e,{kind:`scroll`,subType:this.scrollDirection}),this.hasListeners(`scrollTrajectoryUpdate`)&&(this.scrollTrajectoryEvent.predictedPoint=this.predictedScrollPoint,this.scrollTrajectoryEvent.scrollDirection=this.scrollDirection,this.emit(this.scrollTrajectoryEvent))))}onConnect(){}};export{i as ScrollPredictor};
2
- //# sourceMappingURL=ScrollPredictor-r4YnqcfQ.mjs.map