js.foresight 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/BaseForesightModule-CNTEJw6R.mjs +2 -0
  3. package/dist/BaseForesightModule-CNTEJw6R.mjs.map +1 -0
  4. package/dist/DesktopHandler-BG7l_h03.mjs +2 -0
  5. package/dist/DesktopHandler-BG7l_h03.mjs.map +1 -0
  6. package/dist/ElementObservingModule-Cg7K0M4r.mjs +2 -0
  7. package/dist/{ElementObservingModule-OqTcnagr.mjs.map → ElementObservingModule-Cg7K0M4r.mjs.map} +1 -1
  8. package/dist/ScrollPredictor-X4cEs36N.mjs +2 -0
  9. package/dist/{ScrollPredictor-r4YnqcfQ.mjs.map → ScrollPredictor-X4cEs36N.mjs.map} +1 -1
  10. package/dist/{TabPredictor-CohoaRM5.mjs → TabPredictor-CIOXiYIK.mjs} +2 -2
  11. package/dist/{TabPredictor-CohoaRM5.mjs.map → TabPredictor-CIOXiYIK.mjs.map} +1 -1
  12. package/dist/{TouchDeviceHandler-CKhsCqLJ.mjs → TouchDeviceHandler-DPhXa0T3.mjs} +2 -2
  13. package/dist/{TouchDeviceHandler-CKhsCqLJ.mjs.map → TouchDeviceHandler-DPhXa0T3.mjs.map} +1 -1
  14. package/dist/{TouchStartPredictor-Dqk28kjI.mjs → TouchStartPredictor-BzmPpzh3.mjs} +2 -2
  15. package/dist/{TouchStartPredictor-Dqk28kjI.mjs.map → TouchStartPredictor-BzmPpzh3.mjs.map} +1 -1
  16. package/dist/{ViewportPredictor-BOtudr8f.mjs → ViewportPredictor-DSupqErU.mjs} +2 -2
  17. package/dist/{ViewportPredictor-BOtudr8f.mjs.map → ViewportPredictor-DSupqErU.mjs.map} +1 -1
  18. package/dist/devLog-CyYTiEHt.mjs +2 -0
  19. package/dist/devLog-CyYTiEHt.mjs.map +1 -0
  20. package/dist/index.d.mts +67 -17
  21. package/dist/index.d.mts.map +1 -1
  22. package/dist/index.mjs +1 -1
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/{lineSegmentIntersectsRect-x9nicliA.mjs → lineSegmentIntersectsRect-qrQZ4bxA.mjs} +1 -1
  25. package/dist/{lineSegmentIntersectsRect-x9nicliA.mjs.map → lineSegmentIntersectsRect-qrQZ4bxA.mjs.map} +1 -1
  26. package/dist/{rectAndHitSlop-T7Z3PZlb.mjs → rectAndHitSlop-HPBlI01J.mjs} +1 -1
  27. package/dist/rectAndHitSlop-HPBlI01J.mjs.map +1 -0
  28. package/package.json +1 -1
  29. package/dist/BaseForesightModule-BxWJ6mzK.mjs +0 -2
  30. package/dist/BaseForesightModule-BxWJ6mzK.mjs.map +0 -1
  31. package/dist/DesktopHandler-BgCQieJ9.mjs +0 -2
  32. package/dist/DesktopHandler-BgCQieJ9.mjs.map +0 -1
  33. package/dist/ElementObservingModule-OqTcnagr.mjs +0 -2
  34. package/dist/ScrollPredictor-r4YnqcfQ.mjs +0 -2
  35. package/dist/rectAndHitSlop-T7Z3PZlb.mjs.map +0 -1
package/README.md CHANGED
@@ -24,7 +24,7 @@ _In the GIF above, the [ForesightJS DevTools](https://foresightjs.com/docs/debug
24
24
  Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. There are official packages for React and Vue:
25
25
 
26
26
  - **JavaScript** → [`js.foresight`](https://foresightjs.com/docs/getting-started/quick-start): the framework-agnostic core, usable in any project.
27
- - **React** → [`@foresightjs/react`](https://foresightjs.com/docs/react/installation): `useForesight`, `useForesights`, `useForesightEvent`, plus [Next.js](https://foresightjs.com/docs/react/nextjs) and [React Router](https://foresightjs.com/docs/react/react-router) examples.
27
+ - **React** → [`@foresightjs/react`](https://foresightjs.com/docs/react/installation): `useForesight`, the `Foresight` component, `useForesightEvent`, plus [Next.js](https://foresightjs.com/docs/react/nextjs) and [React Router](https://foresightjs.com/docs/react/react-router) examples.
28
28
  - **Vue** → [`@foresightjs/vue`](https://foresightjs.com/docs/vue/installation): the `v-foresight` directive and the `useForesight` / `useForesights` / `useForesightEvent` composables.
29
29
 
30
30
  > **Note:** The `@foresightjs/react` and `@foresightjs/vue` packages are at `0.1.0` and not yet stable. They work and are fully tested, but the API may still change.
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./devLog-CyYTiEHt.mjs";var t=class{get isConnected(){return this._isConnected}constructor(e){this._isConnected=!1,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(t){if(!this.settings.enableManagerLogging)return;let n=this.moduleName.includes(`Predictor`)?`#ea580c`:`#2563eb`;e(this.moduleName,t,n)}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};export{t};
2
+ //# sourceMappingURL=BaseForesightModule-CNTEJw6R.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseForesightModule-CNTEJw6R.mjs","names":[],"sources":["../src/core/BaseForesightModule.ts"],"sourcesContent":["import { devLogMessage } from \"../helpers/devLog\"\nimport 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\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 const color = this.moduleName.includes(\"Predictor\") ? \"#ea580c\" : \"#2563eb\"\n devLogMessage(this.moduleName, message, color)\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":"0CAyCA,IAAsB,EAAtB,KAA0C,CAWxC,IAAW,aAAuB,CAChC,OAAO,KAAK,aAKd,YAAY,EAA2C,mBARhC,GASrB,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,CACnC,GAAI,CAAC,KAAK,SAAS,qBACjB,OAGF,IAAM,EAAQ,KAAK,WAAW,SAAS,YAAY,CAAG,UAAY,UAClE,EAAc,KAAK,WAAY,EAAS,EAAM,CAMhD,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-HPBlI01J.mjs";import{t as r}from"./BaseForesightModule-CNTEJw6R.mjs";import{t as i}from"./ElementObservingModule-Cg7K0M4r.mjs";import{t as a}from"./lineSegmentIntersectsRect-qrQZ4bxA.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(this.settings.positionHistorySize),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-CIOXiYIK.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-X4cEs36N.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,t]of this.elements)t.state.isActive&&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-BG7l_h03.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DesktopHandler-BG7l_h03.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 { 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(this.settings.positionHistorySize),\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, entry] of this.elements) {\n if (entry.state.isActive) {\n this.positionObserver.observe(element)\n }\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-CNTEJw6R.mjs";var t=class extends e{};export{t};
2
+ //# sourceMappingURL=ElementObservingModule-Cg7K0M4r.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-Cg7K0M4r.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-CNTEJw6R.mjs";import{t}from"./lineSegmentIntersectsRect-qrQZ4bxA.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-X4cEs36N.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-X4cEs36N.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-CNTEJw6R.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-CIOXiYIK.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-CIOXiYIK.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-Cg7K0M4r.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-DSupqErU.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-BzmPpzh3.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,t]of this.elements)t.state.isActive&&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-DPhXa0T3.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-DPhXa0T3.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, entry] of this.elements) {\n if (entry.state.isActive) {\n this.predictor?.observeElement(element)\n }\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,2BA8DpB,CAC7B,KAAK,OAAO,gCAAgC,CAC5C,KAAK,WAAW,YAAY,qBAEF,KAAK,mBAAmB,CA7DlD,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,GAAM,CAAC,EAAS,KAAU,KAAK,SAC9B,EAAM,MAAM,UACd,KAAK,WAAW,eAAe,EAAQ,CAW7C,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-Cg7K0M4r.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-BzmPpzh3.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-BzmPpzh3.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-Cg7K0M4r.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-DSupqErU.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-DSupqErU.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"}
@@ -0,0 +1,2 @@
1
+ const e=(e,t,n)=>{console.log(`%c${e}: ${t}`,`color: ${n}; font-weight: bold;`)};export{e as t};
2
+ //# sourceMappingURL=devLog-CyYTiEHt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devLog-CyYTiEHt.mjs","names":[],"sources":["../src/helpers/devLog.ts"],"sourcesContent":["/**\n * Logs a styled dev message to the console.\n * Callers are responsible for checking `enableManagerLogging` first.\n */\nexport const devLogMessage = (name: string, message: string, color: string): void => {\n console.log(`%c${name}: ${message}`, `color: ${color}; font-weight: bold;`)\n}\n"],"mappings":"AAIA,MAAa,GAAiB,EAAc,EAAiB,IAAwB,CACnF,QAAQ,IAAI,KAAK,EAAK,IAAI,IAAW,UAAU,EAAM,sBAAsB"}
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 = {
@@ -258,6 +272,20 @@ type BaseForesightManagerSettings = {
258
272
  * @default 3g
259
273
  */
260
274
  minimumConnectionType: MinimumConnectionType;
275
+ /**
276
+ * When `true` the manager mirrors every registered element's prediction state
277
+ * onto `data-*` attributes via direct DOM mutation, so plain CSS can style
278
+ * predictions without any framework re-render. Toggling this applies or
279
+ * removes the attributes on all currently-registered elements.
280
+ * - `data-predicted` — present while `isPredicted` is `true`
281
+ * - `data-active` — present while `isActive` is `true`
282
+ * - `data-callback-running` — present while `isCallbackRunning` is `true`
283
+ * - `data-status` — set to `"success"` or `"error"` once a callback completes
284
+ *
285
+ * @link https://foresightjs.com/docs/getting_started/config#available-global-settings
286
+ * @default true
287
+ */
288
+ setDataAttributes: boolean;
261
289
  };
262
290
  type CurrentDeviceStrategy = "mouse" | "touch" | "pen";
263
291
  /**
@@ -424,8 +452,6 @@ declare class ForesightManager {
424
452
  /** Public read-only view exposing only external state, derived from {@link elementEntries}. */
425
453
  readonly registeredElements: ReadonlyMap<ForesightElement, ForesightElementState>;
426
454
  private idCounter;
427
- private activeElementCount;
428
- private parkedElementCount;
429
455
  private desktopHandler;
430
456
  private touchDeviceHandler;
431
457
  private currentlyActiveHandler;
@@ -446,25 +472,42 @@ declare class ForesightManager {
446
472
  static get instance(): ForesightManager;
447
473
  private generateId;
448
474
  private get isUsingDesktopHandler();
475
+ private get activeElementCount();
476
+ private get parkedElementCount();
449
477
  addEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>, options?: {
450
478
  signal?: AbortSignal;
451
479
  }): void;
452
480
  removeEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>): void;
453
481
  hasListeners<K extends ForesightEvent>(eventType: K): boolean;
454
482
  /**
455
- * Subscribe to state changes for a specific element.
483
+ * Subscribe to logical state changes for a specific element.
456
484
  * 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.
485
+ * immutable state snapshot is replaced. Never fires for geometry-only
486
+ * changes (scroll/resize) - see {@link subscribeToElementBounds}.
487
+ * Use {@link registeredElements} to read the latest state inside the listener.
459
488
  *
460
489
  * @returns An unsubscribe function, or `undefined` if the element is not registered.
461
490
  */
462
491
  subscribeToElement(element: ForesightElement, listener: () => void): (() => void) | undefined;
492
+ /**
493
+ * Subscribe to geometry changes for a specific element (position/size, fired
494
+ * on every scroll/resize tick while visible). Use {@link getElementBounds}
495
+ * to read the latest geometry inside the listener.
496
+ *
497
+ * @returns An unsubscribe function, or `undefined` if the element is not registered.
498
+ */
499
+ subscribeToElementBounds(element: ForesightElement, listener: () => void): (() => void) | undefined;
500
+ /**
501
+ * Returns the current immutable geometry snapshot for a registered element,
502
+ * or `undefined` if the element is not registered.
503
+ */
504
+ getElementBounds(element: ForesightElement): ElementBounds | undefined;
463
505
  get getManagerData(): Readonly<ForesightManagerData>;
464
506
  private getLoadedModulesSnapshot;
465
507
  register(options: ForesightRegisterNodeListOptions): ForesightRegisterResult[];
466
508
  register(options: ForesightRegisterOptions): ForesightRegisterResult;
467
509
  private registerElement;
510
+ private buildRegisterResult;
468
511
  /**
469
512
  * Updates the options of an already-registered element.
470
513
  * Only the provided fields are updated; omitted fields keep their current values.
@@ -474,7 +517,7 @@ declare class ForesightManager {
474
517
  */
475
518
  updateElementOptions(element: ForesightElement, options: Partial<ForesightRegisterOptionsWithoutElement>): ForesightElementState;
476
519
  /**
477
- * Create a subscribe function for an element.
520
+ * Create a subscribe function for a listener set (state or bounds subscribers).
478
521
  * Returns an unsubscribe callback when called.
479
522
  */
480
523
  private makeSubscribe;
@@ -484,6 +527,16 @@ declare class ForesightManager {
484
527
  * stable-reference contract relied on by useSyncExternalStore and shallowRef.
485
528
  */
486
529
  private updateElementState;
530
+ /**
531
+ * Replace the immutable geometry ref for an element and notify bounds
532
+ * subscribers. No-op when both rects are content-equal. Preserves the
533
+ * stable-reference contract, mirroring {@link updateElementState}.
534
+ *
535
+ * When a single trigger changes both geometry and logical state (position
536
+ * change, hitSlop update), bounds must be updated BEFORE the state patch so
537
+ * state subscribers always read fresh geometry.
538
+ */
539
+ private updateElementBounds;
487
540
  unregister(element: ForesightElement | NodeListOf<ForesightElement>, unregisterReason?: ElementUnregisteredReason): void;
488
541
  private unregisterElement;
489
542
  reactivate(element: ForesightElement | NodeListOf<ForesightElement>): void;
@@ -549,8 +602,5 @@ declare class ForesightManager {
549
602
  */
550
603
  declare const createUnregisteredSnapshot: (isLimitedConnection: boolean) => ForesightElementState;
551
604
  //#endregion
552
- //#region src/core/BaseForesightModule.d.ts
553
- type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean;
554
- //#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 };
605
+ 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 HitSlop, type ManagerSettingsChangedEvent, type MinimumConnectionType, type MouseTrajectoryUpdateEvent, type ScrollDirection, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting, createUnregisteredSnapshot };
556
606
  //# 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"],"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{t as e}from"./devLog-CyYTiEHt.mjs";import{a as t,i as n,n as r,o as i,t as a}from"./rectAndHitSlop-HPBlI01J.mjs";var o=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 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`,setDataAttributes:!0}),f=(e,t,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:n(c),g=p!==!1;return{state:{id:t,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:r(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:i}),m={"data-predicted":e=>e.isPredicted,"data-active":e=>e.isActive,"data-callback-running":e=>e.isCallbackRunning,"data-status":e=>e.status},h=Object.entries(m),g=Object.keys(m),_=(e,t)=>{for(let[n,r]of h){let i=r(t);typeof i==`string`?e.setAttribute(n,i):e.toggleAttribute(n,i===!0)}},v=e=>{for(let t of g)e.removeAttribute(t)};var y=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 b={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}},x=(e,t)=>e!==void 0&&t!==e,S=(e,n,r)=>{if(!x(r,e[n]))return!1;let{min:i,max:a}=b[n];return e[n]=t(r,i,a,n),!0},C=(e,t,n)=>x(n,e[t])?(e[t]=n,!0):!1,w=(e,t)=>{let r=[],i=!1,o=!1,s=!1,c=!1,l=!1,u=!1;if(!t)return{changedSettings:r,positionHistorySizeChanged:i,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l,setDataAttributesChanged:u};for(let n of[`trajectoryPredictionTime`,`positionHistorySize`,`scrollMargin`,`tabOffset`]){let a=e[n];S(e,n,t[n])&&(r.push({setting:n,oldValue:a,newValue:e[n]}),n===`positionHistorySize`&&(i=!0))}for(let n of[`enableMousePrediction`,`enableScrollPrediction`,`enableTabPrediction`,`enableManagerLogging`,`setDataAttributes`]){let i=e[n];C(e,n,t[n])&&(r.push({setting:n,oldValue:i,newValue:e[n]}),n===`enableScrollPrediction`&&(o=!0),n===`enableTabPrediction`&&(s=!0),n===`setDataAttributes`&&(u=!0))}if(t.defaultHitSlop!==void 0){let i=e.defaultHitSlop,o=n(t.defaultHitSlop);a(i,o)||(e.defaultHitSlop=o,r.push({setting:`defaultHitSlop`,oldValue:i,newValue:o}),c=!0)}if(x(t.touchDeviceStrategy,e.touchDeviceStrategy)){let n=e.touchDeviceStrategy;e.touchDeviceStrategy=t.touchDeviceStrategy,r.push({setting:`touchDeviceStrategy`,oldValue:n,newValue:t.touchDeviceStrategy}),l=!0}if(x(t.minimumConnectionType,e.minimumConnectionType)){let n=e.minimumConnectionType;e.minimumConnectionType=t.minimumConnectionType,r.push({setting:`minimumConnectionType`,oldValue:n,newValue:t.minimumConnectionType})}return{changedSettings:r,positionHistorySizeChanged:i,scrollPredictionChanged:o,tabPredictionChanged:s,hitSlopChanged:c,touchStrategyChanged:l,setDataAttributesChanged:u}};var T=class t{constructor(e){this.elementEntries=new Map,this.registeredElements=new o(this.elementEntries,e=>e.state),this.idCounter=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 y,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)},w(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-BG7l_h03.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-DPhXa0T3.mjs`);this.touchDeviceHandler=new e(this.handlerDependencies),this.devLog(`TouchDeviceHandler lazy loaded`)}return this.touchDeviceHandler}static initialize(e){return this.isInitiated||(t.manager=new t(e)),t.manager}static get isInitiated(){return!!t.manager}static get instance(){return this.initialize()}generateId(){return`foresight-${++this.idCounter}`}get isUsingDesktopHandler(){return this.currentDeviceStrategy===`mouse`||this.currentDeviceStrategy===`pen`}get activeElementCount(){let e=0;for(let t of this.elementEntries.values())t.state.isActive&&e++;return e}get parkedElementCount(){let e=0;for(let t of this.elementEntries.values())t.state.isParked&&e++;return e}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 t=c(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}),this.buildRegisterResult(n,e.element);this.isSetup||this.initializeGlobalListeners();let r=f(e,this.generateId(),this._globalSettings.defaultHitSlop,t);return this.elementEntries.set(e.element,r),this._globalSettings.setDataAttributes&&_(r.element,r.state),r.state.isActive&&this.currentlyActiveHandler?.observeElement(e.element),this.eventEmitter.emit({type:`elementRegistered`,timestamp:Date.now(),element:e.element,state:r.state}),this.buildRegisterResult(r,e.element)}buildRegisterResult(e,t){return{...e.state,unregister:()=>{this.unregister(t)},subscribe:this.makeSubscribe(e.subscribers),getSnapshot:()=>e.state,subscribeToBounds:this.makeSubscribe(e.boundsSubscribers),getBounds:()=>e.bounds}}updateElementOptions(e,t){let i=this.elementEntries.get(e);if(!i)throw Error(`Cannot update options: element is not registered.`);t.callback&&(i.callback=t.callback),t.enabled!==void 0&&this.setElementEnabled(i,e,t.enabled!==!1);let o=i.state.hitSlop;if(t.hitSlop!==void 0){let s=n(t.hitSlop);if(!a(s,o)){o=s;let t=e.getBoundingClientRect();this.updateElementBounds(i,{originalRect:t,expandedRect:r(t,o)})}}let s=i.state.reactivateAfter,c=t.reactivateAfter??s,l=this.updateElementState(i,{name:t.name||i.state.name,meta:t.meta??i.state.meta,reactivateAfter:c,hitSlop:o});return c!==s&&(i.reactivateTimeoutId!==void 0&&this.clearReactivateTimeout(i),c!==1/0&&l.isPredicted&&(i.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,this._globalSettings.setDataAttributes&&_(e.element,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(a(t.originalRect,n.originalRect)&&a(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);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(),this._globalSettings.setDataAttributes&&v(e);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.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.currentlyActiveHandler?.observeElement(t)):(this.clearReactivateTimeout(e),this.currentlyActiveHandler?.unobserveElement(t)),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;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),this.updateElementState(e,{isActive:!1,isCallbackRunning:!1,isParked:!0})}resumeReconnected(e){let t=e.state.isEnabled&&!e.state.isLimitedConnection,n=t&&!e.state.isPredicted;n&&(this.isSetup||this.initializeGlobalListeners(),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=w(this._globalSettings,e);if(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.setDataAttributesChanged){let e=this._globalSettings.setDataAttributes;for(let t of this.elementEntries.values())e?_(t.element,t.state):v(t.element)}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:r(t,e.state.hitSlop)})}devLog(t){this._globalSettings.enableManagerLogging&&e(`🛠️ ForesightManager`,t,`#16a34a`)}};export{T 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/helpers/dataAttributes.ts","../src/core/ForesightEventEmitter.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\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 */\nexport const 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_REACTIVATE_AFTER,\n DEFAULT_SCROLL_MARGIN,\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 setDataAttributes: true,\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_REACTIVATE_AFTER,\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_REACTIVATE_AFTER,\n }\n}\n","import type { ForesightElementState } from \"../types/types\"\n\n// Single source of truth for the mirrored attributes: a boolean toggles the\n// bare attribute, a string becomes its value, undefined removes it.\nconst DATA_ATTRIBUTES: Record<\n string,\n (state: ForesightElementState) => boolean | string | undefined\n> = {\n \"data-predicted\": state => state.isPredicted,\n \"data-active\": state => state.isActive,\n \"data-callback-running\": state => state.isCallbackRunning,\n \"data-status\": state => state.status,\n}\n\nconst DATA_ATTRIBUTE_ENTRIES = Object.entries(DATA_ATTRIBUTES)\nconst DATA_ATTRIBUTE_NAMES = Object.keys(DATA_ATTRIBUTES)\n\n/**\n * Mirror the element's prediction state onto `data-*` attributes via direct DOM\n * mutation, so plain CSS (e.g. `[data-predicted]`) can style predictions without\n * any subscriber re-render. Controlled globally via the `setDataAttributes`\n * manager setting.\n */\nexport const applyDataAttributes = (element: Element, state: ForesightElementState): void => {\n for (const [attribute, read] of DATA_ATTRIBUTE_ENTRIES) {\n const value = read(state)\n if (typeof value === \"string\") {\n element.setAttribute(attribute, value)\n } else {\n element.toggleAttribute(attribute, value === true)\n }\n }\n}\n\n/** Remove every mirrored attribute from the element (used on unregister). */\nexport const removeDataAttributes = (element: Element): void => {\n for (const attribute of DATA_ATTRIBUTE_NAMES) {\n element.removeAttribute(attribute)\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","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 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 * 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 */\nconst 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\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 * 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 setDataAttributesChanged: 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 let setDataAttributesChanged = false\n\n if (!props) {\n return {\n changedSettings,\n positionHistorySizeChanged,\n scrollPredictionChanged,\n tabPredictionChanged,\n hitSlopChanged,\n touchStrategyChanged,\n setDataAttributesChanged,\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 \"enableManagerLogging\",\n \"setDataAttributes\",\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 if (key === \"setDataAttributes\") {\n setDataAttributesChanged = 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 (shouldUpdateSetting(props.touchDeviceStrategy, settings.touchDeviceStrategy)) {\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 (shouldUpdateSetting(props.minimumConnectionType, settings.minimumConnectionType)) {\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 setDataAttributesChanged,\n }\n}\n","import { DerivedMapView } from \"../helpers/DerivedMapView\"\nimport { devLogMessage } from \"../helpers/devLog\"\nimport { areRectsEqual, getExpandedRect, normalizeHitSlop } from \"../helpers/rectAndHitSlop\"\nimport { hasConnectionLimitations, userUsesTouchDevice } from \"../helpers/shouldRegister\"\nimport {\n createDefaultManagerSettings,\n createElementInternal,\n createInitialCallbackHits,\n} from \"../helpers/createInitialState\"\nimport { applyDataAttributes, removeDataAttributes } from \"../helpers/dataAttributes\"\nimport { ForesightEventEmitter } from \"../core/ForesightEventEmitter\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport type { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport { applySettingsChanges } 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\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 // Side-effect flags in the result are no-ops here: handlers are still null\n // and the settings-changed event only fires from alterGlobalSettings.\n applySettingsChanges(this._globalSettings, initialSettings)\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 private get activeElementCount(): number {\n let count = 0\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isActive) {\n count++\n }\n }\n\n return count\n }\n\n private get parkedElementCount(): number {\n let count = 0\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isParked) {\n count++\n }\n }\n\n return count\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 = hasConnectionLimitations(this._globalSettings.minimumConnectionType)\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 this.buildRegisterResult(previousEntry, options.element)\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 if (this._globalSettings.setDataAttributes) {\n applyDataAttributes(entry.element, entry.state)\n }\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.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 this.buildRegisterResult(entry, options.element)\n }\n\n private buildRegisterResult(\n entry: ForesightElementInternal,\n element: ForesightElement\n ): ForesightRegisterResult {\n return {\n ...entry.state,\n unregister: () => {\n this.unregister(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\n if (this._globalSettings.setDataAttributes) {\n applyDataAttributes(entry.element, next)\n }\n\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 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 if (this._globalSettings.setDataAttributes) {\n removeDataAttributes(element)\n }\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.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.currentlyActiveHandler?.observeElement(element)\n } else {\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(element)\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 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\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 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.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.setDataAttributesChanged) {\n const mirror = this._globalSettings.setDataAttributes\n for (const entry of this.elementEntries.values()) {\n if (mirror) {\n applyDataAttributes(entry.element, entry.state)\n } else {\n removeDataAttributes(entry.element)\n }\n }\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 devLogMessage(\"🛠️ ForesightManager\", message, \"#16a34a\")\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
1
  const e=(e,t,n)=>{let r=0,i=1,a=t.x-e.x,o=t.y-e.y,s=(e,t)=>{if(e===0){if(t<0)return!1}else{let n=t/e;if(e<0){if(n>i)return!1;n>r&&(r=n)}else{if(n<r)return!1;n<i&&(i=n)}}return!0};return!s(-a,e.x-n.left)||!s(a,n.right-e.x)||!s(-o,e.y-n.top)||!s(o,n.bottom-e.y)?!1:r<=i};export{e as t};
2
- //# sourceMappingURL=lineSegmentIntersectsRect-x9nicliA.mjs.map
2
+ //# sourceMappingURL=lineSegmentIntersectsRect-qrQZ4bxA.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"lineSegmentIntersectsRect-x9nicliA.mjs","names":[],"sources":["../src/helpers/lineSegmentIntersectsRect.ts"],"sourcesContent":["import type { Point, Rect } from \"../types/types\"\n\n/**\n * Determines if a line segment intersects with a given rectangle.\n * This function implements the Liang-Barsky line clipping algorithm.\n *\n * @param p1 - The starting {@link Point} of the line segment.\n * @param p2 - The ending {@link Point} of the line segment.\n * @param rect - The {@link Rect} to check for intersection.\n * @returns `true` if the line segment intersects the rectangle, `false` otherwise.\n */\nexport const lineSegmentIntersectsRect = (p1: Point, p2: Point, rect: Rect): boolean => {\n let t0 = 0.0\n let t1 = 1.0\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n\n // Hot path; canonical Liang-Barsky form is fastest in V8.\n // fallow-ignore-next-line complexity\n const clipTest = (p: number, q: number): boolean => {\n if (p === 0) {\n // Line is parallel to this clipping edge\n if (q < 0) {\n return false\n } // Line is outside the clipping edge\n } else {\n const r = q / p\n if (p < 0) {\n // Line proceeds from outside to inside\n if (r > t1) {\n return false\n } // Line segment ends before crossing edge\n\n if (r > t0) {\n t0 = r\n } // Update entry point\n } else {\n // Line proceeds from inside to outside\n if (r < t0) {\n return false\n } // Line segment starts after crossing edge\n\n if (r < t1) {\n t1 = r\n } // Update exit point\n }\n }\n\n return true\n }\n\n // Clip against all four edges of the rectangle\n if (!clipTest(-dx, p1.x - rect.left)) {\n return false\n } // Left edge\n\n if (!clipTest(dx, rect.right - p1.x)) {\n return false\n } // Right edge\n\n if (!clipTest(-dy, p1.y - rect.top)) {\n return false\n } // Top edge\n\n if (!clipTest(dy, rect.bottom - p1.y)) {\n return false\n } // Bottom edge\n\n // If t0 <= t1, the line segment intersects the rectangle (or lies within it)\n return t0 <= t1\n}\n"],"mappings":"AAWA,MAAa,GAA6B,EAAW,EAAW,IAAwB,CACtF,IAAI,EAAK,EACL,EAAK,EACH,EAAK,EAAG,EAAI,EAAG,EACf,EAAK,EAAG,EAAI,EAAG,EAIf,GAAY,EAAW,IAAuB,CAClD,GAAI,IAAM,MAEJ,EAAI,EACN,MAAO,OAEJ,CACL,IAAM,EAAI,EAAI,EACd,GAAI,EAAI,EAAG,CAET,GAAI,EAAI,EACN,MAAO,GAGL,EAAI,IACN,EAAK,OAEF,CAEL,GAAI,EAAI,EACN,MAAO,GAGL,EAAI,IACN,EAAK,IAKX,MAAO,IAqBT,MAjBI,CAAC,EAAS,CAAC,EAAI,EAAG,EAAI,EAAK,KAAK,EAIhC,CAAC,EAAS,EAAI,EAAK,MAAQ,EAAG,EAAE,EAIhC,CAAC,EAAS,CAAC,EAAI,EAAG,EAAI,EAAK,IAAI,EAI/B,CAAC,EAAS,EAAI,EAAK,OAAS,EAAG,EAAE,CAC5B,GAIF,GAAM"}
1
+ {"version":3,"file":"lineSegmentIntersectsRect-qrQZ4bxA.mjs","names":[],"sources":["../src/helpers/lineSegmentIntersectsRect.ts"],"sourcesContent":["import type { Point, Rect } from \"../types/types\"\n\n/**\n * Determines if a line segment intersects with a given rectangle.\n * This function implements the Liang-Barsky line clipping algorithm.\n *\n * @param p1 - The starting {@link Point} of the line segment.\n * @param p2 - The ending {@link Point} of the line segment.\n * @param rect - The {@link Rect} to check for intersection.\n * @returns `true` if the line segment intersects the rectangle, `false` otherwise.\n */\nexport const lineSegmentIntersectsRect = (p1: Point, p2: Point, rect: Rect): boolean => {\n let t0 = 0.0\n let t1 = 1.0\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n\n // Hot path; canonical Liang-Barsky form is fastest in V8.\n // fallow-ignore-next-line complexity\n const clipTest = (p: number, q: number): boolean => {\n if (p === 0) {\n // Line is parallel to this clipping edge\n if (q < 0) {\n return false\n } // Line is outside the clipping edge\n } else {\n const r = q / p\n if (p < 0) {\n // Line proceeds from outside to inside\n if (r > t1) {\n return false\n } // Line segment ends before crossing edge\n\n if (r > t0) {\n t0 = r\n } // Update entry point\n } else {\n // Line proceeds from inside to outside\n if (r < t0) {\n return false\n } // Line segment starts after crossing edge\n\n if (r < t1) {\n t1 = r\n } // Update exit point\n }\n }\n\n return true\n }\n\n // Clip against all four edges of the rectangle\n if (!clipTest(-dx, p1.x - rect.left)) {\n return false\n } // Left edge\n\n if (!clipTest(dx, rect.right - p1.x)) {\n return false\n } // Right edge\n\n if (!clipTest(-dy, p1.y - rect.top)) {\n return false\n } // Top edge\n\n if (!clipTest(dy, rect.bottom - p1.y)) {\n return false\n } // Bottom edge\n\n // If t0 <= t1, the line segment intersects the rectangle (or lies within it)\n return t0 <= t1\n}\n"],"mappings":"AAWA,MAAa,GAA6B,EAAW,EAAW,IAAwB,CACtF,IAAI,EAAK,EACL,EAAK,EACH,EAAK,EAAG,EAAI,EAAG,EACf,EAAK,EAAG,EAAI,EAAG,EAIf,GAAY,EAAW,IAAuB,CAClD,GAAI,IAAM,MAEJ,EAAI,EACN,MAAO,OAEJ,CACL,IAAM,EAAI,EAAI,EACd,GAAI,EAAI,EAAG,CAET,GAAI,EAAI,EACN,MAAO,GAGL,EAAI,IACN,EAAK,OAEF,CAEL,GAAI,EAAI,EACN,MAAO,GAGL,EAAI,IACN,EAAK,IAKX,MAAO,IAqBT,MAjBI,CAAC,EAAS,CAAC,EAAI,EAAG,EAAI,EAAK,KAAK,EAIhC,CAAC,EAAS,EAAI,EAAK,MAAQ,EAAG,EAAE,EAIhC,CAAC,EAAS,CAAC,EAAI,EAAG,EAAI,EAAK,IAAI,EAI/B,CAAC,EAAS,EAAI,EAAK,OAAS,EAAG,EAAE,CAC5B,GAIF,GAAM"}
@@ -1,2 +1,2 @@
1
1
  const e=2e3,t=1/0,n=(e,t,n,r)=>(e<t?console.warn(`ForesightJS: "${r}" value ${e} is below minimum bound ${t}, clamping to ${t}`):e>n&&console.warn(`ForesightJS: "${r}" value ${e} is above maximum bound ${n}, clamping to ${n}`),Math.min(Math.max(e,t),n)),r=t=>{if(typeof t==`number`){let r=n(t,0,e,`hitslop`);return{top:r,left:r,right:r,bottom:r}}return{top:n(t.top,0,e,`hitslop - top`),left:n(t.left,0,e,`hitslop - left`),right:n(t.right,0,e,`hitslop - right`),bottom:n(t.bottom,0,e,`hitslop - bottom`)}},i=(e,t)=>({left:e.left-t.left,right:e.right+t.right,top:e.top-t.top,bottom:e.bottom+t.bottom}),a=(e,t)=>!e||!t?e===t:e.left===t.left&&e.right===t.right&&e.top===t.top&&e.bottom===t.bottom,o=(e,t)=>e.x>=t.left&&e.x<=t.right&&e.y>=t.top&&e.y<=t.bottom;export{n as a,r as i,i as n,t as o,o as r,a as t};
2
- //# sourceMappingURL=rectAndHitSlop-T7Z3PZlb.mjs.map
2
+ //# sourceMappingURL=rectAndHitSlop-HPBlI01J.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rectAndHitSlop-HPBlI01J.mjs","names":[],"sources":["../src/constants.ts","../src/helpers/clampNumber.ts","../src/helpers/rectAndHitSlop.ts"],"sourcesContent":["//IMPORTANT: when altering these values change the type jsDocs/actual docs for BaseForesightManagerProps\nexport const MIN_TRAJECTORY_PREDICTION_TIME: number = 10\nexport const MAX_TRAJECTORY_PREDICTION_TIME: number = 200\nexport const DEFAULT_TRAJECTORY_PREDICTION_TIME: number = 120\n\nexport const MIN_POSITION_HISTORY_SIZE: number = 2\nexport const MAX_POSITION_HISTORY_SIZE: number = 30\nexport const DEFAULT_POSITION_HISTORY_SIZE: number = 8\n\nexport const MIN_TAB_OFFSET: number = 0\nexport const MAX_TAB_OFFSET: number = 20\nexport const DEFAULT_TAB_OFFSET: number = 2\n\nexport const MIN_HITSLOP: number = 0\nexport const MAX_HITSLOP: number = 2000\nexport const DEFAULT_HITSLOP: number = 0\n\nexport const DEFAULT_SCROLL_MARGIN: number = 150\nexport const MIN_SCROLL_MARGIN: number = 30\nexport const MAX_SCROLL_MARGIN: number = 300\n\nexport const DEFAULT_ENABLE_TAB_PREDICTION: boolean = true\nexport const DEFAULT_ENABLE_MOUSE_PREDICTION: boolean = true\nexport const DEFAULT_ENABLE_SCROLL_PREDICTION: boolean = true\n\nexport const DEFAULT_REACTIVATE_AFTER: number = Infinity\n","export const clampNumber = (\n number: number,\n lowerBound: number,\n upperBound: number,\n settingName: string\n) => {\n if (number < lowerBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is below minimum bound ${lowerBound}, clamping to ${lowerBound}`\n )\n } else if (number > upperBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is above maximum bound ${upperBound}, clamping to ${upperBound}`\n )\n }\n\n return Math.min(Math.max(number, lowerBound), upperBound)\n}\n","import type { HitSlop, Point, Rect } from \"../types/types\"\nimport { MAX_HITSLOP, MIN_HITSLOP } from \"../constants\"\nimport { clampNumber } from \"./clampNumber\"\n\n/**\n * Normalizes a `hitSlop` value into a {@link Rect} object.\n * If `hitSlop` is a number, it's applied uniformly to all sides (top, left, right, bottom).\n * If `hitSlop` is already a `Rect` object, it's returned as is.\n *\n * @param hitSlop - A number for uniform slop, or a {@link Rect} object for specific slop per side.\n * @returns A {@link Rect} object with `top`, `left`, `right`, and `bottom` properties.\n */\nexport const normalizeHitSlop = (hitSlop: HitSlop): Rect => {\n if (typeof hitSlop === \"number\") {\n const clampedValue = clampNumber(hitSlop, MIN_HITSLOP, MAX_HITSLOP, \"hitslop\")\n\n return {\n top: clampedValue,\n left: clampedValue,\n right: clampedValue,\n bottom: clampedValue,\n }\n }\n\n return {\n top: clampNumber(hitSlop.top, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - top\"),\n left: clampNumber(hitSlop.left, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - left\"),\n right: clampNumber(hitSlop.right, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - right\"),\n bottom: clampNumber(hitSlop.bottom, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - bottom\"),\n }\n}\n\n/**\n * Calculates an expanded rectangle by applying a `hitSlop` to a base rectangle.\n * The `hitSlop` values define how much to extend each side of the `baseRect` outwards.\n *\n * @param baseRect - The original {@link Rect} or `DOMRect` to expand.\n * @param hitSlop - A {@link Rect} object defining how much to expand each side\n * (e.g., `hitSlop.left` expands the left boundary further to the left).\n * @returns A new {@link Rect} object representing the expanded area.\n */\nexport const getExpandedRect = (baseRect: Rect | DOMRect, hitSlop: Rect): Rect => {\n return {\n left: baseRect.left - hitSlop.left,\n right: baseRect.right + hitSlop.right,\n top: baseRect.top - hitSlop.top,\n bottom: baseRect.bottom + hitSlop.bottom,\n }\n}\n\n/**\n * Checks if two rectangle objects are equal by comparing their respective\n * `top`, `left`, `right`, and `bottom` properties.\n * Handles cases where one or both rects might be null or undefined.\n *\n * @param rect1 - The first {@link Rect} object to compare.\n * @param rect2 - The second {@link Rect} object to compare.\n * @returns `true` if the rectangles have identical dimensions or if both are null/undefined,\n * `false` otherwise.\n */\nexport const areRectsEqual = (rect1: Rect, rect2: Rect): boolean => {\n if (!rect1 || !rect2) {\n return rect1 === rect2\n }\n\n return (\n rect1.left === rect2.left &&\n rect1.right === rect2.right &&\n rect1.top === rect2.top &&\n rect1.bottom === rect2.bottom\n )\n}\n\nexport const isPointInRectangle = (point: Point, rect: Rect): boolean => {\n return (\n point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom\n )\n}\n"],"mappings":"AAcA,MAAa,EAAsB,IAWtB,EAAmC,ICzBnC,GACX,EACA,EACA,EACA,KAEI,EAAS,EACX,QAAQ,KACN,iBAAiB,EAAY,UAAU,EAAO,0BAA0B,EAAW,gBAAgB,IACpG,CACQ,EAAS,GAClB,QAAQ,KACN,iBAAiB,EAAY,UAAU,EAAO,0BAA0B,EAAW,gBAAgB,IACpG,CAGI,KAAK,IAAI,KAAK,IAAI,EAAQ,EAAW,CAAE,EAAW,ECJ9C,EAAoB,GAA2B,CAC1D,GAAI,OAAO,GAAY,SAAU,CAC/B,IAAM,EAAe,EAAY,EAAA,EAAsB,EAAa,UAAU,CAE9E,MAAO,CACL,IAAK,EACL,KAAM,EACN,MAAO,EACP,OAAQ,EACT,CAGH,MAAO,CACL,IAAK,EAAY,EAAQ,IAAA,EAAkB,EAAa,gBAAgB,CACxE,KAAM,EAAY,EAAQ,KAAA,EAAmB,EAAa,iBAAiB,CAC3E,MAAO,EAAY,EAAQ,MAAA,EAAoB,EAAa,kBAAkB,CAC9E,OAAQ,EAAY,EAAQ,OAAA,EAAqB,EAAa,mBAAmB,CAClF,EAYU,GAAmB,EAA0B,KACjD,CACL,KAAM,EAAS,KAAO,EAAQ,KAC9B,MAAO,EAAS,MAAQ,EAAQ,MAChC,IAAK,EAAS,IAAM,EAAQ,IAC5B,OAAQ,EAAS,OAAS,EAAQ,OACnC,EAaU,GAAiB,EAAa,IACrC,CAAC,GAAS,CAAC,EACN,IAAU,EAIjB,EAAM,OAAS,EAAM,MACrB,EAAM,QAAU,EAAM,OACtB,EAAM,MAAQ,EAAM,KACpB,EAAM,SAAW,EAAM,OAId,GAAsB,EAAc,IAE7C,EAAM,GAAK,EAAK,MAAQ,EAAM,GAAK,EAAK,OAAS,EAAM,GAAK,EAAK,KAAO,EAAM,GAAK,EAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js.foresight",
3
- "version": "4.0.0",
3
+ "version": "4.2.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
@@ -1 +0,0 @@
1
- {"version":3,"file":"rectAndHitSlop-T7Z3PZlb.mjs","names":[],"sources":["../src/constants.ts","../src/helpers/clampNumber.ts","../src/helpers/rectAndHitSlop.ts"],"sourcesContent":["//IMPORTANT: when altering these values change the type jsDocs/actual docs for BaseForesightManagerProps\nexport const MIN_TRAJECTORY_PREDICTION_TIME: number = 10\nexport const MAX_TRAJECTORY_PREDICTION_TIME: number = 200\nexport const DEFAULT_TRAJECTORY_PREDICTION_TIME: number = 120\n\nexport const MIN_POSITION_HISTORY_SIZE: number = 2\nexport const MAX_POSITION_HISTORY_SIZE: number = 30\nexport const DEFAULT_POSITION_HISTORY_SIZE: number = 8\n\nexport const MIN_TAB_OFFSET: number = 0\nexport const MAX_TAB_OFFSET: number = 20\nexport const DEFAULT_TAB_OFFSET: number = 2\n\nexport const MIN_HITSLOP: number = 0\nexport const MAX_HITSLOP: number = 2000\nexport const DEFAULT_HITSLOP: number = 0\n\nexport const DEFAULT_SCROLL_MARGIN: number = 150\nexport const MIN_SCROLL_MARGIN: number = 30\nexport const MAX_SCROLL_MARGIN: number = 300\n\nexport const DEFAULT_ENABLE_TAB_PREDICTION: boolean = true\nexport const DEFAULT_ENABLE_MOUSE_PREDICTION: boolean = true\nexport const DEFAULT_ENABLE_SCROLL_PREDICTION: boolean = true\n\nexport const DEFAULT_STALE_TIME: number = Infinity\n","export const clampNumber = (\n number: number,\n lowerBound: number,\n upperBound: number,\n settingName: string\n) => {\n if (number < lowerBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is below minimum bound ${lowerBound}, clamping to ${lowerBound}`\n )\n } else if (number > upperBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is above maximum bound ${upperBound}, clamping to ${upperBound}`\n )\n }\n\n return Math.min(Math.max(number, lowerBound), upperBound)\n}\n","import type { HitSlop, Point, Rect } from \"../types/types\"\nimport { MAX_HITSLOP, MIN_HITSLOP } from \"../constants\"\nimport { clampNumber } from \"./clampNumber\"\n\n/**\n * Normalizes a `hitSlop` value into a {@link Rect} object.\n * If `hitSlop` is a number, it's applied uniformly to all sides (top, left, right, bottom).\n * If `hitSlop` is already a `Rect` object, it's returned as is.\n *\n * @param hitSlop - A number for uniform slop, or a {@link Rect} object for specific slop per side.\n * @returns A {@link Rect} object with `top`, `left`, `right`, and `bottom` properties.\n */\nexport const normalizeHitSlop = (hitSlop: HitSlop): Rect => {\n if (typeof hitSlop === \"number\") {\n const clampedValue = clampNumber(hitSlop, MIN_HITSLOP, MAX_HITSLOP, \"hitslop\")\n\n return {\n top: clampedValue,\n left: clampedValue,\n right: clampedValue,\n bottom: clampedValue,\n }\n }\n\n return {\n top: clampNumber(hitSlop.top, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - top\"),\n left: clampNumber(hitSlop.left, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - left\"),\n right: clampNumber(hitSlop.right, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - right\"),\n bottom: clampNumber(hitSlop.bottom, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - bottom\"),\n }\n}\n\n/**\n * Calculates an expanded rectangle by applying a `hitSlop` to a base rectangle.\n * The `hitSlop` values define how much to extend each side of the `baseRect` outwards.\n *\n * @param baseRect - The original {@link Rect} or `DOMRect` to expand.\n * @param hitSlop - A {@link Rect} object defining how much to expand each side\n * (e.g., `hitSlop.left` expands the left boundary further to the left).\n * @returns A new {@link Rect} object representing the expanded area.\n */\nexport const getExpandedRect = (baseRect: Rect | DOMRect, hitSlop: Rect): Rect => {\n return {\n left: baseRect.left - hitSlop.left,\n right: baseRect.right + hitSlop.right,\n top: baseRect.top - hitSlop.top,\n bottom: baseRect.bottom + hitSlop.bottom,\n }\n}\n\n/**\n * Checks if two rectangle objects are equal by comparing their respective\n * `top`, `left`, `right`, and `bottom` properties.\n * Handles cases where one or both rects might be null or undefined.\n *\n * @param rect1 - The first {@link Rect} object to compare.\n * @param rect2 - The second {@link Rect} object to compare.\n * @returns `true` if the rectangles have identical dimensions or if both are null/undefined,\n * `false` otherwise.\n */\nexport const areRectsEqual = (rect1: Rect, rect2: Rect): boolean => {\n if (!rect1 || !rect2) {\n return rect1 === rect2\n }\n\n return (\n rect1.left === rect2.left &&\n rect1.right === rect2.right &&\n rect1.top === rect2.top &&\n rect1.bottom === rect2.bottom\n )\n}\n\nexport const isPointInRectangle = (point: Point, rect: Rect): boolean => {\n return (\n point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom\n )\n}\n"],"mappings":"AAcA,MAAa,EAAsB,IAWtB,EAA6B,ICzB7B,GACX,EACA,EACA,EACA,KAEI,EAAS,EACX,QAAQ,KACN,iBAAiB,EAAY,UAAU,EAAO,0BAA0B,EAAW,gBAAgB,IACpG,CACQ,EAAS,GAClB,QAAQ,KACN,iBAAiB,EAAY,UAAU,EAAO,0BAA0B,EAAW,gBAAgB,IACpG,CAGI,KAAK,IAAI,KAAK,IAAI,EAAQ,EAAW,CAAE,EAAW,ECJ9C,EAAoB,GAA2B,CAC1D,GAAI,OAAO,GAAY,SAAU,CAC/B,IAAM,EAAe,EAAY,EAAA,EAAsB,EAAa,UAAU,CAE9E,MAAO,CACL,IAAK,EACL,KAAM,EACN,MAAO,EACP,OAAQ,EACT,CAGH,MAAO,CACL,IAAK,EAAY,EAAQ,IAAA,EAAkB,EAAa,gBAAgB,CACxE,KAAM,EAAY,EAAQ,KAAA,EAAmB,EAAa,iBAAiB,CAC3E,MAAO,EAAY,EAAQ,MAAA,EAAoB,EAAa,kBAAkB,CAC9E,OAAQ,EAAY,EAAQ,OAAA,EAAqB,EAAa,mBAAmB,CAClF,EAYU,GAAmB,EAA0B,KACjD,CACL,KAAM,EAAS,KAAO,EAAQ,KAC9B,MAAO,EAAS,MAAQ,EAAQ,MAChC,IAAK,EAAS,IAAM,EAAQ,IAC5B,OAAQ,EAAS,OAAS,EAAQ,OACnC,EAaU,GAAiB,EAAa,IACrC,CAAC,GAAS,CAAC,EACN,IAAU,EAIjB,EAAM,OAAS,EAAM,MACrB,EAAM,QAAU,EAAM,OACtB,EAAM,MAAQ,EAAM,KACpB,EAAM,SAAW,EAAM,OAId,GAAsB,EAAc,IAE7C,EAAM,GAAK,EAAK,MAAQ,EAAM,GAAK,EAAK,OAAS,EAAM,GAAK,EAAK,KAAO,EAAM,GAAK,EAAK"}